136 lines
6 KiB
JavaScript
136 lines
6 KiB
JavaScript
|
|
"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);
|