100 lines
5.4 KiB
JavaScript
100 lines
5.4 KiB
JavaScript
|
|
"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);
|