2026-06-28 23:07:40 +09:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
(function (global) {
|
2026-06-29 16:21:58 +09:00
|
|
|
const ImpactCore = global.TarinaiImpactCoreSystem;
|
2026-06-28 23:07:40 +09:00
|
|
|
const PHYSICAL_DAMAGE_SPEED_THRESHOLD = ImpactCore.PHYSICAL_DAMAGE_SPEED_THRESHOLD || 165;
|
|
|
|
|
const physicalDamageFromImpactSpeed = ImpactCore.physicalDamageFromImpactSpeed;
|
|
|
|
|
|
|
|
|
|
function itemDropImpact(worldRef, it) {
|
|
|
|
|
if (!worldRef) return undefined;
|
|
|
|
|
if (!it || it.dead || it.type === "water") return;
|
|
|
|
|
const isStone = it.type === "stone";
|
|
|
|
|
const isGenkotsu = it.type === "genkotsu";
|
|
|
|
|
const dropFallHeight = isGenkotsu ? Math.max(360, (it.r || 88) * 5.8) : 170;
|
|
|
|
|
const dropDuration = Math.max(0.08, Number(it.dropMax || 0.42) || 0.42);
|
|
|
|
|
const impactSpeed = dropFallHeight / dropDuration;
|
|
|
|
|
const radius = Math.max(32, Math.min(220, (it.r || 14) * 1.35 + impactSpeed * 0.055));
|
|
|
|
|
const damage = physicalDamageFromImpactSpeed(impactSpeed, { min: 1.2, max: isGenkotsu ? 58 : 34, scale: 8.6 });
|
|
|
|
|
const push = clamp((impactSpeed - 120) * 0.62, 42, isGenkotsu ? 1040 : 260);
|
|
|
|
|
const reason = isGenkotsu ? "\u3052\u3093\u3053\u3064" : (isStone ? "\u77f3\u306b\u6f70\u3055\u308c\u305f" : (impactSpeed >= PHYSICAL_DAMAGE_SPEED_THRESHOLD ? "\u9ad8\u901f\u3067\u843d\u3061\u3066\u304d\u305f\u9053\u5177\u306b\u885d\u7a81" : "\u843d\u3061\u3066\u304d\u305f\u9053\u5177\u306b\u5f53\u305f\u3063\u305f"));
|
2026-06-29 16:21:58 +09:00
|
|
|
if (isGenkotsu) { worldRef.lastGenkotsuAt = worldRef.time || 0; it.impactFlash = 1; it.lingerTimer = Math.max(it.lingerDuration || 2, 2); worldRef.triggerGroundShake?.(2.0, 10); }
|
2026-06-28 23:07:40 +09:00
|
|
|
let hit = 0;
|
2026-06-29 16:21:58 +09:00
|
|
|
for (const t of worldRef.nearbyTarinai(it.x, it.y, radius + 54)) {
|
|
|
|
|
if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox(t)) continue;
|
2026-06-28 23:07:40 +09:00
|
|
|
const d = Math.max(1, distXY(t.x, t.y, it.x, it.y));
|
|
|
|
|
const p = clamp(1 - d / (radius + t.radius), 0, 1);
|
|
|
|
|
if (p <= 0) continue;
|
|
|
|
|
let nx = (t.x - it.x) / d;
|
|
|
|
|
let ny = (t.y - it.y) / d;
|
|
|
|
|
if (!Number.isFinite(nx) || !Number.isFinite(ny) || Math.abs(nx) + Math.abs(ny) < 0.001) {
|
2026-06-29 16:21:58 +09:00
|
|
|
const a = deterministicAngle(worldRef, "drop-impact-overlap-normal", it, t);
|
2026-06-28 23:07:40 +09:00
|
|
|
nx = Math.cos(a);
|
|
|
|
|
ny = Math.sin(a);
|
|
|
|
|
}
|
|
|
|
|
t.vx += nx * push * (0.35 + p);
|
|
|
|
|
t.vy += ny * push * (0.35 + p) - Math.min(210, push * 0.22) * p;
|
|
|
|
|
const dealtDamage = damage * (0.35 + p * 0.65);
|
2026-07-15 14:44:29 +09:00
|
|
|
if (dealtDamage > 0) t.damage(dealtDamage, reason, { source: it });
|
2026-06-28 23:07:40 +09:00
|
|
|
if (isGenkotsu) {
|
|
|
|
|
t.fearTimer = Math.max(t.fearTimer || 0, 4.0 + p * 2.2);
|
|
|
|
|
if (typeof applyNeedShock === "function") applyNeedShock(t, { safety: 18 + p * 30, health: 8 + p * 15 });
|
|
|
|
|
t.adjustPersonality?.("openness", -(0.020 + p * 0.030), "after being struck by genkotsu.");
|
|
|
|
|
t.thought = "\u3052\u3093\u3053\u3064\u304c\u3053\u308f\u3044";
|
|
|
|
|
} else {
|
|
|
|
|
t.thought = isStone ? "\u77f3\u304c\u843d\u3061\u3066\u304d\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b" : "\u843d\u3061\u3066\u304d\u305f\u9053\u5177\u306b\u5f53\u305f\u3063\u3066\u6df7\u4e71\u3057\u3066\u3044\u308b";
|
|
|
|
|
}
|
|
|
|
|
t.hurtTimer = Math.max(t.hurtTimer, isGenkotsu ? 3.4 : (isStone ? 2.7 : 1.25));
|
|
|
|
|
t.fallTimer = Math.max(t.fallTimer, isGenkotsu ? 0.86 : (isStone ? 0.62 : 0.26));
|
|
|
|
|
t.fallMax = Math.max(t.fallMax || t.fallTimer, t.fallTimer);
|
|
|
|
|
t.fallDir = nx >= 0 ? 1 : -1;
|
|
|
|
|
hit += 1;
|
|
|
|
|
}
|
|
|
|
|
let ballHit = 0;
|
2026-07-03 00:45:22 +09:00
|
|
|
if (isGenkotsu && (worldRef.itemCounts?.ball || worldRef.itemCounts?.balloon)) {
|
2026-06-28 23:07:40 +09:00
|
|
|
const ballRange = radius + Math.max(72, (it.r || 88) * 0.78);
|
2026-07-18 13:06:02 +09:00
|
|
|
const balls = worldRef.nearbyNonGrassItems?.(it.x, it.y, ballRange + 42, true) || worldRef.items || [];
|
2026-06-28 23:07:40 +09:00
|
|
|
for (const ball of balls) {
|
2026-07-03 00:45:22 +09:00
|
|
|
if (!ball || ball.dead || (ball.type !== "ball" && ball.type !== "balloon")) continue;
|
2026-06-28 23:07:40 +09:00
|
|
|
const d = Math.max(1, distXY(ball.x, ball.y, it.x, it.y));
|
|
|
|
|
const p = clamp(1 - d / (ballRange + (ball.r || 18)), 0, 1);
|
|
|
|
|
if (p <= 0) continue;
|
2026-07-03 00:45:22 +09:00
|
|
|
if (ball.type === "balloon") {
|
|
|
|
|
globalThis.TarinaiItemDynamicBallSystem?.popBalloon?.(ball, worldRef, it);
|
|
|
|
|
ballHit += 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-06-28 23:07:40 +09:00
|
|
|
let nx = (ball.x - it.x) / d;
|
|
|
|
|
let ny = (ball.y - it.y) / d;
|
|
|
|
|
if (!Number.isFinite(nx) || !Number.isFinite(ny) || Math.abs(nx) + Math.abs(ny) < 0.001) {
|
2026-06-29 16:21:58 +09:00
|
|
|
const a = deterministicAngle(worldRef, "genkotsu-ball-impact-normal", it, ball);
|
2026-06-28 23:07:40 +09:00
|
|
|
nx = Math.cos(a);
|
|
|
|
|
ny = Math.sin(a);
|
|
|
|
|
}
|
|
|
|
|
const ballPush = clamp(push * (0.44 + p * 0.86), 160, 1080);
|
|
|
|
|
ball.prevX = Number.isFinite(ball.x) ? ball.x : (ball.prevX || 0);
|
|
|
|
|
ball.prevY = Number.isFinite(ball.y) ? ball.y : (ball.prevY || 0);
|
|
|
|
|
ball.vx = clamp((ball.vx || 0) + nx * ballPush, -1180, 1180);
|
|
|
|
|
ball.vy = clamp((ball.vy || 0) + ny * ballPush - Math.min(180, ballPush * 0.12) * p, -1180, 1180);
|
|
|
|
|
ball.spinVelocity = clamp((ball.spinVelocity || 0) + (nx >= 0 ? 1 : -1) * (10 + p * 12), -48, 48);
|
2026-06-29 16:21:58 +09:00
|
|
|
ball.lastPokedAt = worldRef.time || 0;
|
2026-06-28 23:07:40 +09:00
|
|
|
ballHit += 1;
|
2026-06-29 16:21:58 +09:00
|
|
|
worldRef.effects?.push(new Effect("ring", ball.x, ball.y, { size: Math.max(18, (ball.r || 18) * 1.1), life: 0.20, color: "rgba(255,255,255,0.52)" }));
|
2026-06-28 23:07:40 +09:00
|
|
|
}
|
2026-06-29 16:21:58 +09:00
|
|
|
if (ballHit > 0) worldRef.markSpatialDirty?.("genkotsu-ball-impact");
|
2026-06-28 23:07:40 +09:00
|
|
|
}
|
2026-06-29 16:21:58 +09:00
|
|
|
const antHit = damage > 0 ? worldRef.damageAntsInRadius(it.x, it.y, radius + 54, p => damage * (0.35 + p * 0.65), reason, {
|
2026-06-28 23:07:40 +09:00
|
|
|
push: p => push * (0.62 + p * 1.25),
|
2026-07-15 14:44:29 +09:00
|
|
|
source: it,
|
2026-06-28 23:07:40 +09:00
|
|
|
}) : 0;
|
|
|
|
|
if (isGenkotsu) {
|
2026-06-29 16:21:58 +09:00
|
|
|
worldRef.blastZunchiFrom?.(it.x, it.y, radius + 92, 2.75, { limit: 96, effectLimit: 6 });
|
|
|
|
|
worldRef.blastLoosePinsFrom?.(it.x, it.y, radius + 92, it, 2.75, "genkotsu-impact");
|
|
|
|
|
const existingEffects = (worldRef.effects || []).length;
|
2026-06-28 23:07:40 +09:00
|
|
|
const ringCount = existingEffects > 180 ? 2 : 3;
|
|
|
|
|
const burstCount = existingEffects > 180 ? 4 : 7;
|
2026-07-18 13:06:02 +09:00
|
|
|
for (let r = 0; r < ringCount; r++) worldRef.spawnEffect("ring", it.x, it.y, { size: 32 + r * 42, life: 0.32 + r * 0.07, color: r % 2 ? "rgba(255,203,54,0.68)" : "rgba(74,57,42,0.38)" });
|
|
|
|
|
for (let i = 0; i < burstCount; i++) worldRef.spawnEffect("fight", it.x + deterministicRange(worldRef, "genkotsu-impact-effect-x", -38, 38, it, i), it.y + deterministicRange(worldRef, "genkotsu-impact-effect-y", -22, 28, it, i), { size: deterministicRange(worldRef, "genkotsu-impact-effect-size", 12, 22, it, i), life: deterministicRange(worldRef, "genkotsu-impact-effect-life", 0.22, 0.42, it, i), color: "rgba(255,205,46,0.78)" });
|
2026-06-28 23:07:40 +09:00
|
|
|
} else {
|
2026-07-18 13:06:02 +09:00
|
|
|
worldRef.spawnEffect("ring", it.x, it.y, { size: isStone ? 32 : 18, life: 0.34, color: isStone ? "rgba(128,96,58,0.76)" : "rgba(174,128,70,0.52)" });
|
2026-06-28 23:07:40 +09:00
|
|
|
}
|
2026-06-29 16:21:58 +09:00
|
|
|
if (hit > 0 || antHit > 0 || isGenkotsu) worldRef.spawnFallEffect(it.x, it.y + (it.r || 12) * 0.6, isGenkotsu ? 1.32 : (isStone ? 0.9 : 0.45));
|
2026-06-28 23:07:40 +09:00
|
|
|
if (isGenkotsu) audio.genkotsuImpact?.();
|
|
|
|
|
else if (isStone) audio.stoneImpact?.();
|
|
|
|
|
else if (hit > 0 || antHit > 0) audio.poke?.();
|
|
|
|
|
else audio.drop?.();
|
|
|
|
|
if (hit > 0 || antHit > 0) {
|
|
|
|
|
const label = isGenkotsu ? "\u3052\u3093\u3053\u3064" : (isStone ? "\u77f3" : "\u9053\u5177");
|
|
|
|
|
const victims = [];
|
|
|
|
|
if (hit > 0) victims.push(`${hit}\u5339\u306e\u305f\u308a\u306a\u3044`);
|
|
|
|
|
if (antHit > 0) victims.push(`${antHit}\u5339\u306e\u30a2\u30ea`);
|
2026-06-29 16:21:58 +09:00
|
|
|
worldRef.log(`${label}\u304c\u843d\u3061\u3001${victims.join("\u3068")}\u304c\u5dfb\u304d\u8fbc\u307e\u308c\u305f\u3002`, "accident");
|
2026-06-28 23:07:40 +09:00
|
|
|
}
|
2026-06-29 16:21:58 +09:00
|
|
|
|
2026-06-28 23:07:40 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global.TarinaiImpactResponseSystem = Object.freeze({
|
|
|
|
|
itemDropImpact,
|
|
|
|
|
});
|
|
|
|
|
})(typeof window !== "undefined" ? window : globalThis);
|