SandGame/explosive.js

125 lines
5.2 KiB
JavaScript
Raw Normal View History

2026-10-22 14:05:51 +00:00
import { grid, cols, rows } from './state.js';
import { config } from './config.js';
export function updateExplosive(x, y, cell) {
// Check neighbors for fire
const neighbors = [[0, 1], [0, -1], [1, 0], [-1, 0]];
for (let n of neighbors) {
const nx = x + n[0], ny = y + n[1];
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows && grid[nx][ny]) {
if (grid[nx][ny].type === 'fire') {
explode(x, y);
return;
}
}
}
}
export function explode(startX, startY) {
// BFS to find connected explosives (Cluster Logic)
let queue = [{x: startX, y: startY}];
let visited = new Set([`${startX},${startY}`]);
let cluster = [];
let perimeter = []; // "Skin" of the explosive
// Ensure start is explosive
if (!grid[startX][startY] || grid[startX][startY].type !== 'explosive') return;
let head = 0;
while(head < queue.length) {
let curr = queue[head++];
cluster.push(curr);
let isPerimeter = false;
const neighbors = [[0, 1], [0, -1], [1, 0], [-1, 0]];
for (let n of neighbors) {
const nx = curr.x + n[0];
const ny = curr.y + n[1];
const key = `${nx},${ny}`;
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) {
const neighbor = grid[nx][ny];
if (neighbor && neighbor.type === 'explosive') {
if (!visited.has(key)) {
visited.add(key);
queue.push({x: nx, y: ny});
}
} else {
// Neighbor is not explosive (or is empty), so this is an edge
isPerimeter = true;
}
} else {
// Edge of screen
isPerimeter = true;
}
}
if (isPerimeter) {
perimeter.push(curr);
}
}
// Calculate Force based on Mass
const totalMass = cluster.length;
const forceMultiplier = config.explosive.force + Math.log(totalMass) * 0.1;
const reach = config.explosive.radius;
// Remove Cluster
for (let p of cluster) {
grid[p.x][p.y] = null;
}
// Apply Blast from Perimeter (Skin)
for (let p of perimeter) {
for (let i = -reach; i <= reach; i++) {
for (let j = -reach; j <= reach; j++) {
const nx = p.x + i;
const ny = p.y + j;
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) {
const dist = Math.sqrt(i*i + j*j);
if (dist > 0 && dist <= reach) {
const target = grid[nx][ny];
if (target) {
if (target.type === 'explosive') {
explode(nx, ny); // Trigger separate cluster
} else if (target.type === 'wood' && config.explosive.destroy.wood) {
grid[nx][ny] = { type: 'fire', color: '#FF5500', updated: true };
} else if (target.type === 'water' || target.type === 'acid' || target.type === 'steam') {
if (target.type === 'water' && config.explosive.destroy.water) {
grid[nx][ny] = null;
} else if (target.type === 'acid' && config.explosive.destroy.acid) {
grid[nx][ny] = null;
} else {
if (target.type === 'water' && Math.random() < 0.2) {
target.type = 'steam';
target.color = '#E0FFFF';
}
const angle = Math.atan2(j, i);
const force = (reach - dist + 1) * forceMultiplier * 0.5;
target.vx = (target.vx || 0) + Math.cos(angle) * force;
target.vy = (target.vy || 0) + Math.sin(angle) * force;
target.updated = true;
}
} else if (target.type === 'dirt' && config.explosive.destroy.dirt) {
const angle = Math.atan2(j, i);
const force = (reach - dist + 1) * (forceMultiplier * 0.3);
target.vx = (target.vx || 0) + Math.cos(angle) * force;
target.vy = (target.vy || 0) + Math.sin(angle) * force;
target.updated = true;
} else if (target.type !== 'fire' && config.explosive.destroy[target.type]) {
grid[nx][ny] = null;
}
}
if (Math.random() < 0.05 && !grid[nx][ny]) {
grid[nx][ny] = { type: 'fire', color: '#FF5500', updated: true, lifespan: Math.random() * 10 + 5 };
}
}
}
}
}
}
}