This commit is contained in:
33333-33333 2026-06-30 22:30:37 +09:00
commit 7455d630fc
82 changed files with 2179 additions and 900 deletions

View file

@ -20,6 +20,80 @@
return attacker?.outgoingDamage ? attacker.outgoingDamage(raw) : raw;
}
const TARINAI_KING_FLEE_REASON = "\u305F\u308A\u306A\u3044\u738B\u304C\u6016\u304F\u3066\u9003\u3052\u3066\u3044\u308B\u3002";
function tarinaiIsChampionBreedingPartner(champion, candidate) {
if (!champion || !candidate || champion === candidate) return false;
if (champion.birthPartnerId && champion.birthPartnerId === candidate.id) return true;
if (candidate.birthPartnerId && candidate.birthPartnerId === champion.id) return true;
const championBehavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(champion) : null;
const candidateBehavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(candidate) : null;
const breedingIds = new Set(["approach_mate", "birth_ritual"]);
if (breedingIds.has(championBehavior?.actionId) && champion.target === candidate) return true;
if (breedingIds.has(candidateBehavior?.actionId) && candidate.target === champion) return true;
if ((champion.birthRitualTimer || 0) > 0.04 && champion.target === candidate) return true;
if ((candidate.birthRitualTimer || 0) > 0.04 && candidate.target === champion) return true;
return false;
}
function nearestKingFleeNestBox(tarinai, maxDist = 460) {
const worldRef = tarinai?.world;
if (!tarinai || !worldRef) return null;
const source = typeof worldRef.nearbyItems === "function" ? worldRef.nearbyItems(tarinai.x, tarinai.y, maxDist) : (worldRef.items || []);
let best = null;
let bestScore = Infinity;
for (const item of source || []) {
if (!item || item.dead || item.type !== "nest_box") continue;
const d = distXY(tarinai.x, tarinai.y, item.x, item.y);
if (d > maxDist) continue;
const padding = Math.max(16, (tarinai.radius || 20) * 0.72);
if (typeof worldRef.targetBlockedByObstacle === "function" && worldRef.targetBlockedByObstacle(tarinai, item, padding)) continue;
const occupants = typeof worldRef.nestBoxOccupants === "function" ? worldRef.nestBoxOccupants(item, Infinity) : [];
const capacity = typeof worldRef.nestBoxCapacity === "function" ? worldRef.nestBoxCapacity(item) : 5;
const fullnessPenalty = Math.max(0, occupants.length - capacity + 1) * 18;
const score = d + fullnessPenalty;
if (score < bestScore) {
bestScore = score;
best = item;
}
}
return best;
}
function forceKingFlee(avoider, champion, dt = 0.016) {
if (!avoider || !champion || avoider.dead) return false;
const nest = nearestKingFleeNestBox(avoider);
const target = nest || avoider.panicDestination?.(champion, true) || {
x: avoider.x + (avoider.x - champion.x || 1) * 1.8,
y: avoider.y + (avoider.y - champion.y || 0) * 1.8,
};
const now = avoider.world?.time || 0;
avoider.lastNeedShockBreaker = champion;
avoider.lastPanicCause = "tarinai_champion";
avoider.lastPanicDetail = "\u305F\u308A\u306A\u3044\u738B\u304C\u6016\u3044";
avoider.panicStartedAt = now;
avoider.panicHardStopAt = Math.max(avoider.panicHardStopAt || 0, now + 6.5);
avoider.target = target;
avoider.setActionState?.("panic", { target, reason: TARINAI_KING_FLEE_REASON, wake: true, sleeping: false });
if (typeof setBehaviorText === "function") {
setBehaviorText(avoider, {
need: "safety",
actionId: "panic_escape",
actionLabel: "\u9003\u3052\u3066\u3044\u308B",
reasonText: TARINAI_KING_FLEE_REASON,
target,
phase: "perform",
source: "behavior",
});
}
avoider.thought = TARINAI_KING_FLEE_REASON;
avoider.fearTimer = Math.max(avoider.fearTimer || 0, 1.05 * avoider.personalityProfile().fear);
if (nest && distXY(avoider.x, avoider.y, nest.x, nest.y) <= Math.max(50, (avoider.radius || 20) + (nest.r || 24) + 18)) {
avoider.enterNestBox?.(nest, dt, { forceDisplace: true, reason: TARINAI_KING_FLEE_REASON });
}
return true;
}
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
interactWithOthers(dt) {
let closeCount = 0;
@ -41,29 +115,33 @@
const champion = this.isTarinaiChampion ? this : (o.isTarinaiChampion ? o : null);
if (champion && this.isTarinaiChampion !== o.isTarinaiChampion) {
const avoider = champion === this ? o : this;
const nx = (avoider.x - champion.x) / d;
const ny = (avoider.y - champion.y) / d;
const avoidRange = Math.max(92, selfRadius + otherRadius + 34);
const urgency = clamp(1 - d / avoidRange, 0, 1);
const push = dt * (64 + urgency * 260);
avoider.vx += nx * push;
avoider.vy += ny * push;
if (d <= selfRadius + otherRadius + 14) {
avoider.vx += nx * dt * 190;
avoider.vy += ny * dt * 190;
champion.vx -= nx * dt * 8;
champion.vy -= ny * dt * 8;
avoider.surpriseTimer = Math.max(avoider.surpriseTimer || 0, 0.20);
avoider.fearTimer = Math.max(avoider.fearTimer || 0, 0.32 * avoider.personalityProfile().fear);
if (typeof applyNeedShock === "function") applyNeedShock(avoider, { safety: dt * 26, social: dt * 5 }, champion);
if (this.world.relationNotice?.(avoider.id, champion.id, "tarinai-champion-avoid", 18)) {
this.world.log(`${avoider.name}はたりない王者の${champion.name}にぶつかりそうになって避けた。`, "relation", { participants: [avoider, champion], eventType: "avoid_relationship", sound: false });
if (tarinaiIsChampionBreedingPartner(champion, avoider)) {
avoider.adjustRelation?.(champion, dt * 0.035, -dt * 0.06, "tarinai_champion_partner");
if (this !== champion) continue;
} else {
const nx = (avoider.x - champion.x) / d;
const ny = (avoider.y - champion.y) / d;
const avoidRange = Math.max(92, selfRadius + otherRadius + 34);
const urgency = clamp(1 - d / avoidRange, 0, 1);
const push = dt * (64 + urgency * 260);
avoider.vx += nx * push;
avoider.vy += ny * push;
if (d <= selfRadius + otherRadius + 14) {
avoider.vx += nx * dt * 190;
avoider.vy += ny * dt * 190;
champion.vx -= nx * dt * 8;
champion.vy -= ny * dt * 8;
avoider.surpriseTimer = Math.max(avoider.surpriseTimer || 0, 0.20);
avoider.fearTimer = Math.max(avoider.fearTimer || 0, 0.32 * avoider.personalityProfile().fear);
if (typeof applyNeedShock === "function") applyNeedShock(avoider, { safety: dt * 26, social: dt * 5 }, champion);
if (this.world.relationNotice?.(avoider.id, champion.id, "tarinai-champion-avoid", 18)) {
this.world.log(`${avoider.name}\u306F\u305F\u308A\u306A\u3044\u738B\u8005\u306E${champion.name}\u306B\u3076\u3064\u304B\u308A\u305D\u3046\u306B\u306A\u3063\u3066\u907F\u3051\u305F\u3002`, "relation", { participants: [avoider, champion], eventType: "avoid_relationship", sound: false });
}
}
forceKingFlee(avoider, champion, dt);
avoider.adjustRelation?.(champion, -dt * 0.025, dt * 0.18, "tarinai_champion_avoid");
if (this !== champion) continue;
}
avoider.lastNeedShockBreaker = champion;
avoider.thought = `${champion.name || "相手"}はたりない王者だ`;
avoider.adjustRelation?.(champion, -dt * 0.025, dt * 0.18, "tarinai_champion_avoid");
if (this !== champion) continue;
}
const fightMochiContact = this.world.canFightPair?.(this, o) && (this.hasFightMochiEffect() || o.hasFightMochiEffect());
@ -210,7 +288,7 @@
},
move(dt) {
if (this.freezeSleepDiseaseMotion(dt)) return;
if (!(this.burning || (this.burnTimer || 0) > 0.02) && this.freezeSleepDiseaseMotion(dt)) return;
let ax = 0, ay = 0;
const itemSpeedMul = this.itemSpeedMultiplier ? this.itemSpeedMultiplier() : 1;
const baseSpeed = 46.5 * 1.5 * this.trait.speed * itemSpeedMul * (this.zunchiDisease ? 0.5 : 1) * (this.energy < 20 ? 0.42 : 1) * (this.fallTimer > 0 ? 0.35 : 1);
@ -349,6 +427,24 @@
return;
}
const burningNow = Boolean(this.burning || (this.burnTimer || 0) > 0.02);
if (burningNow) {
const burnText = globalThis.TarinaiItemDynamicToolSystem?.fireBurningText || "\u71c3\u3048\u3066\u9003\u3052\u307e\u3069\u3063\u3066\u3044\u308b\u3002";
this.state = "panic";
this.target = null;
this.lastPanicCause = "burning";
this.lastPanicDetail = burnText;
this.thought = burnText;
this.burnTurnTimer = Math.max(0, Number(this.burnTurnTimer || 0) - dt);
if (this.burnTurnTimer <= 0) {
this.burnWanderAngle = deterministicAngle(this.world, "tarinai-burn-run-angle", this, Math.floor((this.world?.time || 0) * 5));
this.burnTurnTimer = deterministicRange(this.world, "tarinai-burn-run-turn", 0.10, 0.34, this);
} else {
this.burnWanderAngle += deterministicRange(this.world, "tarinai-burn-run-jitter", -8.0, 8.0, this) * dt;
}
ax += Math.cos(this.burnWanderAngle || this.wanderAngle || 0) * baseSpeed * 3.15;
ay += Math.sin(this.burnWanderAngle || this.wanderAngle || 0) * baseSpeed * 3.15;
} else {
if (this.state === "panic") {
const targetDist = this.target && !this.target.dead ? distXY(this.x, this.y, this.target.x, this.target.y) : Infinity;
const speed = Math.hypot(this.vx, this.vy);
@ -417,7 +513,15 @@
return;
}
}
if (this.state === "panic" && this.lastPanicCause === "tarinai_champion" && this.target?.type === "nest_box") {
const nestDist = distXY(this.x, this.y, this.target.x, this.target.y);
if (nestDist <= Math.max(50, (this.radius || 20) + (this.target.r || 24) + 18)) {
this.enterNestBox?.(this.target, dt, { forceDisplace: true, reason: TARINAI_KING_FLEE_REASON });
return;
}
}
let sign = (this.state === "panic" || this.state === "cursor_enemy") ? -1 : 1;
if (this.state === "panic" && this.lastPanicCause === "tarinai_champion" && this.target?.type === "nest_box") sign = 1;
if (this.target?.detour) sign = 1;
let strength = 1;
if (this.state === "follow_parent") {
@ -473,6 +577,7 @@
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.34;
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.34;
}
}
this.vx += ax * dt;
this.vy += ay * dt;
@ -481,7 +586,7 @@
this.vx *= Math.pow(moveFriction, dt * 8);
this.vy *= Math.pow(moveFriction, dt * 8);
const maxV = (this.state === "panic" ? 116 : (this.state === "zunchi_sick" ? 54 : (this.state === "fight_sick" ? 68 : 76))) * Math.max(1, itemSpeedMul || 1) * (this.geneticStatRatio ? this.geneticStatRatio("speed") : 1) * (iceGround ? 1.35 : 1);
const maxV = (burningNow ? 168 : (this.state === "panic" ? 116 : (this.state === "zunchi_sick" ? 54 : (this.state === "fight_sick" ? 68 : 76)))) * Math.max(1, itemSpeedMul || 1) * (this.geneticStatRatio ? this.geneticStatRatio("speed") : 1) * (iceGround ? 1.35 : 1);
const v = Math.hypot(this.vx, this.vy);
if (v > maxV) {
this.vx = this.vx / v * maxV;
@ -525,12 +630,12 @@
temperatureDeathReason() {
const status = this.temperatureStatus || this.world?.temperatureStatusFor?.(this.feltTemperature ?? this.tempComfort ?? (CONFIG.standardTemperature ?? 15), this) || null;
const recent = (this.world?.time || 0) - Number(this.lastTemperatureDamageAt || -999) <= 36;
if (recent && status && (status.harmExcess || 0) > 0) return status.direction === "cold" ? "凍結" : "熱中症";
if (recent && status && (status.harmExcess || 0) > 0) return status.direction === "cold" ? "\u51CD\u7D50" : "\u71B1\u4E2D\u75C7";
return "";
},
abandonUnreachableGrassBed(bed, reason = "辿りつけないので放棄した") {
abandonUnreachableGrassBed(bed, reason = "\u8FBF\u308A\u3064\u3051\u306A\u3044\u306E\u3067\u653E\u68C4\u3057\u305F") {
if (!bed || bed.dead || bed.type !== "grass_bed") return false;
bed.ownerId = "";
bed.unreachableUntil = 0;