This commit is contained in:
33333-33333 2026-07-18 13:06:02 +09:00
commit f657b6a4a4
94 changed files with 3970 additions and 1241 deletions

View file

@ -110,6 +110,104 @@ function pickSocialFightMate(world, key, weights, ...seeds) {
return deterministicChance(world, key || "social-fight-mate-choice", fightWeight / (fightWeight + mateWeight), ...seeds, bucket) ? "fight" : "mate";
}
function socialCrowdThresholds(t) {
const cur = t?.currentPersonality || {};
const social = clamp(Number(cur.sociability || 0) || 0, -1, 1);
const open = clamp(Number(cur.openness || 0) || 0, -1, 1);
const social01 = (social + 1) * 0.5;
const open01 = (open + 1) * 0.5;
const preferred = 4 + social01 * 6;
const lower = Math.floor(clamp(preferred - (3 + open01 * 3), 0, 7));
const upper = Math.ceil(clamp(preferred + 5 + open01 * 7, Math.max(8, lower + 4), 24));
return { lower, upper };
}
function findSocialCrowdEscapeTarget(t, world = t?.world) {
if (!t || !world) return null;
const source = typeof world.nearbyTarinai === "function"
? (world.nearbyTarinai(t.x, t.y, 118, true) || [])
: (world.tarinai || []);
let cx = 0, cy = 0, count = 0, inspected = 0;
for (const o of source) {
if (!o || o === t || o.dead) continue;
const dx = o.x - t.x, dy = o.y - t.y;
const d2 = dx * dx + dy * dy;
if (d2 > 118 * 118 || d2 < 0.0001) continue;
const w = 1 / Math.max(24, Math.sqrt(d2));
cx += dx * w;
cy += dy * w;
count++;
if (++inspected >= 28) break;
}
const seed = typeof stableUnit === "function" ? stableUnit(t.familyKey || t.id || "crowd", "crowd-escape-angle") : 0.5;
let base = Math.atan2(-cy, -cx);
if (!count || !Number.isFinite(base)) base = seed * Math.PI * 2;
const pad = Math.max(30, (CONFIG.worldPadding || 30) + (t.radius || 22));
const radius = Math.max(14, (t.radius || 22) * 0.72);
for (const escapeDistance of [150, 215]) {
for (const off of [0, 0.42, -0.42, 0.84, -0.84]) {
const a = base + off + (seed - 0.5) * 0.18;
const x = clamp(t.x + Math.cos(a) * escapeDistance, pad, world.w - pad);
const y = clamp(t.y + Math.sin(a) * escapeDistance, pad, world.h - pad);
if (world.pointBlockedByObstacle?.(x, y, radius, { maxChecks: 12, directionalOneWay: true })) continue;
return { x, y, dead: false, detour: true, crowdEscape: true };
}
}
return null;
}
const SOCIAL_CROWD_ESCAPE_REASON = "\u4ed6\u306e\u305f\u308a\u306a\u3044\u304c\u591a\u3059\u304e\u308b\u306e\u3067\u3001\u5c11\u3057\u96e2\u308c\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u3002";
function updateCrowdEscapeBehavior(t, world) {
if (!t || !world) return false;
const sample = t.socialCrowdSample || null;
const thresholds = socialCrowdThresholds(t);
const now = Number(world.time || 0) || 0;
const age = sample ? now - (Number(sample.at || 0) || 0) : Infinity;
const count = Math.max(0, Number(sample?.count || 0) || 0);
const upper = Math.max(thresholds.lower + 4, Number(sample?.upper ?? thresholds.upper) || thresholds.upper);
if (age > 3.5 || count < Math.max(thresholds.lower + 2, upper - 1)) return "finished";
let target = t.target?.crowdEscape ? t.target : null;
if (!target || target.dead || distXY(t.x, t.y, target.x, target.y) <= Math.max(28, (t.radius || 20) + 8)) {
target = findSocialCrowdEscapeTarget(t, world);
if (!target) return "finished";
t.target = target;
t.setActionState?.("wander", {
target,
reason: SOCIAL_CROWD_ESCAPE_REASON,
wake: true,
sleeping: false,
actionId: "crowd_escape",
actionLabel: "\u6df7\u96d1\u304b\u3089\u5c11\u3057\u96e2\u308c\u3088\u3046\u3068\u3057\u3066\u3044\u308b",
need: "social",
subNeed: "crowd",
});
} else {
t.setActionState?.("wander", {
target,
reason: SOCIAL_CROWD_ESCAPE_REASON,
sleeping: false,
actionId: "crowd_escape",
actionLabel: "\u6df7\u96d1\u304b\u3089\u5c11\u3057\u96e2\u308c\u3088\u3046\u3068\u3057\u3066\u3044\u308b",
need: "social",
subNeed: "crowd",
});
}
if (typeof setBehaviorText === "function") setBehaviorText(t, {
need: "social",
subNeed: "crowd",
actionId: "crowd_escape",
actionLabel: "\u6df7\u96d1\u304b\u3089\u5c11\u3057\u96e2\u308c\u3088\u3046\u3068\u3057\u3066\u3044\u308b",
reasonText: SOCIAL_CROWD_ESCAPE_REASON,
target,
phase: "perform",
source: "behavior",
});
return true;
}
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;
@ -205,6 +303,7 @@ function updatePanicBehavior(t, world, dt, needs) {
const breakerDanger = activePanicBreaker(t, world, 260);
const nearbyDanger = findNearbyDanger(world, t, 260) || null;
const realDanger = breakerDanger || nearbyDanger || null;
if (realDanger && realDanger !== t) t.lastPanicThreat = realDanger;
const currentTarget = currentBehaviorTarget(t);
const safety = Number(needs?.safety || 0) || 0;
const now = Number(world?.time || 0) || 0;