2026-06-21 14:09:35 +09:00
|
|
|
"use strict";
|
2026-06-27 19:22:38 +09:00
|
|
|
|
2026-06-21 14:09:35 +09:00
|
|
|
|
|
|
|
|
(function (global) {
|
|
|
|
|
const Tarinai = global.Tarinai;
|
|
|
|
|
if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_social_move_life.js");
|
|
|
|
|
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
|
|
|
|
|
interactWithOthers(dt) {
|
|
|
|
|
let closeCount = 0;
|
2026-06-24 21:20:53 +09:00
|
|
|
const scanLimit = 8;
|
2026-06-26 19:04:32 +09:00
|
|
|
const selfRadius = Math.max(12, Number(this.radius || 22) || 22);
|
|
|
|
|
const scanRadius = Math.max(96, selfRadius * 2.4 + 88);
|
2026-06-24 17:39:44 +09:00
|
|
|
let scanned = 0;
|
2026-06-26 19:04:32 +09:00
|
|
|
for (const o of this.world.nearbyTarinai(this.x, this.y, scanRadius, true)) {
|
2026-06-21 14:09:35 +09:00
|
|
|
if (o === this || o.dead) continue;
|
2026-06-24 17:39:44 +09:00
|
|
|
if (scanned >= scanLimit && this.fightTimer <= 0.04 && this.birthRitualTimer <= 0.04) break;
|
|
|
|
|
scanned += 1;
|
2026-06-21 14:09:35 +09:00
|
|
|
const dx = o.x - this.x, dy = o.y - this.y;
|
|
|
|
|
const d = Math.hypot(dx, dy);
|
2026-06-26 19:04:32 +09:00
|
|
|
const otherRadius = Math.max(12, Number(o.radius || 22) || 22);
|
|
|
|
|
const bodyContactRange = Math.max(74, selfRadius + otherRadius + 18);
|
|
|
|
|
if (d < 0.01 || d > bodyContactRange) continue;
|
2026-06-21 14:09:35 +09:00
|
|
|
closeCount += 1;
|
2026-06-28 13:40:41 +09:00
|
|
|
|
|
|
|
|
const champion = this.isTarinaiChampion ? this : (o.isTarinaiChampion ? o : null);
|
|
|
|
|
if (champion && this.isTarinaiChampion !== o.isTarinaiChampion) {
|
|
|
|
|
const avoider = champion === this ? o : this;
|
|
|
|
|
const nx = (avoider.x - champion.x) / d;
|
|
|
|
|
const ny = (avoider.y - champion.y) / d;
|
|
|
|
|
const avoidRange = Math.max(92, selfRadius + otherRadius + 34);
|
|
|
|
|
const urgency = clamp(1 - d / avoidRange, 0, 1);
|
|
|
|
|
const push = dt * (64 + urgency * 260);
|
|
|
|
|
avoider.vx += nx * push;
|
|
|
|
|
avoider.vy += ny * push;
|
|
|
|
|
if (d <= selfRadius + otherRadius + 14) {
|
|
|
|
|
avoider.vx += nx * dt * 190;
|
|
|
|
|
avoider.vy += ny * dt * 190;
|
|
|
|
|
champion.vx -= nx * dt * 8;
|
|
|
|
|
champion.vy -= ny * dt * 8;
|
|
|
|
|
avoider.surpriseTimer = Math.max(avoider.surpriseTimer || 0, 0.20);
|
|
|
|
|
avoider.fearTimer = Math.max(avoider.fearTimer || 0, 0.32 * avoider.personalityProfile().fear);
|
|
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(avoider, { safety: dt * 26, social: dt * 5 }, champion);
|
|
|
|
|
if (this.world.relationNotice?.(avoider.id, champion.id, "tarinai-champion-avoid", 18)) {
|
2026-06-28 23:07:40 +09:00
|
|
|
this.world.log(`${avoider.name}はたりない王者の${champion.name}にぶつかりそうになって避けた。`, "relation", { participants: [avoider, champion], eventType: "avoid_relationship", sound: false });
|
2026-06-28 13:40:41 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
avoider.lastNeedShockBreaker = champion;
|
|
|
|
|
avoider.thought = `${champion.name || "相手"}はたりない王者だ`;
|
|
|
|
|
avoider.adjustRelation?.(champion, -dt * 0.025, dt * 0.18, "tarinai_champion_avoid");
|
|
|
|
|
if (this !== champion) continue;
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
|
|
|
|
|
const fightMochiContact = this.world.canFightPair?.(this, o) && (this.hasFightMochiEffect() || o.hasFightMochiEffect());
|
2026-06-25 22:30:35 +09:00
|
|
|
if (fightMochiContact && (this.world.fightPairCooldownRemaining?.(this, o) || 0) <= 0.04 && this.fightCooldown <= 0.04 && o.fightCooldown <= 0.04 && d <= Math.max(42, this.radius + o.radius + 16)) {
|
2026-06-23 22:39:20 +09:00
|
|
|
this.conflictTargetId = o.id;
|
|
|
|
|
o.conflictTargetId = this.id;
|
|
|
|
|
this.conflictUrge = Math.max(this.conflictUrge || 0, 92);
|
|
|
|
|
o.conflictUrge = Math.max(o.conflictUrge || 0, 82);
|
|
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(this, { social: dt * 36, safety: dt * 5 }, o);
|
|
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(o, { social: dt * 30, safety: dt * 5 }, this);
|
|
|
|
|
if (typeof queueForcedTarinaiBehavior === "function") {
|
2026-06-25 22:30:35 +09:00
|
|
|
queueForcedTarinaiBehavior(this, "fight_rival", { target: o, source: "fight_mochi", priority: 180, ttl: 3.2, retryDelay: 0.55, searchRange: 140, replaceSameSource: true, causeText: "\u3051\u3093\u304b\u9905\u306e\u52b9\u679c" });
|
|
|
|
|
queueForcedTarinaiBehavior(o, "fight_rival", { target: this, source: "fight_mochi", priority: 180, ttl: 3.2, retryDelay: 0.55, searchRange: 140, replaceSameSource: true, causeText: "\u3051\u3093\u304b\u9905\u306e\u52b9\u679c" });
|
2026-06-23 22:39:20 +09:00
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
|
2026-06-27 19:22:38 +09:00
|
|
|
const zunchiSpreadChanceA = o.diseaseChance ? o.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1);
|
|
|
|
|
if (this.zunchiDisease && !o.zunchiDisease && (o.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && deterministicChance(this.world, "social-zunchi-spread-a", zunchiSpreadChanceA, this, o)) {
|
2026-06-21 14:09:35 +09:00
|
|
|
if (o.infectZunchiDisease(this)) this.world.log(`${this.name}\u306e\u305a\u3093\u3061\u75c5\u304c${o.name}\u306b\u3046\u3064\u3063\u305f\u3002`, "accident", { participants: [o, this] });
|
|
|
|
|
}
|
2026-06-27 19:22:38 +09:00
|
|
|
const zunchiSpreadChanceB = this.diseaseChance ? this.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1);
|
|
|
|
|
if (o.zunchiDisease && !this.zunchiDisease && (this.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && deterministicChance(this.world, "social-zunchi-spread-b", zunchiSpreadChanceB, this, o)) {
|
2026-06-21 14:09:35 +09:00
|
|
|
if (this.infectZunchiDisease(o)) this.world.log(`${o.name}\u306e\u305a\u3093\u3061\u75c5\u304c${this.name}\u306b\u3046\u3064\u3063\u305f\u3002`, "accident", { participants: [o, this] });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pairBond = this.world.areCoParents?.(this, o);
|
2026-06-21 22:29:00 +09:00
|
|
|
const mixedZunchiSlave = !!this.isZunchiSlave !== !!o.isZunchiSlave;
|
|
|
|
|
if (mixedZunchiSlave && d < 86) {
|
|
|
|
|
const nonSlave = this.isZunchiSlave ? o : this;
|
|
|
|
|
const slave = this.isZunchiSlave ? this : o;
|
|
|
|
|
const nx = (nonSlave.x - slave.x) / d;
|
|
|
|
|
const ny = (nonSlave.y - slave.y) / d;
|
|
|
|
|
nonSlave.vx += nx * dt * 34;
|
|
|
|
|
nonSlave.vy += ny * dt * 34;
|
2026-06-23 22:39:20 +09:00
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(nonSlave, { safety: dt * 24, social: dt * 5 }, slave);
|
|
|
|
|
nonSlave.lastNeedShockBreaker = slave;
|
|
|
|
|
nonSlave.fearTimer = Math.max(nonSlave.fearTimer || 0, 0.22 * nonSlave.personalityProfile().fear);
|
|
|
|
|
nonSlave.target = slave;
|
|
|
|
|
nonSlave.thought = "\u305a\u3093\u3061\u3069\u308c\u3044\u304c\u8fd1\u3044";
|
2026-06-21 22:29:00 +09:00
|
|
|
nonSlave.adjustRelation?.(slave, -dt * 0.08, dt * 0.24, "zunchi_slave_avoid");
|
|
|
|
|
slave.adjustRelation?.(nonSlave, -dt * 0.02, 0, "zunchi_slave_avoid");
|
2026-06-27 19:22:38 +09:00
|
|
|
if (deterministicChance(this.world, "zunchi-slave-surprise", dt * 0.35, nonSlave, slave)) nonSlave.surpriseTimer = Math.max(nonSlave.surpriseTimer || 0, 0.18);
|
2026-06-21 22:29:00 +09:00
|
|
|
continue;
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
const overlap = this.radius + o.radius - d;
|
|
|
|
|
if (overlap > -2) {
|
|
|
|
|
const push = (overlap + 6) * (pairBond ? 0.08 : 0.17);
|
|
|
|
|
this.vx -= (dx / d) * push;
|
|
|
|
|
this.vy -= (dy / d) * push;
|
2026-06-27 19:22:38 +09:00
|
|
|
if (!pairBond && deterministicChance(this.world, "body-overlap-surprise", dt * 1.2, this, o)) this.surpriseTimer = Math.max(this.surpriseTimer, 0.12);
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
if (pairBond && d > 26 && d < 120) {
|
|
|
|
|
this.vx += (dx / d) * dt * 4.8;
|
|
|
|
|
this.vy += (dy / d) * dt * 4.8;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-26 19:04:32 +09:00
|
|
|
if (d < Math.max(60, selfRadius + otherRadius + 8)) {
|
2026-06-21 14:09:35 +09:00
|
|
|
const rel = this.relationTo(o.id);
|
|
|
|
|
const pdef = this.personalityProfile();
|
|
|
|
|
this.loneliness -= dt * 1.65 * this.trait.social;
|
|
|
|
|
this.affection += dt * 0.28 * this.trait.social;
|
|
|
|
|
this.adjustRelation(o, dt * 0.18 * pdef.relation * (rel.fear > 10 ? 0.35 : 1), -dt * 0.018, "nearby");
|
|
|
|
|
if ((rel.affinity || 0) >= FRIEND_AFFINITY_THRESHOLD) {
|
2026-06-23 18:11:42 +09:00
|
|
|
if (typeof applyNeedRelief === "function") applyNeedRelief(this, { social: -dt * 5 * this.trait.social, fulfill: -dt * 2 });
|
2026-06-21 14:09:35 +09:00
|
|
|
this.affection = clamp(this.affection + dt * 0.18, 0, 80);
|
|
|
|
|
if (this.world.relationNotice(this.id, o.id, "friend-calm", 55)) this.world.log(`${this.name}\u306f${o.name}\u3068\u3044\u308b\u3068\u5c11\u3057\u843d\u3061\u7740\u304f\u3002`, "relation", { participants: [o, this] });
|
|
|
|
|
}
|
|
|
|
|
if ((rel.affinity || 0) >= FRIEND_AFFINITY_THRESHOLD && this.world.relationNotice(this.id, o.id, "friend", 32)) {
|
|
|
|
|
this.world.log(`${this.name}\u306f\u3088\u304f${o.name}\u306e\u8fd1\u304f\u306b\u3044\u308b\u3002`, "relation", { participants: [o, this] });
|
|
|
|
|
}
|
2026-06-23 18:11:42 +09:00
|
|
|
if (this.type === "angry" && typeof applyNeedShock === "function") applyNeedShock(this, { social: dt * 1.5, safety: dt * 1 });
|
|
|
|
|
if (this.type === "teary" && o.type === "teary" && typeof applyNeedRelief === "function") applyNeedRelief(this, { social: -dt * 2 });
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fightingThis = this.fightTimer > 0 && (this.fightTargetId === o.id || (this.fightTargetIds || []).includes(o.id));
|
|
|
|
|
if (fightingThis && d < 36 && this.id < o.id) {
|
|
|
|
|
const pulse = Math.sin(this.world.time * 18 + this.age * 0.37) > 0.35;
|
|
|
|
|
if (pulse && this.world.time > this.nextHeadbutt) {
|
|
|
|
|
const nx = dx / d, ny = dy / d;
|
2026-06-27 19:22:38 +09:00
|
|
|
this.vx -= nx * deterministicRange(this.world, "headbutt-self-x", 24, 44, this, o);
|
|
|
|
|
this.vy -= ny * deterministicRange(this.world, "headbutt-self-y", 24, 44, this, o);
|
|
|
|
|
o.vx += nx * deterministicRange(this.world, "headbutt-other-x", 34, 64, this, o);
|
|
|
|
|
o.vy += ny * deterministicRange(this.world, "headbutt-other-y", 34, 64, this, o);
|
2026-06-23 18:11:42 +09:00
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(this, { safety: 8, health: 4 });
|
|
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(o, { safety: 12, health: 6 });
|
2026-06-21 16:26:12 +09:00
|
|
|
const damageToThis = o.outgoingDamage ? o.outgoingDamage(1.2) : 1.2;
|
|
|
|
|
const damageToOther = this.outgoingDamage ? this.outgoingDamage(1.6) : 1.6;
|
2026-06-21 22:29:00 +09:00
|
|
|
this.world.emit?.("fight:hit", { attacker: o, target: this, amount: damageToThis, cause: "\u55a7\u5629" });
|
|
|
|
|
this.world.emit?.("fight:hit", { attacker: this, target: o, amount: damageToOther, cause: "\u55a7\u5629" });
|
2026-06-21 16:26:12 +09:00
|
|
|
this.damage(damageToThis, "\u55a7\u5629");
|
|
|
|
|
o.damage(damageToOther, "\u55a7\u5629");
|
|
|
|
|
this.world.maybeDefeatFromFightDamage?.(this, o, damageToThis);
|
|
|
|
|
this.world.maybeDefeatFromFightDamage?.(o, this, damageToOther);
|
2026-06-21 14:09:35 +09:00
|
|
|
this.hurtTimer = Math.max(this.hurtTimer, 1.4);
|
|
|
|
|
o.hurtTimer = Math.max(o.hurtTimer, 1.8);
|
|
|
|
|
this.adjustRelation(o, -0.16, 0.12, "fight");
|
|
|
|
|
o.adjustRelation(this, -0.22, 0.28 * o.personalityProfile().fear, "fight");
|
2026-06-27 19:22:38 +09:00
|
|
|
if (deterministicChance(this.world, "headbutt-fall", 0.22, this, o) || o.energy < 24) {
|
2026-06-21 14:09:35 +09:00
|
|
|
o.fallTimer = Math.max(o.fallTimer, 0.58);
|
|
|
|
|
o.fallMax = Math.max(o.fallMax || 0.58, o.fallTimer);
|
|
|
|
|
o.fallDir = nx > 0 ? 1 : -1;
|
|
|
|
|
this.world.spawnFallEffect(o.x, o.y + o.radius * 0.45, 0.55);
|
|
|
|
|
}
|
2026-06-23 18:11:42 +09:00
|
|
|
if (o.energy < this.energy - 8) {
|
2026-06-21 16:26:12 +09:00
|
|
|
this.world.maybeDefeatFromFightDamage?.(o, this, Math.max(damageToOther, 2.4));
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
this.world.spawnHeadbuttEffect((this.x + o.x) / 2, (this.y + o.y) / 2 - 5);
|
2026-06-27 19:22:38 +09:00
|
|
|
this.nextHeadbutt = this.world.time + deterministicRange(this.world, "next-headbutt", 0.34, 0.55, this, o);
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const relA = this.relationTo(o.id);
|
|
|
|
|
const relB = o.relationTo(this.id);
|
|
|
|
|
const battleDrug = this.hasFightMochiEffect() || o.hasFightMochiEffect();
|
|
|
|
|
const loveDrug = this.hasLoveMochiEffect() || o.hasLoveMochiEffect();
|
|
|
|
|
const aggressiveThis = this.shouldApplyPersonalityBehavior("aggression", 1);
|
|
|
|
|
const aggressiveOther = o.shouldApplyPersonalityBehavior?.("aggression", 1);
|
|
|
|
|
const calmThis = this.shouldApplyPersonalityBehavior("aggression", -1);
|
|
|
|
|
const calmOther = o.shouldApplyPersonalityBehavior?.("aggression", -1);
|
2026-06-23 18:11:42 +09:00
|
|
|
const fightRisk = (this.stress > 46 || o.stress > 46 || (this.needs?.safety || 0) > 60 || (o.needs?.safety || 0) > 60 || this.type === "angry" || o.type === "angry" || closeCount > 3 || aggressiveThis || aggressiveOther || battleDrug || o.isZunchiSlave || this.isZunchiSlave);
|
2026-06-21 14:09:35 +09:00
|
|
|
const fearBrake = battleDrug ? clamp(1 - ((relA.fear || 0) + (relB.fear || 0)) / 220, 0.74, 1.15) : clamp(1 - ((relA.fear || 0) + (relB.fear || 0)) / 120, 0.34, 1);
|
|
|
|
|
const friendBrake = battleDrug ? clamp(1 - Math.max(relA.affinity || 0, relB.affinity || 0) / 180, 0.72, 1.12) : clamp(1 - Math.max(relA.affinity || 0, relB.affinity || 0) / 90, 0.48, 1);
|
|
|
|
|
const personalityRisk = this.personalityProfile().fight * o.personalityProfile().fight * (calmThis ? 0.62 : 1) * (calmOther ? 0.62 : 1);
|
|
|
|
|
const conflictBias = (battleDrug ? 2.9 : 1) * (o.isZunchiSlave ? 2.2 : 1) * (this.isZunchiSlave ? 1.15 : 1);
|
|
|
|
|
const birthPeace = (this.postBirthPeaceTimer || 0) > 0 || (o.postBirthPeaceTimer || 0) > 0;
|
|
|
|
|
const familyFightBlocked = this.world.areParentChild ? this.world.areParentChild(this, o) : false;
|
|
|
|
|
const pairFightBlocked = this.world.areCoParents ? this.world.areCoParents(this, o) : false;
|
2026-06-27 19:22:38 +09:00
|
|
|
const fightStartChance = dt * (battleDrug ? 0.23 : 0.085) * personalityRisk * fearBrake * friendBrake * conflictBias;
|
|
|
|
|
if (this.world.canFightPair?.(this, o) && d < 56 && !familyFightBlocked && !pairFightBlocked && !birthPeace && fightRisk && this.fightCooldown <= 0 && o.fightCooldown <= 0 && deterministicChance(this.world, "social-fight-start", fightStartChance, this, o)) {
|
2026-06-23 22:39:20 +09:00
|
|
|
this.conflictTargetId = o.id;
|
|
|
|
|
o.conflictTargetId = this.id;
|
2026-06-24 13:48:22 +09:00
|
|
|
const urge = 34 + (battleDrug ? 34 : 0) + (mixedZunchiSlave ? 10 : 0);
|
2026-06-23 22:39:20 +09:00
|
|
|
this.conflictUrge = Math.max(this.conflictUrge || 0, urge);
|
|
|
|
|
o.conflictUrge = Math.max(o.conflictUrge || 0, urge * 0.82);
|
2026-06-24 13:48:22 +09:00
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(this, { social: dt * urge * (battleDrug ? 0.32 : 0.18), safety: dt * (battleDrug ? 4 : 0.9) }, o);
|
|
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(o, { social: dt * urge * (battleDrug ? 0.24 : 0.14), safety: dt * (battleDrug ? 4 : 0.9) }, this);
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 22:29:00 +09:00
|
|
|
const compatibleBirthStatus = this.isZunchiSlave === o.isZunchiSlave;
|
2026-06-23 22:39:20 +09:00
|
|
|
if (compatibleBirthStatus && !this.sleepDisease && !o.sleepDisease && !this.fightDisease && !o.fightDisease && d < (loveDrug ? 52 : 42) && this.reproductionTimer <= 0 && o.reproductionTimer <= 0 && this.fightTimer <= 0 && o.fightTimer <= 0) {
|
2026-06-21 14:09:35 +09:00
|
|
|
const enough = loveDrug ? (this.hunger < 90 && o.hunger < 92 && this.energy > 18 && o.energy > 18) : (this.hunger < 55 && o.hunger < 60 && this.energy > 44 && o.energy > 44);
|
|
|
|
|
const lonelyBond = loveDrug ? (this.loneliness > 4 || o.loneliness > 4 || this.affection > 0 || o.affection > 0) : (this.loneliness > 24 || o.loneliness > 24);
|
2026-06-23 22:39:20 +09:00
|
|
|
if (enough && lonelyBond) {
|
|
|
|
|
if (typeof applyNeedShock === "function") {
|
|
|
|
|
applyNeedShock(this, { social: dt * (loveDrug ? 34 : 14) }, o);
|
|
|
|
|
applyNeedShock(o, { social: dt * (loveDrug ? 34 : 14) }, this);
|
|
|
|
|
}
|
|
|
|
|
if (loveDrug && typeof queueForcedTarinaiBehavior === "function") {
|
2026-06-24 13:48:22 +09:00
|
|
|
queueForcedTarinaiBehavior(this, "approach_mate", { target: o, source: "love_mochi", priority: 150, ttl: 10, causeText: "\u3078\u3053\u9905\u306e\u52b9\u679c" });
|
|
|
|
|
queueForcedTarinaiBehavior(o, "approach_mate", { target: this, source: "love_mochi", priority: 150, ttl: 10, causeText: "\u3078\u3053\u9905\u306e\u52b9\u679c" });
|
2026-06-23 22:39:20 +09:00
|
|
|
}
|
2026-06-29 16:21:58 +09:00
|
|
|
const selfBehaviorId = getTarinaiBehaviorId(this);
|
|
|
|
|
const otherBehaviorId = getTarinaiBehaviorId(o);
|
2026-06-25 14:51:58 +09:00
|
|
|
const mateIntent = selfBehaviorId === "approach_mate" || otherBehaviorId === "approach_mate" || loveDrug;
|
2026-06-27 19:22:38 +09:00
|
|
|
if (mateIntent && deterministicChance(this.world, "birth-ritual-start", dt * (loveDrug ? 0.78 : 0.30), this, o)) {
|
2026-06-23 22:39:20 +09:00
|
|
|
this.world.startBirthRitual(this, o);
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (closeCount > 8) {
|
2026-06-23 18:11:42 +09:00
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(this, { social: dt * 5, safety: dt * 3 * this.personalityProfile().fear });
|
2026-06-21 14:09:35 +09:00
|
|
|
this.energy -= dt * 0.25;
|
|
|
|
|
this.fearTimer = Math.max(this.fearTimer, 0.18);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-27 19:22:38 +09:00
|
|
|
if (this.loneliness < 12 && deterministicChance(this.world, "nearby-calm-log", dt * 0.002, this)) {
|
2026-06-21 14:09:35 +09:00
|
|
|
this.world.log(`${this.name}\u306f\u8ab0\u304b\u306e\u8fd1\u304f\u3067\u3001\u305f\u308a\u306a\u3044\u3053\u3068\u3092\u5c11\u3057\u5fd8\u308c\u305f\u3002`, null, { participants: [this] });
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
move(dt) {
|
|
|
|
|
if (this.freezeSleepDiseaseMotion(dt)) return;
|
|
|
|
|
let ax = 0, ay = 0;
|
|
|
|
|
const itemSpeedMul = this.itemSpeedMultiplier ? this.itemSpeedMultiplier() : 1;
|
|
|
|
|
const baseSpeed = 46.5 * 1.5 * this.trait.speed * itemSpeedMul * (this.zunchiDisease ? 0.5 : 1) * (this.energy < 20 ? 0.42 : 1) * (this.fallTimer > 0 ? 0.35 : 1);
|
|
|
|
|
|
2026-06-22 02:03:19 +09:00
|
|
|
if (this.state === "sunbath") {
|
|
|
|
|
this.target = null;
|
2026-06-24 18:49:17 +09:00
|
|
|
const pad = Math.max(28, CONFIG.worldPadding || 30);
|
|
|
|
|
const safeW = Math.max(pad * 2 + 1, Number(this.world?.w || 1000) || 1000);
|
|
|
|
|
const safeH = Math.max(pad * 2 + 1, Number(this.world?.h || 720) || 720);
|
2026-06-26 19:04:32 +09:00
|
|
|
this.x = clamp(Number.isFinite(this.x) ? this.x : (Number.isFinite(this.sunbathAnchorX) ? this.sunbathAnchorX : pad), pad, safeW - pad);
|
|
|
|
|
this.y = clamp(Number.isFinite(this.y) ? this.y : (Number.isFinite(this.sunbathAnchorY) ? this.sunbathAnchorY : pad), pad, safeH - pad);
|
|
|
|
|
this.sunbathAnchorX = this.x;
|
|
|
|
|
this.sunbathAnchorY = this.y;
|
2026-06-24 21:20:53 +09:00
|
|
|
this._lastSunbathDrawX = this.x;
|
|
|
|
|
this._lastSunbathDrawY = this.y;
|
|
|
|
|
this._lastValidX = this.x;
|
|
|
|
|
this._lastValidY = this.y;
|
2026-06-22 02:03:19 +09:00
|
|
|
return;
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
if (this.state === "sleep") {
|
|
|
|
|
const bedComfort = this.isSleepFurniture(this.target) ? this.world.bedComfort(this.target) * (this.target?.type === "nest_box" ? 1.55 : 1) : 0.72;
|
|
|
|
|
const bedCrowd = this.isSleepFurniture(this.target) ? this.world.bedOccupancy(this.target) : 0;
|
|
|
|
|
const crowdedSleep = bedCrowd > 5;
|
2026-06-26 12:46:32 +09:00
|
|
|
this.energy = clamp(this.energy + dt * (this.target ? 1.8 + bedComfort * 1.35 : 2.1), 0, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(this) : (Number(this.maxEnergy) || 100));
|
2026-06-24 13:48:22 +09:00
|
|
|
if (crowdedSleep && typeof applyNeedShock === "function") applyNeedShock(this, { safety: dt * Math.max(1, bedCrowd - 5) });
|
2026-06-26 12:46:32 +09:00
|
|
|
const sleepHungerScale = this.metabolicHungerMultiplier ? this.metabolicHungerMultiplier() : (typeof tarinaiMetabolicHungerScale === "function" ? tarinaiMetabolicHungerScale(this) : 1);
|
|
|
|
|
this.hunger = clamp(this.hunger + dt * 0.018 * sleepHungerScale, 0, 115);
|
2026-06-21 14:09:35 +09:00
|
|
|
if (this.target && !this.target.dead && this.target.type === "nest_box") {
|
|
|
|
|
this.updateNestBoxPresence(dt);
|
|
|
|
|
this.vx = 0;
|
|
|
|
|
this.vy = 0;
|
2026-06-27 19:22:38 +09:00
|
|
|
this.impulseVx = 0;
|
|
|
|
|
this.impulseVy = 0;
|
2026-06-21 14:09:35 +09:00
|
|
|
return;
|
|
|
|
|
}
|
2026-06-27 19:22:38 +09:00
|
|
|
|
|
|
|
|
const marker = window.TarinaiMovementUpdateStep;
|
|
|
|
|
const externalActive = marker?.markSleepExternalMotion?.(this, dt, "sleep-full-update") || marker?.sleepExternalMotionActive?.(this);
|
2026-06-21 14:09:35 +09:00
|
|
|
if (this.target && !this.target.dead && this.isSleepFurniture(this.target)) {
|
|
|
|
|
const spot = this.sleepSpotFor(this.target);
|
|
|
|
|
const dx = spot.x - this.x;
|
|
|
|
|
const dy = spot.y - this.y;
|
|
|
|
|
const d = Math.hypot(dx, dy);
|
2026-06-28 13:40:41 +09:00
|
|
|
if (externalActive && d > 8.5 && !this.sleepDisease && marker?.wakeSleepingFromExternalMotion?.(this, "\u52d5\u304b\u3055\u308c\u3066\u76ee\u304c\u899a\u3081\u305f")) return;
|
2026-06-21 14:09:35 +09:00
|
|
|
if (d > 2.2) {
|
2026-06-27 19:22:38 +09:00
|
|
|
const restore = externalActive ? (2.0 + bedComfort * 2.0) : (12 + bedComfort * 14);
|
|
|
|
|
const maxRestore = externalActive ? Math.min(d, 3.2) : d;
|
|
|
|
|
const step = Math.min(maxRestore, dt * restore);
|
|
|
|
|
const sx = dx / d * step;
|
|
|
|
|
const sy = dy / d * step;
|
|
|
|
|
if (this.world?.moveTarinaiWithCollision) this.world.moveTarinaiWithCollision(this, sx, sy, { dt, reason: externalActive ? "tarinai-sleep-restore-weak" : "tarinai-sleep-move" });
|
|
|
|
|
else { this.x += sx; this.y += sy; }
|
|
|
|
|
} else if (!externalActive && Math.hypot((this.vx || 0) + (this.impulseVx || 0), (this.vy || 0) + (this.impulseVy || 0)) < 0.35) {
|
2026-06-21 14:09:35 +09:00
|
|
|
this.x = spot.x;
|
|
|
|
|
this.y = spot.y;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-27 19:22:38 +09:00
|
|
|
|
|
|
|
|
const impulseVx = Number.isFinite(this.impulseVx) ? this.impulseVx : 0;
|
|
|
|
|
const impulseVy = Number.isFinite(this.impulseVy) ? this.impulseVy : 0;
|
|
|
|
|
const moveDx = ((Number.isFinite(this.vx) ? this.vx : 0) + impulseVx) * dt;
|
|
|
|
|
const moveDy = ((Number.isFinite(this.vy) ? this.vy : 0) + impulseVy) * dt;
|
|
|
|
|
if (Math.hypot(moveDx, moveDy) > 0.001) {
|
|
|
|
|
if (this.world?.moveTarinaiWithCollision) this.world.moveTarinaiWithCollision(this, moveDx, moveDy, { dt, reason: "tarinai-sleep-physical-full", maxChecks: 18, maxStep: 10 });
|
|
|
|
|
else { this.x += moveDx; this.y += moveDy; }
|
|
|
|
|
}
|
|
|
|
|
if (this.world?.resolveSolidObstacleCollision) {
|
|
|
|
|
const beforeContactX = Number(this.x || 0) || 0;
|
|
|
|
|
const beforeContactY = Number(this.y || 0) || 0;
|
|
|
|
|
const pushed = this.world.resolveSolidObstacleCollision(this, { maxPasses: 2, searchRadius: Math.max(72, (Number(this.radius) || 22) + 120), maxChecks: 18, slop: 0.28, reason: "tarinai-sleep-full-contact" });
|
|
|
|
|
if (pushed) {
|
|
|
|
|
this.lastSolidObstacleCollisionAt = this.world.time || 0;
|
|
|
|
|
marker?.markSleepExternalMotion?.(this, dt, "sleep-full-contact");
|
|
|
|
|
const cdx = (Number(this.x || 0) || 0) - beforeContactX;
|
|
|
|
|
const cdy = (Number(this.y || 0) || 0) - beforeContactY;
|
2026-06-28 13:40:41 +09:00
|
|
|
if (Math.hypot(cdx, cdy) > 1.8 && !this.sleepDisease && marker?.wakeSleepingFromExternalMotion?.(this, "\u3076\u3064\u304b\u3063\u3066\u76ee\u304c\u899a\u3081\u305f")) return;
|
2026-06-27 19:22:38 +09:00
|
|
|
if (Math.hypot(cdx, cdy) > 0.05) {
|
|
|
|
|
this.vx = clamp((Number(this.vx) || 0) + cdx / Math.max(0.016, dt) * 0.085, -170, 170);
|
|
|
|
|
this.vy = clamp((Number(this.vy) || 0) + cdy / Math.max(0.016, dt) * 0.085, -170, 170);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
this.updateNestBoxPresence(dt);
|
2026-06-27 19:22:38 +09:00
|
|
|
const bodyDrag = externalActive ? 0.86 : 0.55;
|
|
|
|
|
this.vx *= Math.pow(bodyDrag, dt * 60);
|
|
|
|
|
this.vy *= Math.pow(bodyDrag, dt * 60);
|
|
|
|
|
const impulseDrag = Math.pow(0.58, dt * 3.2);
|
|
|
|
|
this.impulseVx = Math.abs(impulseVx) < 0.35 ? 0 : impulseVx * impulseDrag;
|
|
|
|
|
this.impulseVy = Math.abs(impulseVy) < 0.35 ? 0 : impulseVy * impulseDrag;
|
2026-06-21 14:09:35 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.birthRitualTimer > 0.04 || this.state === "birth_ritual") {
|
2026-06-27 19:22:38 +09:00
|
|
|
const pendingImpulse = Math.hypot(Number.isFinite(this.impulseVx) ? this.impulseVx : 0, Number.isFinite(this.impulseVy) ? this.impulseVy : 0);
|
|
|
|
|
const canceledByForce = this.world?.cancelBirthRitualOnForce?.(this, pendingImpulse, {
|
2026-06-28 13:40:41 +09:00
|
|
|
reason: "\u5f37\u3044\u885d\u6483\u3067\u7e41\u6b96\u304c\u4e2d\u65ad\u3055\u308c\u305f",
|
2026-06-27 19:22:38 +09:00
|
|
|
target: this.target || null,
|
|
|
|
|
fear: 1.0,
|
|
|
|
|
cause: "stored_impulse",
|
|
|
|
|
panic: true,
|
|
|
|
|
silentLog: true,
|
|
|
|
|
});
|
|
|
|
|
if (!canceledByForce) {
|
|
|
|
|
const partner = this.world.liveTarinaiById?.(this.birthPartnerId);
|
|
|
|
|
if (partner) {
|
|
|
|
|
const midX = (this.x + partner.x) / 2;
|
|
|
|
|
const midY = (this.y + partner.y) / 2;
|
|
|
|
|
const pdx = partner.x - this.x;
|
|
|
|
|
const pdy = partner.y - this.y;
|
|
|
|
|
const plen = Math.max(0.001, Math.hypot(pdx, pdy));
|
|
|
|
|
const nx = -pdy / plen;
|
|
|
|
|
const ny = pdx / plen;
|
|
|
|
|
const desiredGap = this.radius * 0.36;
|
|
|
|
|
const side = this.birthRitualRole || 1;
|
|
|
|
|
const tx = midX + nx * desiredGap * side;
|
|
|
|
|
const ty = midY + ny * desiredGap * side;
|
|
|
|
|
this.vx += (tx - this.x) * dt * 3.8;
|
|
|
|
|
this.vy += (ty - this.y) * dt * 3.8;
|
|
|
|
|
}
|
|
|
|
|
this.vx *= Math.pow(0.48, dt * 60);
|
|
|
|
|
this.vy *= Math.pow(0.48, dt * 60);
|
|
|
|
|
this.x += Math.sin(this.world.time * 26 + this.age) * 0.20;
|
|
|
|
|
return;
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.intimidateTimer > 0.04 || this.state === "intimidate" || this.state === "ant_intimidate") {
|
|
|
|
|
this.vx *= Math.pow(0.22, dt * 60);
|
|
|
|
|
this.vy *= Math.pow(0.22, dt * 60);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.eatTimer > 0.04 || this.state === "eat") {
|
|
|
|
|
this.vx = 0;
|
|
|
|
|
this.vy = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.state === "panic") {
|
|
|
|
|
const targetDist = this.target && !this.target.dead ? distXY(this.x, this.y, this.target.x, this.target.y) : Infinity;
|
|
|
|
|
const speed = Math.hypot(this.vx, this.vy);
|
|
|
|
|
this.panicStuckTimer = speed < 8 ? (this.panicStuckTimer || 0) + dt : 0;
|
|
|
|
|
if (!this.target || this.target.dead || targetDist < 28 || this.panicStuckTimer > 0.75) {
|
|
|
|
|
this.target = this.panicDestination(null, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.target && !this.target.dead) {
|
2026-06-29 21:02:04 +09:00
|
|
|
const baseMoveTarget = this.state === "seek_bed" && this.isSleepFurniture(this.target) ? this.sleepSpotFor(this.target) : this.target;
|
|
|
|
|
const routeStates = new Set(["seek_food", "seek_water", "seek_bed", "seek_material", "build", "follow_parent", "seek_friend", "seek_enemy", "fight", "play_ball", "ant_attack"]);
|
|
|
|
|
const routeTargetItem = this.target?.hostItem || this.target;
|
|
|
|
|
const waypoint = routeStates.has(this.state) && this.world?.findTarinaiPathWaypoint
|
|
|
|
|
? this.world.findTarinaiPathWaypoint(this, baseMoveTarget, { targetItem: routeTargetItem, padding: Math.max(18, (this.radius || 20) * 0.82), state: this.state })
|
|
|
|
|
: null;
|
|
|
|
|
const moveTarget = waypoint || baseMoveTarget;
|
2026-06-21 14:09:35 +09:00
|
|
|
const dx = moveTarget.x - this.x;
|
|
|
|
|
const dy = moveTarget.y - this.y;
|
|
|
|
|
const d = Math.hypot(dx, dy) || 1;
|
2026-06-23 22:39:20 +09:00
|
|
|
if (this.state === "wander" && this.target?.detour && d < Math.max(22, (this.radius || 20) * 0.9)) {
|
|
|
|
|
if (typeof applyNeedSatisfaction === "function") applyNeedSatisfaction(this, { fulfill: 6 }, "wander");
|
2026-06-29 16:21:58 +09:00
|
|
|
clearTarinaiBehavior(this, { reason: this.thought });
|
2026-06-25 14:51:58 +09:00
|
|
|
this.behaviorLockTimer = 0;
|
2026-06-24 13:48:22 +09:00
|
|
|
this.goIdle?.("\u5c11\u3057\u6b69\u3044\u305f");
|
2026-06-23 22:39:20 +09:00
|
|
|
return;
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
if (this.state === "seek_bed" && this.isSleepFurniture(this.target)) {
|
|
|
|
|
const bedDx = this.target.x - this.x;
|
|
|
|
|
const bedDy = this.target.y - this.y;
|
|
|
|
|
const bedDist = Math.hypot(bedDx, bedDy);
|
|
|
|
|
const reachedSleepFurniture = this.target.type === "nest_box"
|
|
|
|
|
? (this.insideNestBoxId === this.target.id || d <= Math.max(this.radius + 34, this.target.r * 1.08) || bedDist <= Math.max(this.target.r * 1.05, 62))
|
|
|
|
|
: (d <= Math.max(this.radius + 8, 26) || bedDist <= Math.max(this.target.r * 0.58, 20));
|
|
|
|
|
if (reachedSleepFurniture) {
|
2026-06-25 14:51:58 +09:00
|
|
|
const sleepTarget = this.target;
|
|
|
|
|
const isEasyBed = sleepTarget?.type === "grass_bed";
|
|
|
|
|
sleepTarget?.use?.(this, this.world, isEasyBed ? { purpose: "sleep" } : undefined);
|
|
|
|
|
this.startSleeping?.(sleepTarget, sleepTarget?.type === "nest_box" ? "\u5de3\u7bb1\u306e\u4e2d\u3067\u4f11\u3093\u3067\u3044\u308b" : (isEasyBed ? "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9\u3067\u5bdd\u3066\u3044\u308b" : "\u30d9\u30c3\u30c9\u3067\u4f11\u3093\u3067\u3044\u308b"));
|
|
|
|
|
if (sleepTarget?.type === "nest_box") this.enterNestBox(sleepTarget, dt);
|
2026-06-21 14:09:35 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let sign = (this.state === "panic" || this.state === "cursor_enemy") ? -1 : 1;
|
|
|
|
|
if (this.target?.detour) sign = 1;
|
|
|
|
|
let strength = 1;
|
|
|
|
|
if (this.state === "follow_parent") {
|
|
|
|
|
strength = d < 42 ? 0.18 : 1.22;
|
|
|
|
|
sign = 1;
|
|
|
|
|
} else if (this.state === "cursor_friend") {
|
|
|
|
|
strength = d < 30 ? 0.12 : 0.95;
|
|
|
|
|
sign = 1;
|
|
|
|
|
} else if (this.state === "cursor_enemy") {
|
|
|
|
|
strength = d < 150 ? 1.35 : 0.35;
|
|
|
|
|
sign = -1;
|
|
|
|
|
} else if (this.state === "play_ball") {
|
|
|
|
|
strength = d < this.radius + 18 ? 1.75 : 1.28;
|
|
|
|
|
sign = 1;
|
|
|
|
|
} else if (this.state === "fight") {
|
|
|
|
|
const headbutt = Math.sin(this.world.time * 18 + this.age * 0.37);
|
|
|
|
|
if (d < 18) {
|
|
|
|
|
strength = 1.05;
|
|
|
|
|
sign = -1;
|
|
|
|
|
} else {
|
|
|
|
|
strength = headbutt > -0.15 ? 2.65 : 0.65;
|
|
|
|
|
sign = headbutt > -0.15 ? 1 : -1;
|
|
|
|
|
}
|
|
|
|
|
} else if (this.state === "ant_attack") {
|
|
|
|
|
strength = d < this.radius + ((this.target && this.target.r) || 4) + 6 ? 0.18 : 1.32;
|
|
|
|
|
sign = 1;
|
|
|
|
|
if (d < this.radius + ((this.target && this.target.r) || 4) + 10) {
|
|
|
|
|
const aggression = Math.max(0, this.currentPersonality?.aggression || 0);
|
|
|
|
|
if ((this.world.time || 0) >= (this.nextAntAttackAt || 0)) {
|
|
|
|
|
this.nextAntAttackAt = (this.world.time || 0) + 0.34;
|
|
|
|
|
if (Number.isFinite(this.target.hp)) this.target.hp = Math.max(0, this.target.hp - (this.outgoingDamage ? this.outgoingDamage(4.6 + aggression * 3.4) : (4.6 + aggression * 3.4)));
|
|
|
|
|
this.fearTimer = Math.max(this.fearTimer, 0.12);
|
|
|
|
|
this.thought = "\u30a2\u30ea\u3092\u653b\u6483\u3057\u3066\u3044\u308b";
|
2026-06-27 19:22:38 +09:00
|
|
|
if (deterministicChance(this.world, "ant-attack-bubble", 0.55, this, this.target)) this.bubble("!", 0.8, "rgba(145,68,52,0.76)");
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ax += (dx / d) * baseSpeed * sign * strength;
|
|
|
|
|
ay += (dy / d) * baseSpeed * sign * strength;
|
|
|
|
|
} else if (this.state === "panic") {
|
2026-06-27 19:22:38 +09:00
|
|
|
this.wanderAngle += deterministicRange(this.world, "panic-wander", -3.8, 3.8, this) * dt;
|
2026-06-21 14:09:35 +09:00
|
|
|
ax += Math.cos(this.wanderAngle) * baseSpeed * 1.5;
|
|
|
|
|
ay += Math.sin(this.wanderAngle) * baseSpeed * 1.5;
|
|
|
|
|
} else if (this.state === "zunchi_sick") {
|
2026-06-27 19:22:38 +09:00
|
|
|
this.wanderAngle += deterministicRange(this.world, "zunchi-sick-wander", -2.4, 2.4, this) * dt + Math.sin(this.world.time * 1.7 + this.age) * dt * 0.8;
|
2026-06-21 14:09:35 +09:00
|
|
|
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.72;
|
|
|
|
|
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.72;
|
|
|
|
|
} else if (this.state === "fight_sick") {
|
2026-06-27 19:22:38 +09:00
|
|
|
this.wanderAngle += deterministicRange(this.world, "fight-sick-wander", -4.2, 4.2, this) * dt + Math.sin(this.world.time * 3.2 + this.age) * dt * 1.6;
|
2026-06-21 14:09:35 +09:00
|
|
|
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.88;
|
|
|
|
|
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.88;
|
|
|
|
|
} else {
|
|
|
|
|
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.34;
|
|
|
|
|
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.34;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.vx += ax * dt;
|
|
|
|
|
this.vy += ay * dt;
|
2026-06-25 22:30:35 +09:00
|
|
|
const iceGround = (this.world?.groundType || "soil") === "ice";
|
|
|
|
|
const moveFriction = iceGround ? 0.965 : 0.84;
|
|
|
|
|
this.vx *= Math.pow(moveFriction, dt * 8);
|
|
|
|
|
this.vy *= Math.pow(moveFriction, dt * 8);
|
2026-06-21 14:09:35 +09:00
|
|
|
|
2026-06-25 22:30:35 +09:00
|
|
|
const maxV = (this.state === "panic" ? 116 : (this.state === "zunchi_sick" ? 54 : (this.state === "fight_sick" ? 68 : 76))) * Math.max(1, itemSpeedMul || 1) * (this.geneticStatRatio ? this.geneticStatRatio("speed") : 1) * (iceGround ? 1.35 : 1);
|
2026-06-21 14:09:35 +09:00
|
|
|
const v = Math.hypot(this.vx, this.vy);
|
|
|
|
|
if (v > maxV) {
|
|
|
|
|
this.vx = this.vx / v * maxV;
|
|
|
|
|
this.vy = this.vy / v * maxV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const impulseVx = Number.isFinite(this.impulseVx) ? this.impulseVx : 0;
|
|
|
|
|
const impulseVy = Number.isFinite(this.impulseVy) ? this.impulseVy : 0;
|
2026-06-27 19:22:38 +09:00
|
|
|
const moveDx = (this.vx + impulseVx) * dt;
|
|
|
|
|
const moveDy = (this.vy + impulseVy) * dt;
|
|
|
|
|
if (this.world?.moveTarinaiWithCollision) {
|
|
|
|
|
const motionOnlyPass = Boolean(this._motionOnlyPhysicsPass);
|
|
|
|
|
this.world.moveTarinaiWithCollision(this, moveDx, moveDy, motionOnlyPass
|
|
|
|
|
? { dt, reason: "tarinai-move-smooth", light: true, maxChecks: this.state === "panic" ? 18 : 14, maxStep: this.state === "panic" ? 12 : 14 }
|
|
|
|
|
: { dt, reason: "tarinai-move" });
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this.x += moveDx;
|
|
|
|
|
this.y += moveDy;
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
const impulseDrag = Math.pow(0.58, dt * 3.2);
|
|
|
|
|
this.impulseVx = Math.abs(impulseVx) < 0.35 ? 0 : impulseVx * impulseDrag;
|
|
|
|
|
this.impulseVy = Math.abs(impulseVy) < 0.35 ? 0 : impulseVy * impulseDrag;
|
|
|
|
|
|
|
|
|
|
if (this.entryTimer > 0) {
|
|
|
|
|
this.entryTimer = Math.max(0, this.entryTimer - dt);
|
|
|
|
|
if (this.x >= CONFIG.worldPadding && this.y >= CONFIG.worldPadding && this.x <= this.world.w - CONFIG.worldPadding && this.y <= this.world.h - CONFIG.worldPadding) {
|
|
|
|
|
this.entryTimer = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const p = this.entryTimer > 0 ? -this.radius * 2.4 : CONFIG.worldPadding;
|
|
|
|
|
const maxX = this.entryTimer > 0 ? this.world.w + this.radius * 2.4 : this.world.w - CONFIG.worldPadding;
|
|
|
|
|
const maxY = this.entryTimer > 0 ? this.world.h + this.radius * 2.4 : this.world.h - CONFIG.worldPadding;
|
2026-06-27 19:22:38 +09:00
|
|
|
if (this.x < p) { this.x = p; this.vx = Math.abs(this.vx) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-left", -0.8, 0.8, this); this.surpriseTimer = 0.15; }
|
|
|
|
|
if (this.y < p) { this.y = p; this.vy = Math.abs(this.vy) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-top", 0.3, 1.8, this); this.surpriseTimer = 0.15; }
|
|
|
|
|
if (this.x > maxX) { this.x = maxX; this.vx = -Math.abs(this.vx) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-right", 2.5, 3.8, this); this.surpriseTimer = 0.15; }
|
|
|
|
|
if (this.y > maxY) { this.y = maxY; this.vy = -Math.abs(this.vy) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-bottom", -2.2, -0.7, this); this.surpriseTimer = 0.15; }
|
2026-06-21 14:09:35 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
checkLife(dt = 0.016) {
|
|
|
|
|
let reason = "";
|
|
|
|
|
if (this.energy <= 0) {
|
|
|
|
|
reason = this.dominantDeathCause("", { lowHealth: true }) || "\u4e8b\u6545";
|
|
|
|
|
}
|
|
|
|
|
else if (this.hunger >= 108) reason = "\u98e2\u9913";
|
|
|
|
|
else if (this.age >= this.lifeSpan) reason = "\u5929\u5bff\u3092\u5168\u3046\u3057\u305f";
|
|
|
|
|
else if (this.stress >= 124) {
|
|
|
|
|
reason = this.dominantDeathCause("\u30b9\u30c8\u30ec\u30b9\u904e\u591a", { stressDeath: true }) || "\u30b9\u30c8\u30ec\u30b9\u904e\u591a";
|
|
|
|
|
}
|
|
|
|
|
if (reason) this.die(reason);
|
|
|
|
|
},
|
|
|
|
|
|
2026-06-28 13:40:41 +09:00
|
|
|
die(reason, opts = {}) {
|
2026-06-21 14:09:35 +09:00
|
|
|
if (this.dead) return;
|
2026-06-28 13:40:41 +09:00
|
|
|
const finalReason = this.normalizeDeathReason ? this.normalizeDeathReason(reason, opts) : (reason || "\u4e8b\u6545");
|
2026-06-21 14:09:35 +09:00
|
|
|
this.dead = true;
|
|
|
|
|
this.deathReason = finalReason;
|
|
|
|
|
this.world.deadCount += 1;
|
2026-06-28 13:40:41 +09:00
|
|
|
this.world.lastDeathAt = this.world.time || 0;
|
|
|
|
|
if (!Array.isArray(this.world.recentDeathTimes)) this.world.recentDeathTimes = [];
|
|
|
|
|
this.world.recentDeathTimes.push(this.world.lastDeathAt);
|
|
|
|
|
if (this.world.recentDeathTimes.length > 24) this.world.recentDeathTimes = this.world.recentDeathTimes.slice(-24);
|
2026-06-26 19:04:32 +09:00
|
|
|
for (const item of this.world.items || []) {
|
|
|
|
|
if (!item || item.dead || !item.isStructure || item.type !== "plushie" || item.ownerId !== this.id) continue;
|
|
|
|
|
item.dead = true;
|
|
|
|
|
item.hp = 0;
|
|
|
|
|
item.amount = 0;
|
|
|
|
|
item.carriedById = "";
|
|
|
|
|
item.onHead = false;
|
|
|
|
|
}
|
|
|
|
|
this.world.drawListDirty = true;
|
|
|
|
|
this.world.markItemBucketsDirty?.("tarinai-death-plushie-cleanup");
|
|
|
|
|
this.world.markSpatialDirty?.("tarinai-death-plushie-cleanup");
|
2026-06-24 13:48:22 +09:00
|
|
|
this.world.addItem?.(new Item("trace", this.x, this.y), "tarinai-death-trace") || this.world.items.push(new Item("trace", this.x, this.y));
|
|
|
|
|
this.world.addItem?.(new Item("splat", this.x, this.y), "tarinai-death-splat") || this.world.items.push(new Item("splat", this.x, this.y));
|
2026-06-27 19:22:38 +09:00
|
|
|
if (deterministicChance(this.world, "death-grass-spawn", 0.55, this, finalReason)) {
|
2026-06-21 14:09:35 +09:00
|
|
|
const spot = this.world.findGrassPlantingSpot(this.x, this.y, { allowOriginal: false, minRadius: 26, maxRadius: 76 });
|
2026-06-24 21:20:53 +09:00
|
|
|
if (spot) {
|
|
|
|
|
const deathGrass = new Item("grass", spot.x, spot.y);
|
|
|
|
|
if (this.world.addItem) this.world.addItem(deathGrass, "tarinai-death-grass");
|
|
|
|
|
else this.world.items.push(deathGrass);
|
|
|
|
|
}
|
2026-06-21 14:09:35 +09:00
|
|
|
}
|
|
|
|
|
this.world.markDead(this, finalReason);
|
|
|
|
|
this.releasedLiveToken = this.liveToken || 0;
|
|
|
|
|
this.world.releaseTarinaiLiveId?.(this);
|
|
|
|
|
audio.death();
|
2026-06-21 22:29:00 +09:00
|
|
|
this.world.emit?.("tarinai:died", { tarinai: this, reason: finalReason });
|
2026-06-21 14:09:35 +09:00
|
|
|
this.world.log(`${this.name}\u306f\u9759\u304b\u306a\u75d5\u8de1\u3092\u6b8b\u3057\u305f\u3002\u6b7b\u56e0: ${finalReason}`, "death", { participants: [this] });
|
|
|
|
|
if (this.world.selected === this) this.world.selected = null;
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
})(typeof window !== "undefined" ? window : globalThis);
|