- Acid now uses acid_resist + health-based damage (not instant delete) - Acid dissolve speeds: Wood ~2s, Dirt ~2.8s, Stone ~4.3s, Iron immune - New material: Iron (ID 16) - silver, static, acid-proof, heat-conductive - Iron texture: bright highlights for metallic shine - Iron reflects light (light_block: 150) - Iron heat_conduct: 200 - conducts heat quickly through entire block - Improved heat transfer formula (divisor 255→200) - Updated AGENTS.md status - No player code (rolled back for fresh start)
266 lines
9.1 KiB
Rust
266 lines
9.1 KiB
Rust
use crate::grid::Grid;
|
|
use crate::material::PROPS;
|
|
|
|
pub struct RenderBuffer {
|
|
pub pixels: Vec<u8>,
|
|
pub width: u32,
|
|
pub height: u32,
|
|
light: Vec<u8>,
|
|
block: Vec<u8>,
|
|
zoom: f32,
|
|
}
|
|
|
|
impl RenderBuffer {
|
|
pub fn new(width: u32, height: u32) -> Self {
|
|
let size = (width * height * 4) as usize;
|
|
let lsize = (width * height) as usize;
|
|
Self {
|
|
pixels: vec![0; size],
|
|
width,
|
|
height,
|
|
light: vec![0; lsize],
|
|
block: vec![0; lsize],
|
|
zoom: 1.0,
|
|
}
|
|
}
|
|
|
|
pub fn render(&mut self, grid: &Grid, cam_x: i32, cam_y: i32, zoom: f32) {
|
|
let rw = self.width as i32;
|
|
let rh = self.height as i32;
|
|
let total = (rw * rh) as usize;
|
|
|
|
self.zoom = zoom;
|
|
|
|
for i in 0..total {
|
|
self.light[i] = 0;
|
|
self.block[i] = 0;
|
|
}
|
|
|
|
for py in 0..rh {
|
|
let gy = cam_y + ((py as f32 - rh as f32 / 2.0) / zoom) as i32;
|
|
for px in 0..rw {
|
|
let gx = cam_x + ((px as f32 - rw as f32 / 2.0) / zoom) as i32;
|
|
let idx = (py * rw + px) as usize;
|
|
if grid.in_bounds(gx, gy) {
|
|
let gi = grid.index(gx as u32, gy as u32);
|
|
let mat = grid.materials[gi];
|
|
if mat > 0 {
|
|
let props = &PROPS[mat as usize];
|
|
self.light[idx] = props.light_emit;
|
|
self.block[idx] = props.light_block;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let max_passes = (16.0 / zoom).max(6.0).min(24.0) as u32;
|
|
for _pass in 0..max_passes {
|
|
let dir = _pass & 1;
|
|
let mut changed = false;
|
|
if dir == 0 {
|
|
for py in 0..rh {
|
|
for px in 0..rw {
|
|
if self.spread_light(py, px, rw, rh) {
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for py in (0..rh).rev() {
|
|
for px in (0..rw).rev() {
|
|
if self.spread_light(py, px, rw, rh) {
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !changed {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for py in 0..rh {
|
|
for px in 0..rw {
|
|
let gx = cam_x + ((px as f32 - rw as f32 / 2.0) / zoom) as i32;
|
|
let gy = cam_y + ((py as f32 - rh as f32 / 2.0) / zoom) as i32;
|
|
|
|
let color = if grid.in_bounds(gx, gy) {
|
|
let gi = grid.index(gx as u32, gy as u32);
|
|
let mat = grid.materials[gi];
|
|
let li = (py * rw + px) as usize;
|
|
let lvl = self.light[li] as u32;
|
|
|
|
if mat == 0 {
|
|
let bg = [20u32, 20, 30];
|
|
let glow_r = 255u32;
|
|
let glow_g = 160u32;
|
|
let glow_b = 40u32;
|
|
let t = ((lvl * lvl) / 255).min(255);
|
|
let r = (bg[0] * (255 - t) + glow_r * t) / 255;
|
|
let g = (bg[1] * (255 - t) + glow_g * t) / 255;
|
|
let b = (bg[2] * (255 - t) + glow_b * t) / 255;
|
|
[r as u8, g as u8, b as u8, 255]
|
|
} else {
|
|
let props = &PROPS[mat as usize];
|
|
let base = props.color;
|
|
let temp = grid.temps[gi];
|
|
let t = (temp as f32 / 200.0).min(1.0);
|
|
|
|
let mut r = (base[0] as f32 + t * 60.0) as u8;
|
|
let mut g = (base[1] as f32 * (1.0 - t * 0.4)) as u8;
|
|
let mut b = (base[2] as f32 * (1.0 - t * 0.6)) as u8;
|
|
|
|
if mat == 9 || mat == 6 {
|
|
let (fr, fg, fb) = flame_variation(gx as u32, gy as u32, mat);
|
|
r = (r as i32 + fr).clamp(0, 255) as u8;
|
|
g = (g as i32 + fg).clamp(0, 255) as u8;
|
|
b = (b as i32 + fb).clamp(0, 255) as u8;
|
|
} else {
|
|
let var = texture_offset(gx as u32, gy as u32, mat);
|
|
r = (r as i32 + var).clamp(0, 255) as u8;
|
|
g = (g as i32 + var).clamp(0, 255) as u8;
|
|
b = (b as i32 + var).clamp(0, 255) as u8;
|
|
}
|
|
|
|
if props.light_emit == 0 {
|
|
let ambient = 150u32;
|
|
let add = lvl;
|
|
r = ((r as u32 * ambient >> 8) + add).min(255) as u8;
|
|
g = ((g as u32 * ambient >> 8) + add).min(255) as u8;
|
|
b = ((b as u32 * ambient >> 8) + add).min(255) as u8;
|
|
}
|
|
|
|
[r, g, b, base[3]]
|
|
}
|
|
} else {
|
|
[20, 20, 30, 255]
|
|
};
|
|
|
|
let pi = ((py * rw + px) * 4) as usize;
|
|
self.pixels[pi] = color[0];
|
|
self.pixels[pi + 1] = color[1];
|
|
self.pixels[pi + 2] = color[2];
|
|
self.pixels[pi + 3] = color[3];
|
|
}
|
|
}
|
|
}
|
|
|
|
fn spread_light(&mut self, py: i32, px: i32, rw: i32, rh: i32) -> bool {
|
|
let idx = (py * rw + px) as usize;
|
|
let cur = self.light[idx] as u32;
|
|
let blk = (self.block[idx] as u32).min(200);
|
|
|
|
let neighbors: [(i32, i32); 8] = [
|
|
(px - 1, py - 1), (px, py - 1), (px + 1, py - 1),
|
|
(px - 1, py), (px + 1, py),
|
|
(px - 1, py + 1), (px, py + 1), (px + 1, py + 1),
|
|
];
|
|
|
|
let mut best = cur;
|
|
for &(nx, ny) in &neighbors {
|
|
if nx >= 0 && nx < rw && ny >= 0 && ny < rh {
|
|
let ni = (ny * rw + nx) as usize;
|
|
let nlight = self.light[ni] as u32;
|
|
if nlight <= 1 { continue; }
|
|
let base_falloff = if nx == px || ny == py { 2.0 } else { 3.0 };
|
|
let falloff = (base_falloff / self.zoom) as u32 + blk;
|
|
if nlight > falloff {
|
|
let incoming = nlight - falloff;
|
|
if incoming > best {
|
|
best = incoming;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if blk > 0 && blk < 180 && best > cur && best > 4 {
|
|
for &(nx, ny) in &neighbors {
|
|
if nx >= 0 && nx < rw && ny >= 0 && ny < rh {
|
|
let ni = (ny * rw + nx) as usize;
|
|
let nlight = self.light[ni] as u32;
|
|
if nlight >= best { continue; }
|
|
let reflect = best >> 1;
|
|
if reflect > nlight {
|
|
self.light[ni] = reflect as u8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if best != cur {
|
|
self.light[idx] = best as u8;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn texture_offset(x: u32, y: u32, mat: u8) -> i32 {
|
|
match mat {
|
|
5 => {
|
|
let wobble = ((y.wrapping_mul(5) ^ y.wrapping_shr(2)) & 3) as i32 - 1;
|
|
let pos = (x as i32).wrapping_add(wobble);
|
|
let grain = pos % 5;
|
|
let line_id = (pos / 5) as u32;
|
|
let darkness = hash(line_id, 0) & 15;
|
|
if grain == 0 {
|
|
-12 - darkness as i32
|
|
} else if grain == 1 {
|
|
-5
|
|
} else {
|
|
let n = hash(x.wrapping_add(y >> 1), y);
|
|
((n as i32 - 128) * 7) >> 7
|
|
}
|
|
}
|
|
2 => {
|
|
let n = hash(x.wrapping_mul(3) >> 2, y.wrapping_mul(3) >> 2);
|
|
((n as i32 - 128) * 20) >> 7
|
|
}
|
|
1 | 10 => {
|
|
let n = hash(x, y);
|
|
((n as i32 - 128) * 10) >> 7
|
|
}
|
|
3 => {
|
|
let n = hash(x.wrapping_add(y), y);
|
|
((n as i32 - 128) * 6) >> 7
|
|
}
|
|
11 => {
|
|
let n = hash(x, y.wrapping_mul(y));
|
|
((n as i32 - 128) * 5) >> 7
|
|
}
|
|
16 => {
|
|
let n = hash(x ^ (y >> 2), y ^ (x >> 2));
|
|
let highlight = ((x.wrapping_mul(13) ^ y.wrapping_mul(7)) & 31) as i32;
|
|
if highlight < 3 {
|
|
25 + (highlight * 6)
|
|
} else if highlight < 6 {
|
|
14
|
|
} else {
|
|
((n as i32 - 128) * 10) >> 7
|
|
}
|
|
}
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
fn hash(x: u32, y: u32) -> u8 {
|
|
let h = x.wrapping_mul(374761393)
|
|
.wrapping_add(y.wrapping_mul(668265263));
|
|
(h ^ h.wrapping_shr(13)).wrapping_mul(1274126177) as u8
|
|
}
|
|
|
|
fn flame_variation(x: u32, y: u32, mat: u8) -> (i32, i32, i32) {
|
|
let h = hash(x, y) as i32;
|
|
let r_var = ((h - 128) * 6) >> 7;
|
|
if mat == 9 {
|
|
let g_var = ((h.wrapping_sub(40) as i32 - 128) * 12) >> 7;
|
|
let b_var = ((h.wrapping_add(60) as i32 - 128) * 8) >> 7;
|
|
(-r_var, g_var, b_var)
|
|
} else {
|
|
let g_var = ((h - 128) * 8) >> 7;
|
|
let b_var = ((h - 128) * 5) >> 7;
|
|
(r_var, g_var, b_var)
|
|
}
|
|
}
|