napalm flowing logic fixes. soot effect added. enhanced explosions effects.
This commit is contained in:
1 parent
63caf5875c
commit
8d5ab422cb
3 files changed
+114
-39
No files matched your search
+44
-19
@@ -738,14 +738,17 @@ function tryEmitParticle(x, y, source) {
|
||||
|
||||
if (canFlowDL || canFlowDR || canSpillL || canSpillR) return p;
|
||||
|
||||
const blocking = napalmParticles.find(np => np.alive && np.x === p.x && np.y === p.y);
|
||||
if (blocking) {
|
||||
for (let y = p.y; y >= 0; y--) {
|
||||
const occupied = napalmParticles.some(np => np.alive && np.x === p.x && np.y === p.y) || hasFireAt(p.x, p.y);
|
||||
if (occupied) {
|
||||
for (let y = p.y - 1; y >= 0; y--) {
|
||||
const np = napalmParticles.find(n => n.alive && n.x === p.x && n.y === y);
|
||||
if (np) np.y--;
|
||||
else break;
|
||||
}
|
||||
return p;
|
||||
if (!isTerrain(terrain, p.x, p.y - 1) && !napalmParticles.some(np => np.alive && np.x === p.x && np.y === p.y - 1)) {
|
||||
p.y--;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -799,16 +802,28 @@ function updateNapalm(dt) {
|
||||
fireCells = fireCells.filter(f => f.timeLeft > 0);
|
||||
for (let f of deadFireCells) {
|
||||
clipTerrain(terrain, (ctx) => drawRect(ctx, f.x, f.y + 1, 1, 1, ctx.color));
|
||||
for (let dx = -2; dx <= 2; dx++) {
|
||||
for (let dy = 0; dy <= 2; dy++) {
|
||||
if (dx === 0 && dy === 0) continue;
|
||||
const sx = f.x + dx;
|
||||
const sy = f.y + 1 + dy;
|
||||
if (!isTerrain(terrain, sx, sy)) continue;
|
||||
const dist = Math.max(Math.abs(dx), Math.abs(dy));
|
||||
const alpha = dist === 1 ? 0.4 : 0.2;
|
||||
terrain.fillStyle = `rgba(0,0,0,${alpha})`;
|
||||
terrain.fillRect(sx, sy, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let f of fireCells) {
|
||||
if (Math.random() < dt * 6) smokeParticles.push({
|
||||
if (Math.random() < dt * 12) smokeParticles.push({
|
||||
x: f.x + (Math.random() > 0.5 ? 1 : -1) * Math.random(),
|
||||
y: f.y,
|
||||
vx: (Math.random() - 0.5) * 5,
|
||||
vy: -(8 + Math.random() * 12),
|
||||
alpha: 0.3 + Math.random() * 0.15,
|
||||
lifetime: 1 + Math.random() * 0.8,
|
||||
alpha: 0.35 + Math.random() * 0.2,
|
||||
lifetime: 1.2 + Math.random() * 1.0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -918,7 +933,7 @@ function updateParticle(p) {
|
||||
// standing on another napalm particle - behave as if on fire terrain
|
||||
}
|
||||
|
||||
if (below && !hasFireAt(p.x, p.y) && !napalmBelow) {
|
||||
if (below && !hasFireAt(p.x, p.y) && !napalmBelow && !hasFireAt(p.x, p.y + 1)) {
|
||||
createFire(p.x, p.y, p.source);
|
||||
p.alive = false;
|
||||
reservedPositions.delete(p.x + ',' + p.y);
|
||||
@@ -934,14 +949,16 @@ function updateParticle(p) {
|
||||
if (claimMove(p, p.x + dir, p.y + 1)) {
|
||||
p.dir = dir;
|
||||
p.hasDir = true;
|
||||
} else if (flowDirs.length > 1) {
|
||||
return;
|
||||
}
|
||||
if (flowDirs.length > 1) {
|
||||
const other = flowDirs.find(d => d !== dir);
|
||||
if (claimMove(p, p.x + other, p.y + 1)) {
|
||||
p.dir = other;
|
||||
p.hasDir = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const spillDirs = [];
|
||||
@@ -952,14 +969,16 @@ function updateParticle(p) {
|
||||
if (claimMove(p, p.x + dir, p.y)) {
|
||||
p.dir = dir;
|
||||
p.hasDir = true;
|
||||
} else if (spillDirs.length > 1) {
|
||||
return;
|
||||
}
|
||||
if (spillDirs.length > 1) {
|
||||
const other = spillDirs.find(d => d !== dir);
|
||||
if (claimMove(p, p.x + other, p.y)) {
|
||||
p.dir = other;
|
||||
p.hasDir = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
const canPushLeft = !isTerrain(terrain, p.x - 1, p.y)
|
||||
&& napalmParticles.some(np => np.alive && np.x === p.x - 1 && np.y === p.y)
|
||||
@@ -982,6 +1001,12 @@ function updateParticle(p) {
|
||||
|
||||
if (tryFlow(p, p.dir) || trySpill(p, p.dir)) return;
|
||||
|
||||
const otherDir = p.dir === -1 ? 1 : -1;
|
||||
if (tryFlow(p, otherDir)) {
|
||||
p.dir = otherDir;
|
||||
return;
|
||||
}
|
||||
|
||||
if (p.x === p.emitX && p.y === p.emitY) {
|
||||
pushNapalmColumn(p.x, p.y - 1);
|
||||
}
|
||||
@@ -1001,8 +1026,8 @@ function applyFireDamage(dt) {
|
||||
|
||||
function drawNapalm() {
|
||||
for (let s of smokeParticles) {
|
||||
foreground.globalAlpha = s.alpha * 0.4;
|
||||
drawRect(foreground, s.x, s.y - 1, 2, 2, '#666');
|
||||
foreground.globalAlpha = s.alpha * 0.7;
|
||||
drawRect(foreground, s.x, s.y - 1, 3, 3, '#999');
|
||||
foreground.globalAlpha = 1;
|
||||
}
|
||||
|
||||
@@ -1191,7 +1216,7 @@ function drawParticles() {
|
||||
|
||||
function drawStartMenuPanel() {
|
||||
drawPanelBg(framebuffer);
|
||||
drawPanelTitle(framebuffer, 'RAVAGED PLANET');
|
||||
drawPanelTitle(framebuffer, 'RAVAGED PLANET', '#f60');
|
||||
|
||||
const playerCounts = [2, 3, 4, 5, 6];
|
||||
const roundCounts = [1, 3, 5, 10];
|
||||
@@ -1220,7 +1245,7 @@ function drawStartMenuPanel() {
|
||||
|
||||
function drawMarketPanel() {
|
||||
drawPanelBg(framebuffer);
|
||||
drawPanelTitle(framebuffer, 'MARKET');
|
||||
drawPanelTitle(framebuffer, 'MARKET', '#0c8');
|
||||
|
||||
const humanPlayer = players.find(p => !p.ai);
|
||||
drawPanelText(framebuffer, `Score: ${score}`, 160, 100, 'yellow');
|
||||
@@ -1268,12 +1293,12 @@ function drawRoundEndPanel() {
|
||||
drawPanelBg(framebuffer);
|
||||
|
||||
if (winner) {
|
||||
drawPanelTitle(framebuffer, `Round ${round} Complete!`);
|
||||
drawPanelTitle(framebuffer, `Round ${round} Complete!`, '#48f');
|
||||
drawPanelText(framebuffer, `Winner: ${winner.name}`, 160, 100, winner.c);
|
||||
drawPanelText(framebuffer, `Kills: ${winner.kills}`, 160, 130, 'white');
|
||||
drawPanelText(framebuffer, `Round Score: ${winner.kills * SCORE_PER_KILL + SCORE_FOR_WIN}`, 160, 160, 'white');
|
||||
} else {
|
||||
drawPanelTitle(framebuffer, `Round ${round} Complete!`);
|
||||
drawPanelTitle(framebuffer, `Round ${round} Complete!`, '#48f');
|
||||
drawPanelText(framebuffer, 'Nobody survived!', 160, 100, 'white');
|
||||
}
|
||||
|
||||
@@ -1289,7 +1314,7 @@ function drawRoundEndPanel() {
|
||||
|
||||
function drawGameOverPanel() {
|
||||
drawPanelBg(framebuffer);
|
||||
drawPanelTitle(framebuffer, 'GAME OVER');
|
||||
drawPanelTitle(framebuffer, 'GAME OVER', '#e22');
|
||||
|
||||
const y = 100;
|
||||
const sortedPlayers = [...players].sort((a, b) => b.wins - a.wins || b.kills - a.kills);
|
||||
|
||||
+29
-4
@@ -17,16 +17,41 @@ export function drawPanelBg(ctx) {
|
||||
ctx.strokeRect(PANEL_X + 3, PANEL_Y + 3, PANEL_WIDTH - 6, PANEL_HEIGHT - 6);
|
||||
}
|
||||
|
||||
export function drawPanelTitle(ctx, text) {
|
||||
const x = PANEL_X + PANEL_WIDTH / 2;
|
||||
export function drawBevelBar(ctx, x, y, w, h, color) {
|
||||
if (w <= 0) return;
|
||||
drawRect(ctx, x, y, w, h, color);
|
||||
ctx.globalAlpha = 0.35;
|
||||
drawRect(ctx, x, y, w, 1, '#fff');
|
||||
drawRect(ctx, x, y, 1, h, '#fff');
|
||||
drawRect(ctx, x, y + h - 1, w, 1, '#000');
|
||||
drawRect(ctx, x + w - 1, y, 1, h, '#000');
|
||||
ctx.globalAlpha = 1.0;
|
||||
}
|
||||
|
||||
export function drawPanelTitle(ctx, text, barColor) {
|
||||
const cx = PANEL_X + PANEL_WIDTH / 2;
|
||||
const y = PANEL_Y + 20;
|
||||
const gap = 8;
|
||||
const margin = 20;
|
||||
|
||||
ctx.font = `${PANEL_TITLE_FONT_SIZE}px ibm-vga`;
|
||||
const textWidth = ctx.measureText(text).width;
|
||||
const barH = PANEL_TITLE_FONT_SIZE;
|
||||
|
||||
const leftEnd = cx - textWidth / 2 - gap;
|
||||
const rightStart = cx + textWidth / 2 + gap;
|
||||
|
||||
if (barColor) {
|
||||
drawBevelBar(ctx, PANEL_X + margin, y, leftEnd - (PANEL_X + margin), barH, barColor);
|
||||
drawBevelBar(ctx, rightStart, y, (PANEL_X + PANEL_WIDTH - margin) - rightStart, barH, barColor);
|
||||
}
|
||||
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(text, x + 1, y + 1);
|
||||
ctx.fillText(text, cx + 1, y + 1);
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillText(text, x, y);
|
||||
ctx.fillText(text, cx, y);
|
||||
}
|
||||
|
||||
export function drawPanelText(ctx, text, x, y, color = 'white', align = 'left') {
|
||||
|
||||
+41
-16
@@ -5,7 +5,7 @@ import {audio, createOsc} from './sound.js?v=15';
|
||||
import {clipTerrain} from './terrain.js?v=15';
|
||||
|
||||
export function drawExplosion(ctx, x, y, r) {
|
||||
const step = Math.max(1, Math.floor(r / 12));
|
||||
const step = Math.max(2, Math.floor(r / 6));
|
||||
for (let i = r; i > 0; i -= step) {
|
||||
const t = 1 - (i / r);
|
||||
const base = 60 + Math.floor(t * 195);
|
||||
@@ -20,19 +20,33 @@ export function drawDirt(ctx, x, y, r, c) {
|
||||
drawCircle(ctx, x, y, r, c);
|
||||
}
|
||||
|
||||
export function drawLavaExplosion(ctx, x, y, r) {
|
||||
export function drawLavaExplosion(ctx, x, y, r, blobs) {
|
||||
drawCircle(ctx, x, y, r, 'rgb(60, 0, 0)');
|
||||
const count = Math.floor(r * 1.2);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dist = Math.random() * r;
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const sx = x + Math.cos(angle) * dist;
|
||||
const sy = y + Math.sin(angle) * dist;
|
||||
const spotR = Math.max(1, Math.random() * (2 + r * 0.03));
|
||||
const heat = 1 - dist / r;
|
||||
const red = 180 + Math.floor(Math.random() * 75);
|
||||
const green = Math.floor(Math.random() * heat * 150);
|
||||
drawCircle(ctx, sx, sy, spotR, `rgb(${red}, ${green}, 0)`);
|
||||
if (!blobs) return;
|
||||
|
||||
for (const blob of blobs) {
|
||||
const t = r * 0.1;
|
||||
const drift = Math.sin(t + blob.phase) * 0.15;
|
||||
const dist = r * blob.distFrac;
|
||||
const angle = blob.angle + Math.sin(t * 0.2 + blob.phase * 0.5) * 0.3;
|
||||
const sx = x + Math.cos(angle + drift) * dist;
|
||||
const sy = y + Math.sin(angle + drift) * dist;
|
||||
const pulse = 0.6 + 0.4 * Math.sin(t * 0.3 + blob.phase * 1.3);
|
||||
const br = Math.max(1, blob.baseR * pulse);
|
||||
const heat = 1 - blob.distFrac;
|
||||
const red = Math.floor(blob.red + Math.sin(t * 0.5 + blob.phase) * 30);
|
||||
const green = Math.floor(Math.abs(Math.sin(t * 0.4 + blob.phase * 0.7)) * heat * 150);
|
||||
|
||||
ctx.globalAlpha = 0.15;
|
||||
drawCircle(ctx, sx, sy, br * 3, `rgb(${red}, ${green}, 0)`);
|
||||
|
||||
ctx.globalAlpha = 0.3;
|
||||
drawCircle(ctx, sx, sy, br * 1.8, `rgb(${red}, ${green}, 0)`);
|
||||
|
||||
ctx.globalAlpha = 0.7;
|
||||
drawCircle(ctx, sx, sy, br, `rgb(${Math.min(255, red + 40)}, ${green}, 0)`);
|
||||
|
||||
ctx.globalAlpha = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +99,29 @@ export const EXPLOSION_TYPES = {
|
||||
lava: {
|
||||
create(spec, x, y) {
|
||||
const {r} = spec;
|
||||
const blobs = [];
|
||||
const count = Math.max(3, Math.floor(r * 0.25));
|
||||
for (let i = 0; i < count; i++) {
|
||||
blobs.push({
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
angle: Math.random() * Math.PI * 2,
|
||||
distFrac: 0.1 + Math.random() * 0.9,
|
||||
baseR: 3 + Math.random() * (4 + r * 0.06),
|
||||
red: 150 + Math.random() * 100,
|
||||
});
|
||||
}
|
||||
const osc = createOsc('sawtooth');
|
||||
osc.start();
|
||||
return {type:'lava', x, y, r, cr:0, osc};
|
||||
return {type:'lava', x, y, r, cr:0, osc, blobs};
|
||||
},
|
||||
update(explosion, dt) {
|
||||
return (explosion.cr += dt * 30) < explosion.r;
|
||||
},
|
||||
draw(explosion, foreground) {
|
||||
const {x, y, cr, osc} = explosion;
|
||||
const {x, y, cr, osc, blobs} = explosion;
|
||||
const f = cycle(explosion.cr, 6) % 2 === 0 ? 55 : 110;
|
||||
osc.frequency.setValueAtTime(f, audio.currentTime);
|
||||
drawLavaExplosion(foreground, x, y, cr);
|
||||
drawLavaExplosion(foreground, x, y, cr, blobs);
|
||||
},
|
||||
stop(explosion) {
|
||||
const {osc} = explosion;
|
||||
|
||||
Reference in new issue
Block a user