This commit is contained in:
33333-33333 2026-06-30 13:40:36 +09:00
commit ad2cc76429
34 changed files with 616 additions and 365 deletions

View file

@ -11,6 +11,93 @@ function mateTargetFor(t, world, maxDist = 520) {
return world.nearestOther?.(t, maxDist, ok) || null;
}
function socialFightMateWeights(t, other, world, options = {}) {
if (!t || !other || !world || t === other || t.dead || other.dead) return { fightWeight: 0, mateWeight: 0, fightEligible: false, mateEligible: false, fightProbability: 0 };
const d = Number.isFinite(Number(options.distance)) ? Number(options.distance) : dist(t, other);
const fightMochi = (t.fightMochiTimer || 0) > 0.04 || (other.fightMochiTimer || 0) > 0.04 || t.hasFightMochiEffect?.() || other.hasFightMochiEffect?.();
const loveMochi = (t.loveMochiTimer || 0) > 0.04 || (other.loveMochiTimer || 0) > 0.04 || t.hasLoveMochiEffect?.() || other.hasLoveMochiEffect?.();
const aProf = t.personalityProfile?.() || {};
const bProf = other.personalityProfile?.() || {};
const aCur = t.currentPersonality || {};
const bCur = other.currentPersonality || {};
const avg = (a, b, fallback = 0) => ((Number.isFinite(Number(a)) ? Number(a) : fallback) + (Number.isFinite(Number(b)) ? Number(b) : fallback)) * 0.5;
const aggression = avg(aCur.aggression ?? aProf.fight, bCur.aggression ?? bProf.fight, 0);
const openness = avg(aCur.openness ?? aProf.play, bCur.openness ?? bProf.play, 0);
const sociability = avg(aCur.sociability ?? aProf.social, bCur.sociability ?? bProf.social, 0);
const fearTrait = avg(aProf.fear, bProf.fear, 1);
const relationA = typeof t.relationTo === "function" ? t.relationTo(other.id) : {};
const relationB = typeof other.relationTo === "function" ? other.relationTo(t.id) : {};
const affinity = Math.max(Number(relationA.affinity || 0) || 0, Number(relationB.affinity || 0) || 0);
const fear = Math.max(Number(relationA.fear || 0) || 0, Number(relationB.fear || 0) || 0);
const postBirthPeace = (t.postBirthPeaceTimer || 0) > 0.04 || (other.postBirthPeaceTimer || 0) > 0.04;
const postConflictPeace = (t.postConflictPeaceTimer || 0) > 0.04 || (other.postConflictPeaceTimer || 0) > 0.04;
const familyFightBlocked = world.areParentChild?.(t, other) || world.areCoParents?.(t, other);
const fightEligible = Boolean(world.canFightPair?.(t, other))
&& !familyFightBlocked
&& !postBirthPeace
&& !postConflictPeace
&& (t.fightCooldown || 0) <= 0.04
&& (other.fightCooldown || 0) <= 0.04
&& (t.fightTimer || 0) <= 0.04
&& (other.fightTimer || 0) <= 0.04
&& (t.birthRitualTimer || 0) <= 0.04
&& (other.birthRitualTimer || 0) <= 0.04
&& (world.fightPairCooldownRemaining?.(t, other) || 0) <= 0.04
&& d <= Math.max(Number(options.fightRange || 58) || 58, (t.radius || 20) + (other.radius || 20) + 18);
const sameStatus = !!t.isZunchiSlave === !!other.isZunchiSlave;
const mateWindow = Number.isFinite(Number(options.mateTimerWindow)) ? Number(options.mateTimerWindow) : 10;
const requireBirthReady = options.requireBirthReady !== false;
const timerOk = requireBirthReady ? ((t.reproductionTimer || 0) <= 0.04 && (other.reproductionTimer || 0) <= 0.04) : ((t.reproductionTimer || 0) <= mateWindow && (other.reproductionTimer || 0) <= mateWindow);
const mateDistanceOk = d <= Math.max(loveMochi ? 54 : 44, (t.radius || 20) + (other.radius || 20) + 14);
const enough = loveMochi ? ((t.hunger || 0) < 90 && (other.hunger || 0) < 92 && (t.energy || 0) > 18 && (other.energy || 0) > 18) : ((t.hunger || 0) < 55 && (other.hunger || 0) < 60 && (t.energy || 0) > 44 && (other.energy || 0) > 44);
const lonelyBond = loveMochi ? ((t.loneliness || 0) > 4 || (other.loneliness || 0) > 4 || (t.affection || 0) > 0 || (other.affection || 0) > 0) : ((t.loneliness || 0) > 24 || (other.loneliness || 0) > 24);
const mateEligible = sameStatus
&& timerOk
&& mateDistanceOk
&& !world.areParentChild?.(t, other)
&& !t.sleepDisease && !other.sleepDisease && !t.fightDisease && !other.fightDisease
&& (t.fightTimer || 0) <= 0.04 && (other.fightTimer || 0) <= 0.04
&& (t.birthRitualTimer || 0) <= 0.04 && (other.birthRitualTimer || 0) <= 0.04
&& (!requireBirthReady || (enough && lonelyBond));
let fightWeight = fightEligible ? 4 : 0;
let mateWeight = mateEligible ? 6 : 0;
if (fightWeight > 0) {
const stressPressure = clamp((((t.stress || 0) + (other.stress || 0)) * 0.5 - 36) / 72, 0, 1.8);
const safetyPressure = clamp((((t.needs?.safety || 0) + (other.needs?.safety || 0)) * 0.5 - 36) / 72, 0, 1.4);
fightWeight *= clamp(1 + aggression * 0.55, 0.25, 2.8);
fightWeight *= 1 + stressPressure * 0.58 + safetyPressure * 0.24;
if (t.type === "angry" || other.type === "angry") fightWeight *= 1.42;
if (t.isZunchiSlave || other.isZunchiSlave) fightWeight *= 1.22;
if (fightMochi) fightWeight *= 3.4;
if (loveMochi) fightWeight *= 0.58;
fightWeight *= clamp(1 - affinity / 150, 0.34, 1.08);
fightWeight *= clamp(1 - fear / 170, fightMochi ? 0.72 : 0.42, 1.05);
fightWeight *= clamp(1.14 - Math.max(0, fearTrait - 1) * 0.16, 0.62, 1.22);
if (options.mode === "conflict") fightWeight *= 1.35;
}
if (mateWeight > 0) {
const stressPressure = clamp((((t.stress || 0) + (other.stress || 0)) * 0.5 - 42) / 84, 0, 1.4);
mateWeight *= clamp(1 + openness * 0.34 + sociability * 0.22, 0.34, 2.4);
mateWeight *= clamp(1 - stressPressure * 0.22, 0.55, 1.0);
mateWeight *= 1 + Math.min(0.70, affinity / 140);
if (loveMochi) mateWeight *= 3.15;
if (fightMochi) mateWeight *= 0.52;
if (options.mode === "mate") mateWeight *= 1.42;
}
const total = Math.max(0, fightWeight) + Math.max(0, mateWeight);
return { fightWeight, mateWeight, fightEligible, mateEligible, fightProbability: total > 0 ? fightWeight / total : 0, totalWeight: total };
}
function pickSocialFightMate(world, key, weights, ...seeds) {
const fightWeight = Math.max(0, Number(weights?.fightWeight || 0) || 0);
const mateWeight = Math.max(0, Number(weights?.mateWeight || 0) || 0);
if (fightWeight <= 0 && mateWeight <= 0) return "none";
if (fightWeight > 0 && mateWeight <= 0) return "fight";
if (mateWeight > 0 && fightWeight <= 0) return "mate";
const bucket = Math.floor((Number(world?.time || 0) || 0) / 3);
return deterministicChance(world, key || "social-fight-mate-choice", fightWeight / (fightWeight + mateWeight), ...seeds, bucket) ? "fight" : "mate";
}
function triggerFriendContact(t, other, world, dt = 0) {
if (!t || !other || !world) return false;
const amount = 0.30 + Math.max(0, Number(dt) || 0) * 0.85;
@ -34,22 +121,14 @@ function updateSocialContactBehavior(t, world, dt, mode = "bond") {
const near = dist(t, other) <= Math.max(48, (t.radius || 20) + (other.radius || 20) + 12);
if (!near) return moveToOrUse(t, other, mode === "family" ? "follow_parent" : "seek_friend", mode === "family" ? "\u5bb6\u65cf\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b" : "\u4ef2\u9593\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b");
const conflict = conflictTargetFor(t, world, 30);
const forcedConflict = (t.fightMochiTimer || 0) > 0.04 || (other.fightMochiTimer || 0) > 0.04;
const socialPartsForContact = t.socialReasonParts || {};
const conflictDominant = Number(socialPartsForContact.conflict || 0) >= Math.max(Number(socialPartsForContact.bond || 0), Number(socialPartsForContact.mate || 0), Number(socialPartsForContact.family || 0)) + 8;
if (forcedConflict || (conflict?.target === other && conflict.score >= 34 && conflictDominant)) {
const weights = socialFightMateWeights(t, other, world, { mode, requireBirthReady: true, distance: dist(t, other) });
const choice = pickSocialFightMate(world, "social-contact-weighted-fight-mate", weights, t, other, mode);
if (choice === "fight") {
t.conflictTargetId = other.id;
other.conflictTargetId = t.id;
return updateFightBehavior(t, world, dt) || true;
}
const mate = mateTargetFor(t, world, 150);
const parts = t.socialReasonParts || {};
const selfBehaviorId = getTarinaiBehaviorId(t);
const otherBehaviorId = getTarinaiBehaviorId(other);
const mateIntent = mode === "mate" || selfBehaviorId === "approach_mate" || otherBehaviorId === "approach_mate" || (t.loveMochiTimer || 0) > 0.04 || (other.loveMochiTimer || 0) > 0.04;
const mateDominant = Number(parts.mate || 0) >= Math.max(Number(parts.bond || 0), Number(parts.conflict || 0)) + 10;
if (mate === other && (t.reproductionTimer || 0) <= 8 && (other.reproductionTimer || 0) <= 10 && (mateIntent || mateDominant)) {
if (world.startBirthRitual?.(t, other)) return "finished";
}
if (choice === "mate" && world.startBirthRitual?.(t, other)) return "finished";
return triggerFriendContact(t, other, world, dt);
}
@ -58,9 +137,17 @@ function updateMateBehavior(t, world, dt) {
if (!other) return false;
const d = dist(t, other);
if (d <= Math.max(50, (t.radius || 20) + (other.radius || 20) + 14)) {
const otherWantsMate = (getTarinaiBehaviorId(other)) === "approach_mate" || (other.loveMochiTimer || 0) > 0.04 || (other.reproductionTimer || 0) <= 4;
const selfForced = isCurrentBehaviorForced(t) || (t.loveMochiTimer || 0) > 0.04;
if ((otherWantsMate || selfForced || (t.reproductionTimer || 0) <= 4) && world.startBirthRitual?.(t, other)) return "finished";
const forced = isCurrentBehaviorForced(t);
if (!forced) {
const weights = socialFightMateWeights(t, other, world, { mode: "mate", requireBirthReady: true, distance: d });
const choice = pickSocialFightMate(world, "mate-contact-weighted-fight-mate", weights, t, other);
if (choice === "fight") {
t.conflictTargetId = other.id;
other.conflictTargetId = t.id;
return updateFightBehavior(t, world, dt) || true;
}
if (choice === "mate" && world.startBirthRitual?.(t, other)) return "finished";
} else if (world.startBirthRitual?.(t, other)) return "finished";
moveToOrUse(t, other, "seek_friend", "\u7e41\u6b96\u3067\u304d\u308b\u76f8\u624b\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b");
if (typeof applyNeedShock === "function") applyNeedShock(other, { social: dt * 10 }, t);
return true;
@ -87,13 +174,17 @@ function updateFightBehavior(t, world, dt) {
t.setActionState?.("fight", { target: rival || t.target, reason: rival?.name ? `${rival.name}\u3068\u55a7\u5629\u3057\u3066\u3044\u308b` : "\u55a7\u5629\u3057\u3066\u3044\u308b", sleeping: false });
return true;
}
const candidate = conflictTargetFor(t, world, (t?.fightMochiTimer || 0) > 0.04 || isCurrentBehaviorForced(t) ? 24 : 42);
const candidate = conflictTargetFor(t, world, (t?.fightMochiTimer || 0) > 0.04 || isCurrentBehaviorForced(t) ? 24 : 14);
const current = currentBehaviorTarget(t);
const rival = isLiveTarinaiEntity(candidate?.target) ? candidate.target : (isLiveTarinaiEntity(current) ? current : nearestForcedFightTarget(t, world, isCurrentBehaviorForced(t) ? 260 : 220));
if (!isLiveTarinaiEntity(rival)) return false;
t.conflictTargetId = rival.id;
if (dist(t, rival) > Math.max(42, (t.radius || 20) + (rival.radius || 20) + 12)) return moveToOrUse(t, rival, "seek_enemy", "\u6c17\u306b\u5165\u3089\u306a\u3044\u76f8\u624b\u306b\u5411\u304b\u3063\u3066\u3044\u308b");
const nearDistance = dist(t, rival);
if (nearDistance > Math.max(42, (t.radius || 20) + (rival.radius || 20) + 12)) return moveToOrUse(t, rival, "seek_enemy", "\u6c17\u306b\u5165\u3089\u306a\u3044\u76f8\u624b\u306b\u5411\u304b\u3063\u3066\u3044\u308b");
if (candidate?.forced || isCurrentBehaviorForced(t) || (t.fightMochiTimer || 0) > 0.04 || (rival.fightMochiTimer || 0) > 0.04) return !!world.startForcedFight?.(t, rival);
const weights = socialFightMateWeights(t, rival, world, { mode: "conflict", requireBirthReady: true, distance: nearDistance });
const choice = pickSocialFightMate(world, "fight-contact-weighted-fight-mate", weights, t, rival);
if (choice === "mate" && world.startBirthRitual?.(t, rival)) return true;
world.startConflict?.(t, rival);
return true;
}