"use strict"; // Layer: entity-runtime/items/dynamic-system // Owns zunchi item motion, high-speed collision, and lifecycle stage ticking. // Prototype methods delegate here. (function (global) { function updateMotion(item, dt, worldRef) { if (!item || item.dead || item.type !== "zunchi") return false; if (item.playerHeld || item._heldByPlayer) { if (item.achievementBurstOwnerId) item.achievementBurstNeverStopped = false; item.prevX = item.x; item.prevY = item.y; return true; } const speed = Math.hypot(item.vx || 0, item.vy || 0); if (speed < 0.08) { if (item.achievementBurstOwnerId) item.achievementBurstNeverStopped = false; item.vx = 0; item.vy = 0; global.TarinaiItemDynamicBallSystem.resolveObstacleCollisions(item, dt, worldRef); 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; } global.TarinaiItemDynamicBallSystem.resolveObstacleCollisions(item, dt, worldRef); const collisionSpeed = Math.hypot(item.vx || 0, item.vy || 0); if (collisionSpeed < 0.08 && item.achievementBurstOwnerId) item.achievementBurstNeverStopped = false; resolveHighSpeedTarinaiCollision(item, dt, worldRef, collisionSpeed); item.spin = (item.spin || 0) + collisionSpeed * 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); if (speed >= 185) { const damage = Math.min(18, Math.max(1, (speed - 185) / 22)); worldRef.applyImpactDamage?.(t, damage, "\u885d\u7a81", { source: item }) || t.damage?.(damage, "\u885d\u7a81"); if (t.dead && item.achievementBurstOwnerId === t.id && item.achievementBurstNeverStopped !== false) { globalThis.TarinaiAchievements?.recordSelfZunchiDeath?.({ world: worldRef, tarinai: t, zunchi: item, speed }); } } 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 updateBurningLifecycle(item, dt, worldRef) { if (!item || item.dead || item.type !== "zunchi" || !(item.burning || (item.burnTimer || 0) > 0.02)) return false; item.burning = true; item.burnTimer = Math.max(0, Number(item.burnTimer || 0) - dt); item.spawnGrace = 0; item.freshness = clamp((item.freshness ?? 1) - dt * 0.62, 0, 1); item.amount = Math.max(0, Number(item.amount || 0) - dt * 30); item.stageTimer = Math.max(0, Number(item.stageTimer || 0) + dt * 1.8); if (item.stage === "fresh" && item.freshness < 0.45) { item.stage = "dry"; item.stageTimer = 0; } if (item.stage === "dry" && item.freshness < 0.18) { item.stage = "decomposing"; item.stageTimer = 0; item.fertility = Math.max(item.fertility || 0, 0.18); } if (worldRef?.effects && typeof Effect === "function" && global.deterministicChance(worldRef, "zunchi-fire-effect", dt * 1.15, item)) { worldRef.effects.push(new Effect("flame", item.x + deterministicRange(worldRef, "zunchi-fire-x", -5, 5, item), item.y - Math.max(4, (item.r || 14) * 0.25), { vx: deterministicRange(worldRef, "zunchi-fire-vx", -3, 3, item), vy: deterministicRange(worldRef, "zunchi-fire-vy", -10, -4, item), size: deterministicRange(worldRef, "zunchi-fire-size", 6, 12, item), life: deterministicRange(worldRef, "zunchi-fire-life", 0.18, 0.34, item), color: "rgba(236,46,24,0.72)", })); } if (item.burnTimer <= 0.01 || (item.amount || 0) <= 0.5) { item.burning = false; item.burnTimer = 0; item.amount = 0; worldRef?.effects?.push(new Effect("zunchi_miasma", item.x, item.y - 4, { size: Math.max(13, (item.r || 15) * 1.25), life: 0.42, color: "rgba(64,44,34,0.44)" })); } worldRef?.markTerrainDirtyAt?.(item.x, item.y, Math.max(item.r || 15, 28), "zunchi-burning"); worldRef?.markItemBucketsDirty?.("zunchi-burning"); worldRef?.markSpatialDirty?.("zunchi-burning"); worldRef.drawListDirty = true; return true; } function updateLifecycle(item, dt, worldRef) { if (!item || item.dead || item.type !== "zunchi") return false; updateBurningLifecycle(item, dt, worldRef); const rainy = worldRef.weather === "light_rain"; const rainBoost = rainy ? 1.28 : 1; const groundId = worldRef?.groundType || "soil"; const stageMul = Math.max(0.1, Number(global.TarinaiGround.zunchiStageMultiplier(groundId) || 1) || 1); const groundDecayMul = Math.max(0.1, Number(global.TarinaiGround.zunchiDecayMultiplier(groundId) || 1) || 1); const decayBoost = (rainy ? 1.35 : 1) * groundDecayMul; 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 * stageMul * (rainBoost + waterBoost * 0.65); const decayDt = ((item.spawnGrace || 0) > 0 && groundDecayMul <= 1.01) ? 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; } if ((item.amount || 0) <= 0.5) { item.amount = 0; worldRef?.effects?.push(new Effect("zunchi_miasma", item.x, item.y - 4, { size: Math.max(12, (item.r || 15) * 1.15), life: 0.34, color: "rgba(64,44,34,0.34)" })); worldRef?.markTerrainDirtyAt?.(item.x, item.y, Math.max(item.r || 15, 28), "zunchi-decay-out"); worldRef?.markItemBucketsDirty?.("zunchi-decay-out"); worldRef?.markSpatialDirty?.("zunchi-decay-out"); if (worldRef) worldRef.drawListDirty = true; } return true; } global.TarinaiItemDynamicZunchiSystem = Object.freeze({ updateMotion, updateLifecycle }); })(typeof window !== "undefined" ? window : globalThis);