feat: Add fire simulation with color variation and flammable material restrictions
This commit is contained in:
parent
0788b3067d
commit
d6d874ab99
@ -18,6 +18,7 @@
|
||||
<button id="wood-btn">Wood</button>
|
||||
<button id="seed-btn">Seed</button>
|
||||
<button id="tree-seed-btn">Tree Seed</button>
|
||||
<button id="fire-btn">Fire</button>
|
||||
<button id="eraser-btn">Eraser</button>
|
||||
</div>
|
||||
<div class="navigation">
|
||||
|
141
script.js
141
script.js
@ -13,6 +13,7 @@ const WOOD_COLOR = '#8B5A2B';
|
||||
const SEED_COLOR = '#654321';
|
||||
const FLOWER_COLORS = ['#FF0000', '#FFFF00', '#FF00FF', '#FFA500', '#FFFFFF', '#00FFFF'];
|
||||
const LEAF_COLOR = '#228B22';
|
||||
const FIRE_COLORS = ['#FF0000', '#FF3300', '#FF6600', '#FF9900', '#FFCC00', '#FFFF00'];
|
||||
|
||||
// Element types
|
||||
const EMPTY = 0;
|
||||
@ -28,6 +29,10 @@ const GRASS_BLADE = 9;
|
||||
const FLOWER = 10;
|
||||
const TREE_SEED = 11;
|
||||
const LEAF = 12;
|
||||
const FIRE = 13;
|
||||
|
||||
// Flammable materials
|
||||
const FLAMMABLE_MATERIALS = [GRASS, WOOD, SEED, GRASS_BLADE, FLOWER, TREE_SEED, LEAF];
|
||||
|
||||
// Global variables
|
||||
let canvas, ctx;
|
||||
@ -45,6 +50,7 @@ let worldOffsetYBeforeDrag = 0;
|
||||
let chunks = new Map(); // Map to store chunks with key "x,y"
|
||||
let metadata = new Map(); // Map to store metadata for pixels
|
||||
let debugMode = false;
|
||||
let fireUpdateCounter = 0;
|
||||
|
||||
// Initialize the simulation
|
||||
window.onload = function() {
|
||||
@ -64,6 +70,7 @@ window.onload = function() {
|
||||
document.getElementById('wood-btn').addEventListener('click', () => setTool(WOOD));
|
||||
document.getElementById('seed-btn').addEventListener('click', () => setTool(SEED));
|
||||
document.getElementById('tree-seed-btn').addEventListener('click', () => setTool(TREE_SEED));
|
||||
document.getElementById('fire-btn').addEventListener('click', () => setTool(FIRE));
|
||||
document.getElementById('eraser-btn').addEventListener('click', () => setTool(EMPTY));
|
||||
|
||||
// Navigation controls
|
||||
@ -116,6 +123,8 @@ function setTool(tool) {
|
||||
document.getElementById('seed-btn').classList.add('active');
|
||||
} else if (tool === TREE_SEED) {
|
||||
document.getElementById('tree-seed-btn').classList.add('active');
|
||||
} else if (tool === FIRE) {
|
||||
document.getElementById('fire-btn').classList.add('active');
|
||||
} else if (tool === EMPTY) {
|
||||
document.getElementById('eraser-btn').classList.add('active');
|
||||
}
|
||||
@ -192,18 +201,30 @@ function draw(x, y) {
|
||||
const pixelX = worldX + dx;
|
||||
const pixelY = worldY + dy;
|
||||
|
||||
setPixel(pixelX, pixelY, currentTool);
|
||||
|
||||
// Add metadata for special types
|
||||
if (currentTool === SEED) {
|
||||
setMetadata(pixelX, pixelY, { type: 'regular' });
|
||||
} else if (currentTool === FLOWER) {
|
||||
setMetadata(pixelX, pixelY, {
|
||||
type: 'flower',
|
||||
color: FLOWER_COLORS[Math.floor(Math.random() * FLOWER_COLORS.length)],
|
||||
age: 0,
|
||||
height: 1
|
||||
});
|
||||
// Special handling for fire - only set fire to flammable materials
|
||||
if (currentTool === FIRE) {
|
||||
const currentPixel = getPixel(pixelX, pixelY);
|
||||
if (FLAMMABLE_MATERIALS.includes(currentPixel)) {
|
||||
setPixel(pixelX, pixelY, FIRE);
|
||||
setMetadata(pixelX, pixelY, {
|
||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setPixel(pixelX, pixelY, currentTool);
|
||||
|
||||
// Add metadata for special types
|
||||
if (currentTool === SEED) {
|
||||
setMetadata(pixelX, pixelY, { type: 'regular' });
|
||||
} else if (currentTool === FLOWER) {
|
||||
setMetadata(pixelX, pixelY, {
|
||||
type: 'flower',
|
||||
color: FLOWER_COLORS[Math.floor(Math.random() * FLOWER_COLORS.length)],
|
||||
age: 0,
|
||||
height: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -377,6 +398,9 @@ function updatePhysics() {
|
||||
// Get visible chunks
|
||||
const visibleChunks = getVisibleChunks();
|
||||
|
||||
// Increment fire update counter
|
||||
fireUpdateCounter++;
|
||||
|
||||
// Process each visible chunk
|
||||
for (const { chunkX, chunkY } of visibleChunks) {
|
||||
const chunk = getOrCreateChunk(chunkX, chunkY);
|
||||
@ -413,6 +437,8 @@ function updatePhysics() {
|
||||
updateFlower(worldX, worldY);
|
||||
} else if (type === TREE_SEED) {
|
||||
updateTreeSeed(worldX, worldY);
|
||||
} else if (type === FIRE) {
|
||||
updateFire(worldX, worldY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -793,6 +819,92 @@ function updateWater(x, y) {
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Water extinguishes fire
|
||||
const directions = [
|
||||
{dx: -1, dy: 0}, {dx: 1, dy: 0},
|
||||
{dx: 0, dy: -1}, {dx: 0, dy: 1},
|
||||
{dx: -1, dy: -1}, {dx: 1, dy: -1},
|
||||
{dx: -1, dy: 1}, {dx: 1, dy: 1}
|
||||
];
|
||||
|
||||
for (const dir of directions) {
|
||||
if (getPixel(x + dir.dx, y + dir.dy) === FIRE) {
|
||||
setPixel(x + dir.dx, y + dir.dy, EMPTY);
|
||||
removeMetadata(x + dir.dx, y + dir.dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add fire update function
|
||||
function updateFire(x, y) {
|
||||
const metadata = getMetadata(x, y);
|
||||
|
||||
if (!metadata) {
|
||||
// Initialize metadata if it doesn't exist
|
||||
setMetadata(x, y, {
|
||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrease lifetime
|
||||
metadata.lifetime--;
|
||||
|
||||
// Randomly change color for flickering effect
|
||||
if (Math.random() < 0.2) {
|
||||
metadata.colorIndex = Math.floor(Math.random() * FIRE_COLORS.length);
|
||||
}
|
||||
|
||||
// Update metadata
|
||||
setMetadata(x, y, metadata);
|
||||
|
||||
// Fire rises upward occasionally
|
||||
if (Math.random() < 0.3 && getPixel(x, y - 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x, y - 1, FIRE);
|
||||
moveMetadata(x, y, x, y - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire can also move slightly to the sides
|
||||
if (Math.random() < 0.1) {
|
||||
const direction = Math.random() > 0.5 ? 1 : -1;
|
||||
if (getPixel(x + direction, y - 1) === EMPTY) {
|
||||
setPixel(x, y, EMPTY);
|
||||
setPixel(x + direction, y - 1, FIRE);
|
||||
moveMetadata(x, y, x + direction, y - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire spreads to nearby flammable materials (less frequently to reduce performance impact)
|
||||
if (fireUpdateCounter % 3 === 0 && Math.random() < 0.3) {
|
||||
const directions = [
|
||||
{dx: -1, dy: 0}, {dx: 1, dy: 0},
|
||||
{dx: 0, dy: -1}, {dx: 0, dy: 1},
|
||||
{dx: -1, dy: -1}, {dx: 1, dy: -1},
|
||||
{dx: -1, dy: 1}, {dx: 1, dy: 1}
|
||||
];
|
||||
|
||||
const dir = directions[Math.floor(Math.random() * directions.length)];
|
||||
const nearbyType = getPixel(x + dir.dx, y + dir.dy);
|
||||
|
||||
if (FLAMMABLE_MATERIALS.includes(nearbyType)) {
|
||||
setPixel(x + dir.dx, y + dir.dy, FIRE);
|
||||
setMetadata(x + dir.dx, y + dir.dy, {
|
||||
lifetime: 100 + Math.floor(Math.random() * 100),
|
||||
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fire burns out after its lifetime
|
||||
if (metadata.lifetime <= 0) {
|
||||
setPixel(x, y, EMPTY);
|
||||
removeMetadata(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
function getVisibleChunks() {
|
||||
@ -889,6 +1001,11 @@ function render() {
|
||||
ctx.fillStyle = SEED_COLOR;
|
||||
} else if (type === LEAF) {
|
||||
ctx.fillStyle = LEAF_COLOR;
|
||||
} else if (type === FIRE) {
|
||||
// Get fire color from metadata
|
||||
const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y);
|
||||
const colorIndex = metadata ? metadata.colorIndex : 0;
|
||||
ctx.fillStyle = FIRE_COLORS[colorIndex];
|
||||
}
|
||||
|
||||
// Draw the pixel
|
||||
|
Loading…
x
Reference in New Issue
Block a user