1.4
This commit is contained in:
parent
ad2cc76429
commit
7455d630fc
82 changed files with 2179 additions and 900 deletions
|
|
@ -2,17 +2,41 @@
|
|||
|
||||
|
||||
(function (global) {
|
||||
|
||||
function socialPersonalityProfile(tarinai) {
|
||||
if (tarinai && typeof tarinai.personalityProfile === "function") return tarinai.personalityProfile();
|
||||
const base = { label: "\u4E2D\u7ACB", fear: 1, fight: 1, social: 1, relation: 1, sleep: 1, zunda: 1, play: 1 };
|
||||
const cur = tarinai?.currentPersonality || {};
|
||||
const neu = Number(cur.neuroticism || 0) || 0;
|
||||
const aggr = Number(cur.aggression || 0) || 0;
|
||||
const soc = Number(cur.sociability || 0) || 0;
|
||||
const open = Number(cur.openness || 0) || 0;
|
||||
return {
|
||||
...base,
|
||||
fear: clamp(1 + neu * 0.38, 0.55, 1.85),
|
||||
fight: clamp(1 + aggr * 0.55, 0.35, 2.35),
|
||||
social: clamp(1 + soc * 0.24, 0.62, 1.52),
|
||||
relation: clamp(1 + soc * 0.24, 0.62, 1.60),
|
||||
play: clamp(1 + open * 0.45, 0.45, 2.10),
|
||||
zunda: 1,
|
||||
sleep: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function isSocialTarinaiEntity(value) {
|
||||
return Boolean(value && !value.dead && typeof value.relationTo === "function" && Number.isFinite(value.x) && Number.isFinite(value.y));
|
||||
}
|
||||
const World = global.World;
|
||||
if (!World) throw new Error("World is not available for mixin: world_family_social.js");
|
||||
|
||||
const RESET_PRESETS = Object.freeze({
|
||||
empty: { label: "空白" },
|
||||
default: { label: "デフォルト" },
|
||||
athletic: { label: "アスレチック" },
|
||||
crowded: { label: "過密" },
|
||||
family: { label: "家族" },
|
||||
war: { label: "戦争" },
|
||||
happy: { label: "幸福" },
|
||||
empty: { label: "\u7A7A\u767D" },
|
||||
default: { label: "\u30C7\u30D5\u30A9\u30EB\u30C8" },
|
||||
athletic: { label: "\u30A2\u30B9\u30EC\u30C1\u30C3\u30AF" },
|
||||
crowded: { label: "\u904E\u5BC6" },
|
||||
family: { label: "\u5BB6\u65CF" },
|
||||
war: { label: "\u6226\u4E89" },
|
||||
happy: { label: "\u5E78\u798F" },
|
||||
});
|
||||
|
||||
function normalizeResetPresetId(presetId = "default") {
|
||||
|
|
@ -205,7 +229,7 @@
|
|||
if (opts.toolSize) item.toolSize = opts.toolSize;
|
||||
if (type === "duplicator") {
|
||||
item.storedFoodType = opts.storedFoodType || "sweet";
|
||||
item.storedFoodLabel = typeof foodLabel === "function" ? foodLabel(item.storedFoodType) : (typeof toolLabel === "function" ? toolLabel(item.storedFoodType) : item.storedFoodType);
|
||||
item.storedFoodLabel = typeof toolLabel === "function" ? toolLabel(item.storedFoodType) : item.storedFoodType;
|
||||
if (item.roles) item.roles.food = true;
|
||||
}
|
||||
if (type === "rotator" || type === "reciprocator") configurePresetMechanicalItem(item, opts);
|
||||
|
|
@ -281,7 +305,7 @@
|
|||
const compact = fieldId === "cage" || fieldId === "garden";
|
||||
const center = { x0: worldRef.w * 0.10, x1: worldRef.w * 0.90, y0: worldRef.h * 0.14, y1: worldRef.h * 0.86 };
|
||||
addPresetTarinai(worldRef, presetCount(field, 14, 7, 24), { x0: worldRef.w * 0.08, x1: worldRef.w * 0.30, y0: worldRef.h * 0.18, y1: worldRef.h * 0.82 });
|
||||
// 物理ブロックは柵より先に置く。狭い中小フィールドでは柵側を間引き、ブロックを優先する。
|
||||
// \u7269\u7406\u30D6\u30ED\u30C3\u30AF\u306F\u67F5\u3088\u308A\u5148\u306B\u7F6E\u304F\u3002\u72ED\u3044\u4E2D\u5C0F\u30D5\u30A3\u30FC\u30EB\u30C9\u3067\u306F\u67F5\u5074\u3092\u9593\u5F15\u304D\u3001\u30D6\u30ED\u30C3\u30AF\u3092\u512A\u5148\u3059\u308B\u3002
|
||||
addPresetItemsScattered(worldRef, "stone", presetCount(field, compact ? 4 : 6, 3, 9), center, { pad: compact ? 118 : 132, reason: "preset-athletic-physical-block", avoidOverlap: true, avoidAllObstacles: true, attempts: 140, maxRadius: compact ? 260 : 220 });
|
||||
addPresetItemsScattered(worldRef, "stove", presetCount(field, 2, 1, 3), center, { pad: 118, reason: "preset-athletic-stove", avoidOverlap: true, avoidAllObstacles: true, attempts: 100, maxRadius: 210 });
|
||||
addPresetItemsScattered(worldRef, "ball", presetCount(field, compact ? 8 : 10, 4, 18), null, { pad: 96, reason: "preset-ball", avoidOverlap: true, avoidAllObstacles: true, attempts: 90, maxRadius: compact ? 230 : 190 });
|
||||
|
|
@ -354,8 +378,6 @@
|
|||
return "soil";
|
||||
}
|
||||
|
||||
global.TarinaiResetPresets = Object.freeze({ ids: Object.keys(RESET_PRESETS), meta: RESET_PRESETS, normalize: normalizeResetPresetId });
|
||||
|
||||
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
|
||||
reset(seedPopulation = null, fieldType = this.fieldType || "garden", presetId = "default") {
|
||||
const field = FIELD_TYPES?.[fieldType] || FIELD_TYPES.garden;
|
||||
|
|
@ -424,8 +446,8 @@
|
|||
this.updateEffectCounts();
|
||||
this.markTerrainDirty?.("reset");
|
||||
this.rebuildSpatial(true);
|
||||
const label = RESET_PRESETS[preset]?.label || "デフォルト";
|
||||
showToast(`${label}で新しい観察を開始しました。`);
|
||||
const label = RESET_PRESETS[preset]?.label || "\u30C7\u30D5\u30A9\u30EB\u30C8";
|
||||
showToast(`${label}\u3067\u65B0\u3057\u3044\u89B3\u5BDF\u3092\u958B\u59CB\u3057\u307E\u3057\u305F\u3002`);
|
||||
syncTopButtons();
|
||||
},
|
||||
|
||||
|
|
@ -810,17 +832,17 @@
|
|||
const occupants = this.nestBoxOccupants(box, Infinity);
|
||||
const names = occupants.map(t => t?.name).filter(Boolean).slice(0, 5);
|
||||
const more = occupants.length > names.length ? `\u3001\u307b\u304b${occupants.length - names.length}\u5339` : "";
|
||||
return ["\u5de3\u7bb1", names.length ? `\u5165\u3063\u3066\u308b\u5b50: ${names.join("\u3001")}${more}` : "\u5165\u3063\u3066\u308b\u5b50: \u306a\u3057", this.temperatureTooltipLineFor?.(box, "内部気温")].filter(Boolean);
|
||||
return ["\u5de3\u7bb1", names.length ? `\u5165\u3063\u3066\u308b\u5b50: ${names.join("\u3001")}${more}` : "\u5165\u3063\u3066\u308b\u5b50: \u306a\u3057", this.temperatureTooltipLineFor?.(box, "\u5185\u90E8\u6C17\u6E29")].filter(Boolean);
|
||||
},
|
||||
|
||||
|
||||
temperatureTooltipLineFor(target, label = "体感気温") {
|
||||
temperatureTooltipLineFor(target, label = "\u4F53\u611F\u6C17\u6E29") {
|
||||
if (!target || !this.temperatureAt) return "";
|
||||
const temp = this.temperatureAt(target.x, target.y);
|
||||
if (!Number.isFinite(Number(temp))) return "";
|
||||
const status = this.temperatureStatusFor?.(temp) || null;
|
||||
const suffix = status?.label ? `(${status.label})` : "";
|
||||
return `${label}: ${Number(temp).toFixed(1)}℃${suffix}`;
|
||||
const suffix = status?.label ? `\uFF08${status.label}\uFF09` : "";
|
||||
return `${label}: ${Number(temp).toFixed(1)}\u2103${suffix}`;
|
||||
},
|
||||
|
||||
pointerTarinaiTooltipInfo() {
|
||||
|
|
@ -836,9 +858,9 @@
|
|||
if (!best) return null;
|
||||
const behavior = (typeof behaviorText === "function" ? behaviorText(best) : "") || window.TEXT_CATALOG?.stateLabel?.(best.state, best) || best.thought || "";
|
||||
const maxEnergy = typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(best) : (Number(best.maxEnergy) || 100);
|
||||
const statusLine = `体力: ${Math.round(best.energy || 0)}/${Math.round(maxEnergy)} 空腹: ${Math.round(best.hunger || 0)} ストレス: ${Math.round(best.stress || 0)}`;
|
||||
const tempLine = this.temperatureTooltipLineFor(best, "体感気温");
|
||||
return { target: best, lines: [best.name || "たりない", behavior, statusLine, tempLine].filter(Boolean), kind: "tarinai" };
|
||||
const statusLine = `\u4F53\u529B: ${Math.round(best.energy || 0)}/${Math.round(maxEnergy)}\u3000\u7A7A\u8179: ${Math.round(best.hunger || 0)}\u3000\u30B9\u30C8\u30EC\u30B9: ${Math.round(best.stress || 0)}`;
|
||||
const tempLine = this.temperatureTooltipLineFor(best, "\u4F53\u611F\u6C17\u6E29");
|
||||
return { target: best, lines: [best.name || "\u305F\u308A\u306A\u3044", behavior, statusLine, tempLine].filter(Boolean), kind: "tarinai" };
|
||||
},
|
||||
|
||||
pointerOwnedBedInfo() {
|
||||
|
|
@ -858,7 +880,7 @@
|
|||
ownedBedTooltipLines(bed) {
|
||||
if (!bed || bed.dead || bed.type !== "grass_bed") return [];
|
||||
const owner = this.liveTarinaiById?.(bed.ownerId) || null;
|
||||
return ["\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9", `\u6301\u3061\u4e3b: ${owner?.name || "\u4e0d\u660e"}`, this.temperatureTooltipLineFor?.(bed, "周辺気温")].filter(Boolean);
|
||||
return ["\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9", `\u6301\u3061\u4e3b: ${owner?.name || "\u4e0d\u660e"}`, this.temperatureTooltipLineFor?.(bed, "\u5468\u8FBA\u6C17\u6E29")].filter(Boolean);
|
||||
},
|
||||
|
||||
pointerItemTooltipInfo() {
|
||||
|
|
@ -1161,19 +1183,24 @@
|
|||
},
|
||||
|
||||
startFightMochiIntimidation(a, b) {
|
||||
if (!a || !b || a.dead || b.dead) return false;
|
||||
if (!isSocialTarinaiEntity(a) || !isSocialTarinaiEntity(b)) return false;
|
||||
const aDrug = (a.fightMochiTimer || 0) > 0.04;
|
||||
const bDrug = (b.fightMochiTimer || 0) > 0.04;
|
||||
const actor = aDrug && !bDrug ? a : (!aDrug && bDrug ? b : (a.energy + a.stress * 0.36 >= b.energy + b.stress * 0.36 ? a : b));
|
||||
const target = actor === a ? b : a;
|
||||
if (target.isTarinaiChampion) return false;
|
||||
if ((actor.intimidationCooldown || 0) > 0.04 || (target.intimidationCooldown || 0) > 0.04) return false;
|
||||
if (!this.canFightPair(actor, target)) return false;
|
||||
actor.intimidationCooldown = Math.max(actor.intimidationCooldown || 0, deterministicRange(this, "fight-mochi-intimidate-repeat-cooldown-actor", 5.5, 8.5, actor, target));
|
||||
target.intimidationCooldown = Math.max(target.intimidationCooldown || 0, deterministicRange(this, "fight-mochi-intimidate-repeat-cooldown-target", 4.5, 7.5, actor, target));
|
||||
actor.intimidateTimer = Math.max(actor.intimidateTimer || 0, deterministicRange(this, "fight-mochi-intimidate-actor-timer", 0.85, 1.35, actor, target));
|
||||
actor.intimidateTargetId = target.id;
|
||||
actor.surpriseTimer = Math.max(actor.surpriseTimer || 0, 0.28);
|
||||
target.intimidatedTimer = Math.max(target.intimidatedTimer || 0, deterministicRange(this, "fight-mochi-intimidate-target-timer", 0.75, 1.25, actor, target));
|
||||
target.fearTimer = Math.max(target.fearTimer || 0, 0.72 * target.personalityProfile().fear);
|
||||
const targetProfile = socialPersonalityProfile(target);
|
||||
target.fearTimer = Math.max(target.fearTimer || 0, 0.72 * targetProfile.fear);
|
||||
actor.adjustRelation(target, -0.05, 0.06, "intimidate");
|
||||
target.adjustRelation(actor, -0.12, 0.30 * target.personalityProfile().fear, "intimidate");
|
||||
target.adjustRelation(actor, -0.12, 0.30 * targetProfile.fear, "intimidate");
|
||||
this.effects.push(new Effect("ring", actor.x, actor.y - actor.radius * 0.65, {
|
||||
life: 0.34,
|
||||
size: actor.radius * 0.62,
|
||||
|
|
@ -1201,7 +1228,7 @@
|
|||
if (!this.canFightPair(actor, target)) return false;
|
||||
const actorScore = actor === a ? scoreA : scoreB;
|
||||
const targetScore = actor === a ? scoreB : scoreA;
|
||||
const canIntimidate = !actor.lowHealthSprite || !actor.lowHealthSprite();
|
||||
const canIntimidate = !target.isTarinaiChampion && (actor.intimidationCooldown || 0) <= 0.04 && (target.intimidationCooldown || 0) <= 0.04 && (!actor.lowHealthSprite || !actor.lowHealthSprite());
|
||||
const intimidationChance = 0.30;
|
||||
if (canIntimidate && deterministicChance(this, "conflict-intimidation-choice", intimidationChance, actor, target)) {
|
||||
return this.startIntimidation(actor, target, actorScore, targetScore);
|
||||
|
|
@ -1210,12 +1237,15 @@
|
|||
},
|
||||
|
||||
startIntimidation(actor, target, actorScore = 0, targetScore = 0) {
|
||||
if (!actor || !target || actor.dead || target.dead) return false;
|
||||
if (!isSocialTarinaiEntity(actor) || !isSocialTarinaiEntity(target) || actor === target) return false;
|
||||
if (target.isTarinaiChampion) return false;
|
||||
if ((actor.fightTimer || 0) > 0.04 || (target.fightTimer || 0) > 0.04) return false;
|
||||
if ((actor.intimidateTimer || 0) > 0.04 || (target.intimidateTimer || 0) > 0.04) return false;
|
||||
if ((actor.intimidationCooldown || 0) > 0.04 || (target.intimidationCooldown || 0) > 0.04) return false;
|
||||
if (this.fightPairCooldownRemaining?.(actor, target) > 0.04) return false;
|
||||
if (actor.lowHealthSprite && actor.lowHealthSprite()) return false;
|
||||
const fearLoad = ((typeof target.relationTo === "function" ? (target.relationTo(actor.id).fear || 0) : 0) * 0.72) + target.personalityProfile().fear * 8;
|
||||
const targetProfile = socialPersonalityProfile(target);
|
||||
const fearLoad = ((typeof target.relationTo === "function" ? (target.relationTo(actor.id).fear || 0) : 0) * 0.72) + targetProfile.fear * 8;
|
||||
const actorAggression = Math.max(0, actor.currentPersonality?.aggression || 0);
|
||||
const targetAggression = Math.max(0, target.currentPersonality?.aggression || 0);
|
||||
const actorSize = actor.effectiveScale ? actor.effectiveScale() : (actor.scale || 0.28);
|
||||
|
|
@ -1228,6 +1258,8 @@
|
|||
target.surpriseTimer = Math.max(target.surpriseTimer, 0.32);
|
||||
actor.fightCooldown = CONFIG.fightCooldown + deterministicRange(this, "intimidation-actor-cooldown", 1, 4, actor, target);
|
||||
target.fightCooldown = CONFIG.fightCooldown + deterministicRange(this, "intimidation-target-cooldown", 1, 4, actor, target);
|
||||
actor.intimidationCooldown = Math.max(actor.intimidationCooldown || 0, deterministicRange(this, "intimidation-repeat-cooldown-actor", 7.5, 11.5, actor, target));
|
||||
target.intimidationCooldown = Math.max(target.intimidationCooldown || 0, deterministicRange(this, "intimidation-repeat-cooldown-target", 5.5, 9.0, actor, target));
|
||||
actor.intimidateTimer = Math.max(actor.intimidateTimer, deterministicRange(this, "intimidation-actor-timer", 1.05, 1.65, actor, target));
|
||||
actor.intimidateTargetId = target.id;
|
||||
actor.setActionState("intimidate", { target, reason: "\u76f8\u624b\u3092\u5a01\u5687\u3057\u3066\u3044\u308b" });
|
||||
|
|
@ -1239,10 +1271,10 @@
|
|||
}));
|
||||
this.spawnBubble(actor.x, actor.y - actor.radius * 1.30, "\u306f\u3046\u30fc\uff01", "rgba(92,62,34,0.82)");
|
||||
target.intimidatedTimer = Math.max(target.intimidatedTimer || 0, deterministicRange(this, "intimidation-target-prep-timer", 0.95, 1.55, actor, target));
|
||||
target.fearTimer = Math.max(target.fearTimer, 0.65 * target.personalityProfile().fear);
|
||||
target.fearTimer = Math.max(target.fearTimer, 0.65 * targetProfile.fear);
|
||||
if (!deterministicChance(this, "intimidation-success", successChance, actor, target)) {
|
||||
actor.adjustPersonality?.("aggression", -0.018, "after failed intimidation");
|
||||
target.adjustRelation(actor, -0.08, 0.22 * target.personalityProfile().fear, "intimidate");
|
||||
target.adjustRelation(actor, -0.08, 0.22 * targetProfile.fear, "intimidate");
|
||||
actor.intimidateTimer = 0;
|
||||
actor.intimidateTargetId = null;
|
||||
target.intimidatedTimer = 0;
|
||||
|
|
@ -1259,17 +1291,19 @@
|
|||
target.defeatedTimer = Math.max(target.defeatedTimer, deterministicRange(this, "intimidation-defeated-timer", 1.8, 2.8, actor, target));
|
||||
target.intimidatedTimer = Math.max(target.intimidatedTimer || 0, deterministicRange(this, "intimidation-success-timer", 1.2, 2.0, actor, target));
|
||||
if (target.enterPanic) target.enterPanic({ threat: actor, target: actor, reason: "\u5a01\u5687\u3055\u308c\u3066\u9003\u3052\u3066\u3044\u308b", fear: 1.35, wake: true, cause: "intimidated" });
|
||||
else { target.fearTimer = Math.max(target.fearTimer, 1.35 * target.personalityProfile().fear); target.setActionState?.("panic", { target: actor, reason: "\u5a01\u5687\u3055\u308c\u3066\u9003\u3052\u3066\u3044\u308b", wake: true }); }
|
||||
else { target.fearTimer = Math.max(target.fearTimer, 1.35 * targetProfile.fear); target.setActionState?.("panic", { target: actor, reason: "\u5a01\u5687\u3055\u308c\u3066\u9003\u3052\u3066\u3044\u308b", wake: true }); }
|
||||
this.spawnBubble(target.x, target.y - target.radius * 1.28, "\u306f\u3041\u3041\u3041\uff01", "rgba(84,66,86,0.80)");
|
||||
target.vx += (target.x < actor.x ? -1 : 1) * deterministicRange(this, "intimidation-knockback-x", 26, 54, actor, target);
|
||||
target.vy += deterministicRange(this, "intimidation-knockback-y", -18, 18, actor, target);
|
||||
actor.adjustRelation(target, -0.08, 0.06, "intimidate");
|
||||
target.adjustRelation(actor, -0.22, 0.85 * target.personalityProfile().fear, "intimidate");
|
||||
target.adjustRelation(actor, -0.22, 0.85 * targetProfile.fear, "intimidate");
|
||||
if (this.time - Math.max(actor.lastLog, target.lastLog) > 5.5) {
|
||||
this.log(`${actor.name}\u306f${target.name}\u3092\u5a01\u5687\u3057\u3066\u8ffd\u3044\u6255\u3063\u305f\u3002`, "fight", { participants: [actor, target] });
|
||||
actor.lastLog = target.lastLog = this.time;
|
||||
}
|
||||
this.markFightPairCooldown?.(actor, target, 2.8);
|
||||
this.markFightPairCooldown?.(actor, target, Math.max(6.5, CONFIG.fightCooldown + 3));
|
||||
actor.postConflictPeaceTimer = Math.max(actor.postConflictPeaceTimer || 0, deterministicRange(this, "intimidation-success-peace-actor", 3.8, 6.2, actor, target));
|
||||
target.postConflictPeaceTimer = Math.max(target.postConflictPeaceTimer || 0, deterministicRange(this, "intimidation-success-peace-target", 3.8, 6.2, actor, target));
|
||||
return true;
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue