This commit is contained in:
33333-33333 2026-06-27 19:22:38 +09:00
commit 129c07183a
51 changed files with 5711 additions and 1122 deletions

View file

@ -1,4 +1,34 @@
"use strict";
(function ensureDeterministicHelpers(global) {
if (typeof global.deterministicChance === "function" && typeof global.deterministicRange === "function") return;
const clamp01 = v => Math.max(0, Math.min(1, Number(v) || 0));
const keyPart = value => {
if (value == null) return "null";
if (typeof value === "number") return Number.isFinite(value) ? value.toFixed(3) : "nan";
if (typeof value === "string" || typeof value === "boolean") return String(value);
if (typeof value === "object") {
const id = value.familyKey || value.id || value.seed || value.liveToken || value.name;
if (id) return `${value.type || value.constructor?.name || "entity"}:${id}`;
const x = Number.isFinite(value.x) ? value.x.toFixed(1) : "x";
const y = Number.isFinite(value.y) ? value.y.toFixed(1) : "y";
return `${value.type || value.constructor?.name || "entity"}@${x},${y}`;
}
return String(value);
};
const hashUnit = (seed, salt = "") => {
const str = `${seed}:${salt}`;
let h = 2166136261;
for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); }
return ((h >>> 0) % 100000) / 100000;
};
global.deterministicFrame = global.deterministicFrame || ((worldRef = null, hz = 60) => Math.floor((Number(worldRef?.time || 0) || 0) * Math.max(1, Number(hz) || 60) + 1e-6));
global.deterministicUnit = global.deterministicUnit || ((worldRef = null, salt = "", ...parts) => hashUnit(String(worldRef?.worldSeed || "tarinai-world"), [salt, global.deterministicFrame(worldRef, 60), Number(worldRef?._deterministicEpoch || 0) || 0, ...parts.map(keyPart)].join("|")));
global.deterministicRange = global.deterministicRange || ((worldRef = null, salt = "", min = 0, max = 1, ...parts) => (Number(min) || 0) + global.deterministicUnit(worldRef, salt, ...parts) * ((Number(max) || 0) - (Number(min) || 0)));
global.deterministicSigned = global.deterministicSigned || ((worldRef = null, salt = "", ...parts) => global.deterministicUnit(worldRef, salt, ...parts) < 0.5 ? -1 : 1);
global.deterministicChance = global.deterministicChance || ((worldRef = null, salt = "", chance = 0, ...parts) => { const p = clamp01(chance); return p >= 1 || (p > 0 && global.deterministicUnit(worldRef, salt, ...parts) < p); });
global.deterministicAngle = global.deterministicAngle || ((worldRef = null, salt = "", ...parts) => global.deterministicRange(worldRef, salt, 0, Math.PI * 2, ...parts));
})(typeof window !== "undefined" ? window : globalThis);
(function (global) {
const World = global.World;
@ -539,6 +569,61 @@
return true;
},
cancelBirthRitual(actor, options = {}) {
const source = actor || null;
const partnerId = source?.birthPartnerId || null;
const partner = partnerId ? this.liveTarinaiById?.(partnerId) : null;
const participants = [];
if (source) participants.push(source);
if (partner && partner !== source) participants.push(partner);
if (!participants.length) return false;
const reason = options.reason || "繁殖が中断された";
let canceled = false;
for (const t of participants) {
if (!t || t.dead) continue;
if ((t.birthRitualTimer || 0) <= 0.04 && t.state !== "birth_ritual") continue;
t.birthRitualTimer = 0;
t.birthRitualMax = 0;
t.birthPartnerId = null;
t.birthRitualLeader = false;
t.birthRitualRole = 0;
t.postBirthPeaceTimer = Math.max(t.postBirthPeaceTimer || 0, 1.4);
t.behaviorLockTimer = Math.max(t.behaviorLockTimer || 0, 0.25);
if (typeof clearTarinaiBehavior === "function") clearTarinaiBehavior(t, { reason });
if (typeof clearForcedBehaviorQueue === "function") clearForcedBehaviorQueue(t, e => e && (e.id === "approach_mate" || e.id === "birth_ritual"));
if (options.panic && t === source) {
if (t.enterPanic) t.enterPanic({ target: options.target || null, reason, fear: options.fear ?? 0.95, wake: true, cause: options.cause || "birth_ritual_interrupted" });
else t.setActionState?.("panic", { target: options.target || null, reason, wake: true });
t.fearTimer = Math.max(t.fearTimer || 0, options.fear ?? 0.95);
} else {
t.goIdle?.(reason);
t.thought = reason;
}
canceled = true;
}
if (canceled && !options.silentLog && this.time - (this.lastBirthCancelLogAt || -999) > 1.2) {
this.log?.(reason, "birth", { participants });
this.lastBirthCancelLogAt = this.time || 0;
}
return canceled;
},
cancelBirthRitualOnForce(tarinai, force = 0, options = {}) {
if (!tarinai || tarinai.dead) return false;
if ((tarinai.birthRitualTimer || 0) <= 0.04 && tarinai.state !== "birth_ritual") return false;
const amount = Number(force) || 0;
const threshold = Number(options.threshold || 180) || 180;
if (amount < threshold) return false;
return this.cancelBirthRitual(tarinai, {
reason: options.reason || "強い衝撃で繁殖が中断された",
target: options.target || null,
fear: options.fear ?? 1.0,
cause: options.cause || "external_force",
panic: options.panic !== false,
silentLog: !!options.silentLog,
});
},
finishBirthRitual(a, b) {
if (!a || !b || a.dead || b.dead) return null;
const reproductionBlockedByDisease = a.sleepDisease || b.sleepDisease || a.fightDisease || b.fightDisease;
@ -725,10 +810,10 @@
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 (!this.canFightPair(actor, target)) return false;
actor.intimidateTimer = Math.max(actor.intimidateTimer || 0, rand(0.85, 1.35));
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, rand(0.75, 1.25));
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);
actor.adjustRelation(target, -0.05, 0.06, "intimidate");
target.adjustRelation(actor, -0.12, 0.30 * target.personalityProfile().fear, "intimidate");
@ -751,8 +836,8 @@
if (a.fightTimer > 0.04 || b.fightTimer > 0.04 || a.intimidateTimer > 0.04 || b.intimidateTimer > 0.04) return false;
const aAggressiveIntent = a.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
const bAggressiveIntent = b.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
const scoreA = a.energy * 0.38 + a.mood * 0.18 + a.stress * 0.12 + (a.type === "angry" ? 12 : 0) + aAggressiveIntent * 8 + ((typeof b.relationTo === "function" ? (b.relationTo(a.id).fear || 0) : 0) * 0.65) + (a.isZunchiSlave ? -10 : 0) + ((a.fightMochiTimer || 0) > 0.04 ? 9 : 0) + rand(-5, 5);
const scoreB = b.energy * 0.38 + b.mood * 0.18 + b.stress * 0.12 + (b.type === "angry" ? 12 : 0) + bAggressiveIntent * 8 + ((typeof a.relationTo === "function" ? (a.relationTo(b.id).fear || 0) : 0) * 0.65) + (b.isZunchiSlave ? -10 : 0) + ((b.fightMochiTimer || 0) > 0.04 ? 9 : 0) + rand(-5, 5);
const scoreA = a.energy * 0.38 + a.mood * 0.18 + a.stress * 0.12 + (a.type === "angry" ? 12 : 0) + aAggressiveIntent * 8 + ((typeof b.relationTo === "function" ? (b.relationTo(a.id).fear || 0) : 0) * 0.65) + (a.isZunchiSlave ? -10 : 0) + ((a.fightMochiTimer || 0) > 0.04 ? 9 : 0) + deterministicRange(this, "conflict-score-a", -5, 5, a, b);
const scoreB = b.energy * 0.38 + b.mood * 0.18 + b.stress * 0.12 + (b.type === "angry" ? 12 : 0) + bAggressiveIntent * 8 + ((typeof a.relationTo === "function" ? (a.relationTo(b.id).fear || 0) : 0) * 0.65) + (b.isZunchiSlave ? -10 : 0) + ((b.fightMochiTimer || 0) > 0.04 ? 9 : 0) + deterministicRange(this, "conflict-score-b", -5, 5, a, b);
const actor = scoreA >= scoreB ? a : b;
const target = actor === a ? b : a;
if (!this.canFightPair(actor, target)) return false;
@ -761,7 +846,7 @@
const canIntimidate = !actor.lowHealthSprite || !actor.lowHealthSprite();
const battleDrug = (actor.fightMochiTimer || 0) > 0.04 || (target.fightMochiTimer || 0) > 0.04;
const intimidationChance = clamp(0.40 + (battleDrug ? 0.24 : 0) + (target.isZunchiSlave ? 0.18 : 0), 0.24, 0.90);
if (canIntimidate && Math.random() < intimidationChance) {
if (canIntimidate && deterministicChance(this, "conflict-intimidation-choice", intimidationChance, actor, target)) {
return this.startIntimidation(actor, target, actorScore, targetScore);
}
return this.startFight(actor, target, { initiator: actor });
@ -780,13 +865,13 @@
const targetSize = target.effectiveScale ? target.effectiveScale() : (target.scale || 0.28);
const sizeEdge = clamp((actorSize - targetSize) / 0.16, -1, 1);
const aggressionEdge = clamp(actorAggression - targetAggression * 0.55, -1, 1);
const resistance = target.energy * 0.30 + target.mood * 0.16 + targetAggression * 8 + (target.shouldApplyPersonalityBehavior?.("aggression", 1) ? 5 : 0) + rand(-5, 5);
const resistance = target.energy * 0.30 + target.mood * 0.16 + targetAggression * 8 + (target.shouldApplyPersonalityBehavior?.("aggression", 1) ? 5 : 0) + deterministicRange(this, "intimidation-resistance", -5, 5, actor, target);
const successChance = clamp(0.56 + (actorScore - targetScore + fearLoad - resistance) / 82 + aggressionEdge * 0.13 + sizeEdge * 0.16, 0.30, 0.96);
actor.surpriseTimer = Math.max(actor.surpriseTimer, 0.20);
target.surpriseTimer = Math.max(target.surpriseTimer, 0.32);
actor.fightCooldown = CONFIG.fightCooldown + rand(1, 4);
target.fightCooldown = CONFIG.fightCooldown + rand(1, 4);
actor.intimidateTimer = Math.max(actor.intimidateTimer, rand(1.05, 1.65));
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.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" });
if (typeof setBehaviorText === "function") setBehaviorText(actor, { need: "social", subNeed: "conflict", actionId: "intimidate_enemy", actionLabel: "\u5a01\u5687\u3057\u3066\u3044\u308b", reasonText: `${target.name || "\u76f8\u624b"}\u3092\u5a01\u5687\u3057\u3066\u3044\u308b`, target, phase: "perform", source: "behavior" });
@ -796,9 +881,9 @@
color: "rgba(145, 106, 55, 0.70)",
}));
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, rand(0.95, 1.55));
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);
if (Math.random() >= successChance) {
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");
actor.intimidateTimer = 0;
@ -814,13 +899,13 @@
actor.adjustPersonality?.("aggression", 0.018, "after successful intimidation");
target.defeatedById = actor.id;
target.fightWinnerId = actor.id;
target.defeatedTimer = Math.max(target.defeatedTimer, rand(1.8, 2.8));
target.intimidatedTimer = Math.max(target.intimidatedTimer || 0, rand(1.2, 2.0));
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 }); }
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) * rand(26, 54);
target.vy += rand(-18, 18);
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");
if (this.time - Math.max(actor.lastLog, target.lastLog) > 5.5) {
@ -862,12 +947,12 @@
setBehaviorText(a, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: a === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText: reasonA, causeText: causeFor(a, b), target: b, phase: "perform", source: "behavior" });
setBehaviorText(b, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: b === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText: reasonB, causeText: causeFor(b, a), target: a, phase: "perform", source: "behavior" });
}
a.fightTimer = Math.max(a.fightTimer, rand(3.2, 4.8));
b.fightTimer = Math.max(b.fightTimer, rand(3.2, 4.8));
a.nextHeadbutt = this.time + rand(0.08, 0.18);
a.fightTimer = Math.max(a.fightTimer, deterministicRange(this, "fight-start-timer-a", 3.2, 4.8, a, b));
b.fightTimer = Math.max(b.fightTimer, deterministicRange(this, "fight-start-timer-b", 3.2, 4.8, a, b));
a.nextHeadbutt = this.time + deterministicRange(this, "fight-start-headbutt", 0.08, 0.18, a, b);
b.nextHeadbutt = a.nextHeadbutt;
a.fightCooldown = CONFIG.fightCooldown + rand(1, 5);
b.fightCooldown = CONFIG.fightCooldown + rand(1, 5);
a.fightCooldown = CONFIG.fightCooldown + deterministicRange(this, "fight-start-cooldown-a", 1, 5, a, b);
b.fightCooldown = CONFIG.fightCooldown + deterministicRange(this, "fight-start-cooldown-b", 1, 5, a, b);
if (!a.fightTargetIds) a.fightTargetIds = [];
if (!b.fightTargetIds) b.fightTargetIds = [];
if (!a.fightTargetIds.includes(b.id)) a.fightTargetIds.push(b.id);
@ -876,22 +961,24 @@
b.fightTargetIds = b.fightTargetIds.slice(-4);
a.fightTargetId = a.fightTargetIds[0];
b.fightTargetId = b.fightTargetIds[0];
if (a.addStress) a.addStress(rand(8, 18), { threshold: 8 });
if (b.addStress) b.addStress(rand(8, 18), { threshold: 8 });
const damageToA = b.outgoingDamage ? b.outgoingDamage(rand(2, 7)) : rand(2, 7);
const damageToB = a.outgoingDamage ? a.outgoingDamage(rand(2, 7)) : rand(2, 7);
if (a.addStress) a.addStress(deterministicRange(this, "fight-start-stress-a", 8, 18, a, b), { threshold: 8 });
if (b.addStress) b.addStress(deterministicRange(this, "fight-start-stress-b", 8, 18, a, b), { threshold: 8 });
const rawDamageToA = deterministicRange(this, "fight-pulse-damage-a", 2, 7, a, b);
const rawDamageToB = deterministicRange(this, "fight-pulse-damage-b", 2, 7, a, b);
const damageToA = b.outgoingDamage ? b.outgoingDamage(rawDamageToA) : rawDamageToA;
const damageToB = a.outgoingDamage ? a.outgoingDamage(rawDamageToB) : rawDamageToB;
a.damage(damageToA, "\u55a7\u5629");
b.damage(damageToB, "\u55a7\u5629");
this.maybeDefeatFromFightDamage?.(a, b, damageToA);
this.maybeDefeatFromFightDamage?.(b, a, damageToB);
const dx = b.x - a.x, dy = b.y - a.y;
const d = Math.hypot(dx, dy) || 1;
a.vx -= dx / d * rand(32, 62); a.vy -= dy / d * rand(32, 62);
b.vx += dx / d * rand(32, 62); b.vy += dy / d * rand(32, 62);
a.vx -= dx / d * deterministicRange(this, "fight-pulse-knockback-ax", 32, 62, a, b); a.vy -= dy / d * deterministicRange(this, "fight-pulse-knockback-ay", 32, 62, a, b);
b.vx += dx / d * deterministicRange(this, "fight-pulse-knockback-bx", 32, 62, a, b); b.vy += dy / d * deterministicRange(this, "fight-pulse-knockback-by", 32, 62, a, b);
a.surpriseTimer = 0.24; b.surpriseTimer = 0.24;
this.effects.push(new Effect("fight", (a.x + b.x) / 2, (a.y + b.y) / 2 - 8, {
size: rand(12, 20),
life: rand(0.32, 0.52),
size: deterministicRange(this, "fight-pulse-effect-size", 12, 20, a, b),
life: deterministicRange(this, "fight-pulse-effect-life", 0.32, 0.52, a, b),
color: "rgba(116, 73, 38, 0.82)",
}));
audio.fight();