tarinai/js/item_dynamic_zunchi_system.js

100 lines
5.4 KiB
JavaScript
Raw Normal View History

2026-06-26 12:46:32 +09:00
"use strict";
// Layer: entity-runtime/items/dynamic-system
// Owns zunchi item motion, high-speed collision, and lifecycle stage ticking.
// Prototype methods remain as compatibility facades.
(function (global) {
function updateMotion(item, dt, worldRef) {
if (!item || item.dead || item.type !== "zunchi") return false;
const speed = Math.hypot(item.vx || 0, item.vy || 0);
if (speed < 0.08) { item.vx = 0; item.vy = 0; return true; }
if (global.TarinaiPhysics?.applyKinematicItemMotion) {
global.TarinaiPhysics.applyKinematicItemMotion(item, worldRef, dt, { bounce: 0.34, frictionBase: 0.68, frictionRate: 2.4, spin: false, stopSpeed: 0.08 });
} else {
item.prevX = item.x;
item.prevY = item.y;
item.x += (item.vx || 0) * dt;
item.y += (item.vy || 0) * dt;
const p = Math.max(28, CONFIG.worldPadding || 30);
if (item.x < p) { item.x = p; item.vx = Math.abs(item.vx || 0) * 0.34; }
if (item.x > worldRef.w - p) { item.x = worldRef.w - p; item.vx = -Math.abs(item.vx || 0) * 0.34; }
if (item.y < p) { item.y = p; item.vy = Math.abs(item.vy || 0) * 0.34; }
if (item.y > worldRef.h - p) { item.y = worldRef.h - p; item.vy = -Math.abs(item.vy || 0) * 0.34; }
const slow = Math.pow(0.68, dt * 2.4);
item.vx *= slow;
item.vy *= slow;
worldRef.drawListDirty = true;
}
resolveHighSpeedTarinaiCollision(item, dt, worldRef, speed);
item.spin = (item.spin || 0) + speed * dt / Math.max(8, item.r || 14);
return true;
}
function resolveHighSpeedTarinaiCollision(item, dt, worldRef, speed = 0) {
if (!worldRef || speed < 140 || (item.amount || 0) <= 0) return;
const search = Math.max(38, (item.r || 14) + speed * Math.max(dt || 0.016, 0.016) + 18);
for (const t of worldRef.nearbyTarinai?.(item.x, item.y, search, true) || []) {
if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox?.(t)) continue;
const d = distXY(item.x, item.y, t.x, t.y);
if (d > (t.radius || 20) * 0.72 + (item.r || 14)) continue;
const now = worldRef.time || 0;
if ((item.lastHitTarinaiAt || {})[t.id] && item.lastHitTarinaiAt[t.id] + 0.55 > now) continue;
item.lastHitTarinaiAt = item.lastHitTarinaiAt || {};
item.lastHitTarinaiAt[t.id] = now;
const nx = speed > 0 ? (item.vx || 1) / speed : rand(-1, 1);
const ny = speed > 0 ? (item.vy || 0) / speed : rand(-1, 1);
const impulse = clamp(speed * 1.15, 150, 620);
t.vx = (t.vx || 0) + nx * impulse + rand(-20, 20);
t.vy = (t.vy || 0) + ny * impulse * 0.72 + rand(-18, 10);
t.fallTimer = Math.max(t.fallTimer || 0, 0.85);
t.fallMax = Math.max(t.fallMax || 0.85, t.fallTimer);
t.fallDir = nx < 0 ? -1 : 1;
if (t.enterPanic) t.enterPanic({ threat: item, reason: "\u9ad8\u901f\u305a\u3093\u3061\u306b\u3076\u3064\u304b\u3063\u3066\u5439\u304d\u98db\u3093\u3067\u3044\u308b", fear: 0.55, stress: 7, stressDuration: 3.2, surpriseTimer: 0.5, cause: "fast_zunchi_hit" });
else {
t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.5);
t.fearTimer = Math.max(t.fearTimer || 0, 0.55);
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination(item) : null, reason: "\u9ad8\u901f\u305a\u3093\u3061\u306b\u3076\u3064\u304b\u3063\u3066\u5439\u304d\u98db\u3093\u3067\u3044\u308b", wake: true });
if (t.addStress) t.addStress(7, { threshold: 8, duration: 3.2 });
}
worldRef.spawnFallEffect?.(t.x, t.y + (t.radius || 20) * 0.45, 0.65);
worldRef.effects?.push(new Effect("zunchi_miasma", item.x, item.y - 4, { size: 14, life: 0.38, color: "rgba(77,92,42,0.36)" }));
item.vx *= -0.18;
item.vy *= -0.18;
break;
}
}
function updateLifecycle(item, dt, worldRef) {
if (!item || item.dead || item.type !== "zunchi") return false;
const rainy = worldRef.weather === "light_rain";
const rainBoost = rainy ? 1.28 : 1;
const decayBoost = rainy ? 1.35 : 1;
let waterBoost = 0;
const nearby = worldRef.nearbyItems ? worldRef.nearbyItems(item.x, item.y, 84) : (worldRef.items || []);
for (const it of nearby) {
if (it.type !== "water") continue;
waterBoost += clamp(1 - distXY(item.x, item.y, it.x, it.y) / 80, 0, 1);
}
item.stageTimer += dt * 0.70 * (rainBoost + waterBoost * 0.65);
const decayDt = (item.spawnGrace || 0) > 0 ? 0 : dt;
if (item.stage === "fresh") {
item.freshness = clamp(1 - item.stageTimer / 110, 0, 1);
item.amount -= decayDt * 0.018 * decayBoost;
if (item.stageTimer > 110) { item.stage = "dry"; item.stageTimer = 0; }
} else if (item.stage === "dry") {
item.freshness = clamp(0.55 - item.stageTimer / 220, 0.20, 0.55);
item.amount -= decayDt * 0.032 * decayBoost;
if (item.stageTimer > 150) { item.stage = "decomposing"; item.stageTimer = 0; item.fertility = 0.35; }
} else if (item.stage === "decomposing") {
item.fertility = clamp(item.fertility + dt * 0.015, 0, 1);
item.amount -= decayDt * 0.046 * decayBoost;
if (item.stageTimer > 190) { item.stage = "fertile_soil"; item.stageTimer = 0; item.amount = Math.min(item.amount, 140); }
} else if (item.stage === "fertile_soil") {
item.fertility = clamp(1 - item.stageTimer / 360, 0, 1);
item.amount -= decayDt * 0.090 * decayBoost;
}
return true;
}
global.TarinaiItemDynamicZunchiSystem = Object.freeze({ updateMotion, resolveHighSpeedTarinaiCollision, updateLifecycle });
})(typeof window !== "undefined" ? window : globalThis);