86 lines
2.8 KiB
JavaScript
86 lines
2.8 KiB
JavaScript
// ==================== TNT В 3D — GrechkaCraft v3 ====================
|
||
// Взрыв сферический: сносит блоки в радиусе по всем трём осям.
|
||
import { BLOCKS } from '../data/blocks.js';
|
||
import { state } from '../core/state.js';
|
||
import { getBlock, setBlock, removeBlock } from '../world/world-storage3d.js';
|
||
import { playSound } from '../audio/sound-engine.js';
|
||
|
||
export function activateTNT3D(b, fuseSeconds) {
|
||
b.active = true;
|
||
b.fuse = fuseSeconds;
|
||
playSound('fuse');
|
||
}
|
||
|
||
export function explodeTNT3D(b, st) {
|
||
const TILE = st.TILE;
|
||
const cx = b.gx, cy = b.gy, cz = b.gz || 0;
|
||
const radius = 3.2;
|
||
const r2 = radius * radius;
|
||
|
||
// убрать сам блок
|
||
removeBlock(cx, cy, cz);
|
||
|
||
const toRemove = [];
|
||
const r = Math.ceil(radius);
|
||
for (let dx = -r; dx <= r; dx++) {
|
||
for (let dy = -r; dy <= r; dy++) {
|
||
for (let dz = -r; dz <= r; dz++) {
|
||
const d2 = dx * dx + dy * dy + dz * dz;
|
||
if (d2 > r2) continue;
|
||
const blk = getBlock(cx + dx, cy + dy, cz + dz);
|
||
if (!blk) continue;
|
||
const def = BLOCKS[blk.t];
|
||
if (!def) continue;
|
||
if (def.unbreakable) continue;
|
||
// взрывная сила падает с расстоянием
|
||
if (d2 > r2 * 0.55 && blk.t !== 'dirt' && blk.t !== 'sand' && blk.t !== 'grass') continue;
|
||
toRemove.push({ gx: cx + dx, gy: cy + dy, gz: cz + dz, t: blk.t });
|
||
}
|
||
}
|
||
}
|
||
for (const rem of toRemove) removeBlock(rem.gx, rem.gy, rem.gz);
|
||
|
||
// частицы
|
||
const parts = st.parts || (st.parts = []);
|
||
const n = 30;
|
||
for (let i = 0; i < n; i++) {
|
||
const a = Math.random() * Math.PI * 2;
|
||
const p = Math.acos(2 * Math.random() - 1);
|
||
const sp = 60 + Math.random() * 180;
|
||
parts.push({
|
||
x: (cx + 0.5) * TILE,
|
||
y: (cy + 0.5) * TILE,
|
||
z: (cz + 0.5) * TILE,
|
||
vx: Math.sin(p) * Math.cos(a) * sp,
|
||
vy: Math.cos(p) * sp,
|
||
vz: Math.sin(p) * Math.sin(a) * sp,
|
||
life: 0.6 + Math.random() * 0.5,
|
||
c: Math.random() > 0.5 ? '#ff7a1a' : '#f1c40f',
|
||
});
|
||
}
|
||
|
||
// урон мобам и игроку в радиусе
|
||
const dmg = (e) => {
|
||
const ex = (e.x + e.w / 2) / TILE, ey = (e.y + e.h / 2) / TILE, ez = ((e.z || 0) + e.d / 2) / TILE;
|
||
const d = Math.hypot(ex - cx, ey - cy, ez - cz);
|
||
if (d < radius + 1) {
|
||
const f = 1 - d / (radius + 1);
|
||
e.hp -= 14 * f;
|
||
e.vx += (ex - cx) * 220;
|
||
e.vy -= 300 * f;
|
||
e.vz = (e.vz || 0) + (ez - cz) * 220;
|
||
}
|
||
};
|
||
for (const m of st.mobs) dmg(m);
|
||
const p = st.player;
|
||
dmg(p);
|
||
if (p.hp <= 0 && p.invuln <= 0) { p.hp = 0; p.invuln = 1.5; }
|
||
|
||
playSound('explode');
|
||
|
||
// взрывные частицы убрать по таймеру
|
||
st.explosionFlash = 0.4;
|
||
setBlock(cx, cy, cz, 'air');
|
||
void setBlock;
|
||
}
|