This commit is contained in:
33333-33333 2026-06-21 22:29:00 +09:00
commit 0a6148c73f
67 changed files with 2653 additions and 1519 deletions

View file

@ -64,6 +64,62 @@ function stableUnit(seed, salt = "") {
return ((h >>> 0) % 100000) / 100000;
}
const GENETIC_KEYS = ["lifeSpanMul", "attackMul", "speedMul", "sizeMul"];
const GENETIC_LIMITS = Object.freeze({
lifeSpanMul: [0.82, 1.24],
attackMul: [0.80, 1.24],
speedMul: [0.86, 1.18],
sizeMul: [0.84, 1.18],
});
function clampGeneticValue(key, value) {
const range = GENETIC_LIMITS[key] || [0.75, 1.25];
return clamp(Number(value) || 1, range[0], range[1]);
}
function randomGenetics(seed = "") {
return {
lifeSpanMul: clampGeneticValue("lifeSpanMul", 0.82 + stableUnit(seed, "gene-life") * 0.42),
attackMul: clampGeneticValue("attackMul", 0.80 + stableUnit(seed, "gene-attack") * 0.44),
speedMul: clampGeneticValue("speedMul", 0.86 + stableUnit(seed, "gene-speed") * 0.32),
sizeMul: clampGeneticValue("sizeMul", 0.84 + stableUnit(seed, "gene-size") * 0.34),
};
}
function normalizeGenetics(source = null, seed = "") {
const fallback = randomGenetics(seed);
const out = {};
for (const key of GENETIC_KEYS) out[key] = clampGeneticValue(key, source?.[key] ?? fallback[key]);
return out;
}
function inheritedGenetics(a, b, seed = "") {
const ga = normalizeGenetics(a?.genetics, a?.familyKey || a?.id || "a");
const gb = normalizeGenetics(b?.genetics, b?.familyKey || b?.id || "b");
const out = {};
for (const key of GENETIC_KEYS) {
const base = ((ga[key] || 1) + (gb[key] || 1)) / 2;
const mutation = (stableUnit(seed, `gene-mut-${key}`) - 0.5) * 0.10;
out[key] = clampGeneticValue(key, base + mutation);
}
return out;
}
function adultScaleFromGenetics(seed = "", genetics = null) {
const g = normalizeGenetics(genetics, seed);
return clamp((0.265 + stableUnit(seed, "adult-scale") * 0.060) * g.sizeMul, 0.205, 0.390);
}
function childGrowthScaleFactor(growth = 1) {
const x = clamp(Number(growth) || 0, 0, 1);
const smooth = x * x * (3 - 2 * x);
return 0.46 + smooth * 0.54;
}
function scaleForGrowth(adultScale = 0.28, growth = 1) {
return clamp((Number(adultScale) || 0.28) * childGrowthScaleFactor(growth), 0.10, 0.42);
}
const PERSONALITIES = Object.freeze({});
const PERSONALITY_IDS = [];
@ -73,7 +129,7 @@ function personalityForSeed(seed) {
const PERSONALITY_KEYS = ["aggression", "openness", "sociability", "neuroticism"];
const PERSONALITY_DAILY_LIMIT = { perAxis: 0.08, total: 0.20 };
const FRIEND_AFFINITY_THRESHOLD = 16;
const FRIEND_AFFINITY_THRESHOLD = 14;
const PERSONALITY_LABELS = {
aggression: { label: "\u653b\u6483\u6027", low: "\u304a\u3060\u3084\u304b", high: "\u6012\u308a\u3063\u307d\u3044" },
openness: { label: "\u958b\u653e\u6027", low: "\u4fdd\u5b88\u7684", high: "\u904a\u3073\u597d\u304d" },
@ -91,6 +147,28 @@ function normalizePersonality(source = null) {
return out;
}
function zunchiSlavePersonalityValue() {
const out = {};
for (const key of PERSONALITY_KEYS) out[key] = -1;
return out;
}
function applyZunchiSlavePersonality(tarinai, opts = {}) {
if (!tarinai) return false;
const next = zunchiSlavePersonalityValue();
let changed = false;
tarinai.birthPersonality = normalizePersonality(tarinai.birthPersonality || next);
tarinai.currentPersonality = normalizePersonality(tarinai.currentPersonality || next);
for (const key of PERSONALITY_KEYS) {
if ((tarinai.currentPersonality[key] || 0) !== -1) changed = true;
tarinai.currentPersonality[key] = -1;
if (opts.birth) tarinai.birthPersonality[key] = -1;
}
tarinai.personalityDaily = { day: tarinai.world?.day || 1, total: 0, byKey: {}, byCause: {} };
if (changed && opts.record !== false) tarinai.recordChangeCause?.("\u305a\u3093\u3061\u3069\u308c\u3044\u5316", "\u6027\u683c", { value: -1 });
return changed;
}
function randomBirthPersonality(seed = "") {
const out = {};
for (const key of PERSONALITY_KEYS) out[key] = clampPersonalityValue(stableUnit(seed, `personality-${key}`) * 1.2 - 0.6);
@ -137,6 +215,10 @@ function personalityReasonText(reason = "") {
"after repeated contact with poop": "\u305a\u3093\u3061\u306b\u6163\u308c\u305f\u5f71\u97ff\u3067",
"after repeated contact with corpses": "\u6b7b\u9ab8\u306b\u6163\u308c\u305f\u5f71\u97ff\u3067",
"after being hurt": "\u75db\u307f\u3092\u899a\u3048\u305f\u5f71\u97ff\u3067",
"after ammo knockback": "\u5f3e\u85ac\u3067\u5f3e\u304d\u98db\u3070\u3057\u305f\u5f71\u97ff\u3067",
"after successful intimidation": "\u5a01\u5687\u306b\u6210\u529f\u3057\u305f\u5f71\u97ff\u3067",
"after failed intimidation": "\u5a01\u5687\u306b\u5931\u6557\u3057\u305f\u5f71\u97ff\u3067",
"after being struck by genkotsu": "\u3052\u3093\u3053\u3064\u3092\u53d7\u3051\u305f\u5f71\u97ff\u3067",
};
return map[key] || key;
}
@ -171,14 +253,31 @@ function personalityDailyState(tarinai) {
const day = tarinai?.world?.day || 1;
const state = tarinai.personalityDaily || {};
if (state.day !== day) {
tarinai.personalityDaily = { day, total: 0, byKey: {} };
tarinai.personalityDaily = { day, total: 0, byKey: {}, byCause: {} };
return tarinai.personalityDaily;
}
state.byKey = state.byKey || {};
for (const key of Object.keys(state.byKey)) {
const v = state.byKey[key];
if (typeof v === "number") state.byKey[key] = { up: Math.max(0, v), down: Math.max(0, -v) };
else state.byKey[key] = { up: Math.max(0, Number(v?.up) || 0), down: Math.max(0, Number(v?.down) || 0) };
if (typeof v === "number") state.byKey[key] = { up: Math.max(0, v), down: Math.max(0, -v), causes: [] };
else {
const causes = Array.isArray(v?.causes) ? [...new Set(v.causes.map(c => String(c || "").trim()).filter(Boolean))].slice(0, 4) : [];
state.byKey[key] = { up: Math.max(0, Number(v?.up) || 0), down: Math.max(0, Number(v?.down) || 0), causes };
}
}
state.byCause = state.byCause && typeof state.byCause === "object" ? state.byCause : {};
for (const cause of Object.keys(state.byCause)) {
const entry = state.byCause[cause] || {};
const byKey = entry.byKey && typeof entry.byKey === "object" ? entry.byKey : {};
const nextByKey = {};
for (const key of PERSONALITY_KEYS) {
const v = byKey[key] || {};
const up = Math.max(0, Number(v.up) || 0);
const down = Math.max(0, Number(v.down) || 0);
if (up > 0.0001 || down > 0.0001) nextByKey[key] = { up, down };
}
if (Object.keys(nextByKey).length) state.byCause[cause] = { byKey: nextByKey };
else delete state.byCause[cause];
}
state.total = Math.max(0, Number(state.total) || 0);
tarinai.personalityDaily = state;
@ -229,8 +328,23 @@ function adjustPersonality(tarinai, key, delta, reason = "") {
if (!actual) return 0;
tarinai.currentPersonality[key] = after;
state.byKey[key] = used;
if (!Array.isArray(used.causes)) used.causes = [];
used[actual > 0 ? "up" : "down"] = (used[actual > 0 ? "up" : "down"] || 0) + Math.abs(actual);
state.total = (state.total || 0) + Math.abs(actual);
const label = PERSONALITY_LABELS[key]?.label || key;
const reasonText = personalityReasonText(reason) || "\u6027\u683c\u5909\u5316";
if (reasonText && !used.causes.includes(reasonText)) used.causes.push(reasonText);
if (used.causes.length > 4) used.causes.length = 4;
if (reasonText) {
state.byCause = state.byCause && typeof state.byCause === "object" ? state.byCause : {};
const causeEntry = state.byCause[reasonText] || { byKey: {} };
causeEntry.byKey = causeEntry.byKey && typeof causeEntry.byKey === "object" ? causeEntry.byKey : {};
const causeKeyEntry = causeEntry.byKey[key] || { up: 0, down: 0 };
causeKeyEntry[actual > 0 ? "up" : "down"] = (causeKeyEntry[actual > 0 ? "up" : "down"] || 0) + Math.abs(actual);
causeEntry.byKey[key] = causeKeyEntry;
state.byCause[reasonText] = causeEntry;
}
tarinai.recordChangeCause?.(reasonText, label, { value: actual });
const afterBand = personalityBand(after);
if (beforeBand !== afterBand) {
const text = personalityThresholdLogText(tarinai, key, after, reason, beforeBand);
@ -458,7 +572,7 @@ class SpatialGrid {
const type = it.type || "";
if (type === "stone" || type === "ball" || type === "nest_box" || type === "bed" || type === "fence_v" || type === "fence_h") this.add(this.obstacleCells, it);
if (type === "sweet" || type === "love_mochi" || type === "fight_mochi" || type === "grass" || type === "water" || type === "water_bowl" || type === "ant_corpse" || (typeof isParamEffectItemType === "function" && isParamEffectItemType(type))) this.add(this.foodCells, it);
if (type === "firecracker" || type === "genkotsu" || type === "sleep_drug" || type === "zunchi" || type === "splat" || type === "mystery_drug" || type === "laxative" || type === "niteropu" || type === "mercury" || type === "giant_drug" || type === "dwarf_drug") this.add(this.hazardCells, it);
if (type === "firecracker" || type === "genkotsu" || type === "pushpin" || type === "sleep_drug" || type === "zunchi" || type === "splat" || type === "mystery_drug" || type === "laxative" || type === "niteropu" || type === "mercury" || type === "giant_drug" || type === "dwarf_drug") this.add(this.hazardCells, it);
}
rebuild(items, tarinai, ants = []) {