2026-10-22 14:05:51 +00:00
|
|
|
import { grid, cols, rows } from './state.js';
|
|
|
|
|
import { config } from './config.js';
|
2026-10-22 14:45:19 +00:00
|
|
|
import { createCell } from './physics.js';
|
|
|
|
|
|
|
|
|
|
let explosionQueue = [];
|
|
|
|
|
let isProcessingExplosions = false;
|
2026-10-22 14:05:51 +00:00
|
|
|
|
|
|
|
|
export function updateExplosive(x, y, cell) {
|
2026-10-22 14:45:19 +00:00
|
|
|
const dxs = [0, 0, -1, 1];
|
|
|
|
|
const dys = [1, -1, 0, 0];
|
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
|
|
|
const nx = x + dxs[i];
|
|
|
|
|
const ny = y + dys[i];
|
2026-10-22 14:05:51 +00:00
|
|
|
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows && grid[nx][ny]) {
|
2026-10-22 14:45:19 +00:00
|
|
|
if (grid[nx][ny].type === 'fire' || grid[nx][ny].type === 'lava') {
|
2026-10-22 14:05:51 +00:00
|
|
|
explode(x, y);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function explode(startX, startY) {
|
|
|
|
|
if (!grid[startX][startY] || grid[startX][startY].type !== 'explosive') return;
|
|
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
explosionQueue.push({ x: startX, y: startY });
|
|
|
|
|
if (isProcessingExplosions) return;
|
|
|
|
|
|
|
|
|
|
isProcessingExplosions = true;
|
|
|
|
|
|
|
|
|
|
while (explosionQueue.length > 0) {
|
|
|
|
|
const origin = explosionQueue.shift();
|
|
|
|
|
processSingleExplosion(origin.x, origin.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isProcessingExplosions = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function processSingleExplosion(startX, startY) {
|
|
|
|
|
if (startX < 0 || startX >= cols || startY < 0 || startY >= rows) return;
|
|
|
|
|
|
|
|
|
|
// BFS to find contiguous cluster of TNT
|
|
|
|
|
const queue = [{ x: startX, y: startY }];
|
|
|
|
|
const cluster = [];
|
|
|
|
|
const visited = new Uint8Array(cols * rows);
|
|
|
|
|
visited[startY * cols + startX] = 1;
|
|
|
|
|
|
2026-10-22 14:05:51 +00:00
|
|
|
let head = 0;
|
2026-10-22 14:45:19 +00:00
|
|
|
let minX = startX, maxX = startX, minY = startY, maxY = startY;
|
|
|
|
|
|
|
|
|
|
while (head < queue.length) {
|
|
|
|
|
const curr = queue[head++];
|
2026-10-22 14:05:51 +00:00
|
|
|
cluster.push(curr);
|
2026-10-22 14:45:19 +00:00
|
|
|
|
|
|
|
|
if (curr.x < minX) minX = curr.x;
|
|
|
|
|
if (curr.x > maxX) maxX = curr.x;
|
|
|
|
|
if (curr.y < minY) minY = curr.y;
|
|
|
|
|
if (curr.y > maxY) maxY = curr.y;
|
|
|
|
|
|
|
|
|
|
const dxs = [0, 0, -1, 1];
|
|
|
|
|
const dys = [1, -1, 0, 0];
|
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
|
|
|
const nx = curr.x + dxs[i];
|
|
|
|
|
const ny = curr.y + dys[i];
|
2026-10-22 14:05:51 +00:00
|
|
|
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) {
|
2026-10-22 14:45:19 +00:00
|
|
|
const idx = ny * cols + nx;
|
|
|
|
|
if (!visited[idx]) {
|
|
|
|
|
visited[idx] = 1;
|
|
|
|
|
if (grid[nx][ny] && grid[nx][ny].type === 'explosive') {
|
|
|
|
|
queue.push({ x: nx, y: ny });
|
2026-10-22 14:05:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
// Clear the TNT cluster
|
|
|
|
|
for (let i = 0; i < cluster.length; i++) {
|
|
|
|
|
const p = cluster[i];
|
2026-10-22 14:05:51 +00:00
|
|
|
grid[p.x][p.y] = null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-22 14:45:19 +00:00
|
|
|
const reach = Math.round(config.explosive.radius);
|
|
|
|
|
const totalMass = cluster.length;
|
|
|
|
|
const forceMultiplier = config.explosive.force + Math.log(totalMass + 1) * 0.15;
|
|
|
|
|
|
|
|
|
|
// Center of the explosion
|
|
|
|
|
const centerX = Math.round((minX + maxX) / 2);
|
|
|
|
|
const centerY = Math.round((minY + maxY) / 2);
|
|
|
|
|
|
|
|
|
|
const blastMinX = Math.max(0, minX - reach);
|
|
|
|
|
const blastMaxX = Math.min(cols - 1, maxX + reach);
|
|
|
|
|
const blastMinY = Math.max(0, minY - reach);
|
|
|
|
|
const blastMaxY = Math.min(rows - 1, maxY + reach);
|
|
|
|
|
|
|
|
|
|
// Apply blast wave across the combined bounding area
|
|
|
|
|
for (let ny = blastMinY; ny <= blastMaxY; ny++) {
|
|
|
|
|
for (let nx = blastMinX; nx <= blastMaxX; nx++) {
|
|
|
|
|
// Find distance to closest cluster block or center
|
|
|
|
|
const dx = nx - centerX;
|
|
|
|
|
const dy = ny - centerY;
|
|
|
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
const effectiveReach = reach + Math.min(10, Math.sqrt(totalMass));
|
|
|
|
|
|
|
|
|
|
if (dist <= effectiveReach) {
|
|
|
|
|
const target = grid[nx][ny];
|
|
|
|
|
if (target) {
|
|
|
|
|
const tt = target.type;
|
|
|
|
|
if (tt === 'explosive') {
|
|
|
|
|
// Queue chain reaction
|
|
|
|
|
explosionQueue.push({ x: nx, y: ny });
|
|
|
|
|
} else if (config.explosive.destroy[tt]) {
|
|
|
|
|
if (tt === 'wood' || tt === 'plant' || tt === 'grass') {
|
|
|
|
|
grid[nx][ny] = createCell(Math.random() < 0.5 ? 'fire' : 'smoke');
|
|
|
|
|
} else if (tt === 'dirt' || tt === 'sand') {
|
|
|
|
|
// Fling granular particles outward with blast force
|
|
|
|
|
const angle = Math.atan2(dy, dx);
|
|
|
|
|
const force = (effectiveReach - dist + 1) * forceMultiplier * 0.4;
|
|
|
|
|
target.vx = (target.vx || 0) + Math.cos(angle) * force;
|
|
|
|
|
target.vy = (target.vy || 0) + Math.sin(angle) * force - 1.5;
|
|
|
|
|
} else {
|
|
|
|
|
grid[nx][ny] = null;
|
2026-10-22 14:05:51 +00:00
|
|
|
}
|
2026-10-22 14:45:19 +00:00
|
|
|
} else if (tt === 'water' || tt === 'acid' || tt === 'oil') {
|
|
|
|
|
if (tt === 'water' && Math.random() < 0.35) {
|
|
|
|
|
grid[nx][ny] = createCell('steam');
|
|
|
|
|
} else if (tt === 'oil' && Math.random() < 0.5) {
|
|
|
|
|
grid[nx][ny] = createCell('fire');
|
|
|
|
|
} else {
|
|
|
|
|
const angle = Math.atan2(dy, dx);
|
|
|
|
|
const force = (effectiveReach - dist + 1) * forceMultiplier * 0.5;
|
|
|
|
|
target.vx = (target.vx || 0) + Math.cos(angle) * force;
|
|
|
|
|
target.vy = (target.vy || 0) + Math.sin(angle) * force - 1.0;
|
2026-10-22 14:05:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-22 14:45:19 +00:00
|
|
|
} else if (Math.random() < 0.08 && !grid[nx][ny]) {
|
|
|
|
|
// Spawn fire or smoke in empty space within blast core
|
|
|
|
|
grid[nx][ny] = createCell(Math.random() < 0.4 ? 'fire' : 'smoke');
|
2026-10-22 14:05:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|