tarinai/js/item_dynamic_tool_system.js

136 lines
6 KiB
JavaScript
Raw Normal View History

2026-06-26 12:46:32 +09:00
"use strict";
// Layer: entity-runtime/items/dynamic-system
// Owns dynamic tool behavior for fan, magnet, rotator, firecracker, genkotsu,
// and static gate-fence ticking. Item.prototype methods delegate here for
// compatibility; lifecycle dispatch also calls this boundary directly.
(function (global) {
function support() {
return global.TarinaiItemLifecycleRuntimeSupport || {};
}
function updateFirecracker(item, dt, worldRef) {
item.fuseTimer = Math.max(0, (item.fuseTimer ?? 5) - dt);
if (item.fuseTimer <= 0) {
if (worldRef?.explodeFirecracker) worldRef.explodeFirecracker(item);
else item.amount = 0;
}
}
function updateGenkotsu(item, dt) {
item.impactFlash = Math.max(0, (item.impactFlash || 0) - dt);
if (item.dropImpactDone) {
item.lingerTimer = Math.max(0, (item.lingerTimer || 0) - dt);
if ((item.lingerTimer || 0) <= 0) item.amount = 0;
} else {
item.amount = Math.max(item.amount || 0, 100);
}
}
function updateFan(item, dt, worldRef) {
if (!item || item.dead || item.type !== "fan") return false;
item.amount = 999;
if (!worldRef) return false;
const { fanApplyToBody, itemAngleForRuntime } = support();
if (typeof fanApplyToBody !== "function") return false;
let changed = false;
const range = Math.max(190, (item.r || 32) * 7.4);
for (const t of worldRef.nearbyTarinai?.(item.x, item.y, range + 80, true) || []) {
if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox?.(t)) continue;
changed = fanApplyToBody(item, t, dt, worldRef, 1.0) || changed;
}
const kinematicTypes = new Set(["ball", "zunchi", "pushpin", "oshibyo"]);
for (const it of worldRef.nearbyItems?.(item.x, item.y, range + 80, true) || []) {
if (!it || it === item || it.dead || !kinematicTypes.has(it.type)) continue;
changed = fanApplyToBody(item, it, dt, worldRef, it.type === "ball" ? 1.12 : 0.82) || changed;
}
const now = worldRef.time || 0;
if (changed && (item.fanPulseAt || -999) + 0.12 <= now) {
item.fanPulseAt = now;
const angle = typeof itemAngleForRuntime === "function" ? itemAngleForRuntime(item) : (Number(item.angle) || 0);
const x = item.x + Math.cos(angle) * (item.r || 32) * rand(0.8, 2.8);
const y = item.y + Math.sin(angle) * (item.r || 32) * rand(0.8, 2.8) + rand(-10, 10);
worldRef.effects?.push(new Effect("wind", x, y, { vx: Math.cos(angle) * rand(16, 40), vy: Math.sin(angle) * rand(16, 40), size: rand(8, 18), life: rand(0.18, 0.32), color: "rgba(160,210,245,0.28)" }));
}
if (changed) {
worldRef.drawListDirty = true;
worldRef.markSpatialDirty?.("fan-force");
}
return changed;
}
function updateMagnet(item, dt, worldRef) {
if (!item || item.dead || item.type !== "magnet") return false;
item.amount = 999;
if (!worldRef) return false;
const { isMagnetTargetItem, magnetApplyToBody, magnetPolePoint } = support();
if (typeof isMagnetTargetItem !== "function" || typeof magnetApplyToBody !== "function") return false;
let changed = false;
const range = Math.max(170, (item.r || 30) * 7.4);
for (const it of worldRef.nearbyItems?.(item.x, item.y, range + 80, true) || []) {
if (!isMagnetTargetItem(it)) continue;
if (it.type === "pushpin" || it.type === "oshibyo") {
if (it.pinState === "lodged") continue;
}
changed = magnetApplyToBody(item, it, dt, worldRef, it.type === "ball" ? 0.42 : 1.0) || changed;
}
for (const t of worldRef.nearbyTarinai?.(item.x, item.y, range + 80, true) || []) {
if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox?.(t)) continue;
const lodged = t.currentLodgedPin?.() || (worldRef.items || []).find(it => it && isPinType(it.type) && it.pinState === "lodged" && it.pinTargetId === t.id);
if (!lodged) continue;
changed = magnetApplyToBody(item, t, dt, worldRef, 0.55) || changed;
}
const now = worldRef.time || 0;
if (changed && (item.magnetPulseAt || -999) + 0.18 <= now) {
item.magnetPulseAt = now;
const pole = typeof magnetPolePoint === "function" ? magnetPolePoint(item) : { x: item.x, y: item.y };
worldRef.effects?.push(new Effect("ring", pole.x, pole.y, { size: Math.max(12, (item.r || 30) * 0.85), life: 0.16, color: "rgba(120,150,230,0.34)" }));
}
if (changed) {
worldRef.drawListDirty = true;
worldRef.markSpatialDirty?.("magnet-force");
}
return changed;
}
function updateRotator(item, dt, worldRef) {
if (!item || item.dead || item.type !== "rotator") return false;
item.amount = 999;
const speed = Number(item.rotatorSpeed || 0) || 0;
if (!speed || !Number.isFinite(speed)) return false;
const before = Number(item.angle || 0) || 0;
item.angle = typeof normalizedItemAngle === "function" ? normalizedItemAngle(before + speed * dt, 0) : before + speed * dt;
const extent = worldRef?.rotatorExtent?.(item);
if (Number.isFinite(extent)) item.r = Math.max(item.r || 64, Math.min(460, extent));
item.rotatorSpinPhase = (item.rotatorSpinPhase || 0) + Math.abs(speed) * dt;
worldRef.drawListDirty = true;
worldRef.markSpatialDirty?.("rotator-spin");
return true;
}
function updateGateFence(item) {
if (item) item.amount = 999;
return Boolean(item);
}
function update(item, dt, worldRef) {
if (!item || item.dead) return false;
if (item.type === "firecracker") return updateFirecracker(item, dt, worldRef);
if (item.type === "genkotsu") return updateGenkotsu(item, dt, worldRef);
if (item.type === "fan") return updateFan(item, dt, worldRef);
if (item.type === "magnet") return updateMagnet(item, dt, worldRef);
if (item.type === "rotator") return updateRotator(item, dt, worldRef);
if (item.type === "gate_fence") return updateGateFence(item, dt, worldRef);
return false;
}
global.TarinaiItemDynamicToolSystem = Object.freeze({
update,
updateFirecracker,
updateGenkotsu,
updateFan,
updateMagnet,
updateRotator,
updateGateFence,
});
})(typeof window !== "undefined" ? window : globalThis);