SandGame/explosive.js

143 lines
No EOL
5.5 KiB
JavaScript

import { grid, cols, rows } from './state.js';
import { config } from './config.js';
import { createCell } from './physics.js';
let explosionQueue = [];
let isProcessingExplosions = false;
export function updateExplosive(x, y, cell) {
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];
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows && grid[nx][ny]) {
if (grid[nx][ny].type === 'fire' || grid[nx][ny].type === 'lava') {
explode(x, y);
return;
}
}
}
}
export function explode(startX, startY) {
if (!grid[startX][startY] || grid[startX][startY].type !== 'explosive') return;
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;
let head = 0;
let minX = startX, maxX = startX, minY = startY, maxY = startY;
while (head < queue.length) {
const curr = queue[head++];
cluster.push(curr);
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];
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) {
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 });
}
}
}
}
}
// Clear the TNT cluster
for (let i = 0; i < cluster.length; i++) {
const p = cluster[i];
grid[p.x][p.y] = null;
}
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;
}
} 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;
}
}
} 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');
}
}
}
}
}