diff --git a/.gitignore b/.gitignore index eb5a316c..6e383fb4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ target +mat_colors.txt +mat_data.txt \ No newline at end of file diff --git a/noita-proxy/src/net/world.rs b/noita-proxy/src/net/world.rs index 223b09ec..b767b04e 100644 --- a/noita-proxy/src/net/world.rs +++ b/noita-proxy/src/net/world.rs @@ -33,7 +33,7 @@ struct ByteParser<'a> { } pub struct WorldManager { - // writer: BufWriter, + writer: BufWriter, } impl<'a> ByteParser<'a> { @@ -81,16 +81,16 @@ pub enum WorldUpdateKind { impl WorldManager { pub fn new() -> Self { Self { - // writer: BufWriter::new(File::create("worldlog.bin").unwrap()), + writer: BufWriter::new(File::create("worldlog.bin").unwrap()), } } pub fn add_update(&mut self, update: RunLengthUpdate) { - // bincode::serialize_into(&mut self.writer, &WorldUpdateKind::Update(update)).unwrap(); + bincode::serialize_into(&mut self.writer, &WorldUpdateKind::Update(update)).unwrap(); } pub fn add_end(&mut self) { - // bincode::serialize_into(&mut self.writer, &WorldUpdateKind::End).unwrap(); + bincode::serialize_into(&mut self.writer, &WorldUpdateKind::End).unwrap(); } } @@ -110,16 +110,25 @@ mod test { model.apply_update(&entry); // println!("{:?}", entry.header) entry_id += 1; - if entry_id > 1000000 { - break; - } + // if entry_id > 1000000 { + // break; + // } if entry_id % 10000 == 0 { - println!("{}", model.pixels.len()); let (x, y) = model.get_start(); let img = model.gen_image(x, y, 2048, 2048); img.save(format!("/tmp/img_{}.png", entry_id)).unwrap(); } } } + + let (x, y) = model.get_start(); + let img = model.gen_image(x, y, 2048 * 2, 2048 * 2); + img.save(format!("/tmp/img_{}.png", entry_id)).unwrap(); + + let mut mats = model.mats.iter().copied().collect::>(); + mats.sort(); + for mat in mats { + println!("{}", mat) + } } } diff --git a/noita-proxy/src/net/world/world_model.rs b/noita-proxy/src/net/world/world_model.rs index 37c86ba2..cac26201 100644 --- a/noita-proxy/src/net/world/world_model.rs +++ b/noita-proxy/src/net/world/world_model.rs @@ -1,28 +1,88 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use image::{Rgb, RgbImage}; use super::RunLengthUpdate; +const CHUNK_SIZE: usize = 256; + #[derive(Clone, Copy, Default)] pub struct Pixel { material: i16, } +struct Chunk { + pixels: [Pixel; CHUNK_SIZE * CHUNK_SIZE], +} + pub struct WorldModel { - pub pixels: HashMap<(i32, i32), Pixel>, + chunks: HashMap<(i32, i32), Chunk>, + pub mats: HashSet, + palette: MatPalette, +} + +struct MatPalette { + colors: Vec>, +} + +impl Default for Chunk { + fn default() -> Self { + Self { + pixels: [Pixel::default(); CHUNK_SIZE * CHUNK_SIZE], + } + } +} + +impl MatPalette { + fn new() -> Self { + let raw_data = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/mat_colors.txt" + )); + + let mut colors = Vec::new(); + + for line in raw_data.split_ascii_whitespace() { + let num: u32 = line.parse().unwrap(); + let color = Rgb::from([(num >> 16) as u8, (num >> 8) as u8, num as u8]); + colors.push(color); + } + Self { colors } + } } impl WorldModel { + fn to_chunk_coords(x: i32, y: i32) -> ((i32, i32), usize) { + let chunk_x = x.div_euclid(CHUNK_SIZE as i32); + let chunk_y = y.div_euclid(CHUNK_SIZE as i32); + let x = x.rem_euclid(CHUNK_SIZE as i32) as usize; + let y = y.rem_euclid(CHUNK_SIZE as i32) as usize; + let offset = x + y * CHUNK_SIZE; + ((chunk_x, chunk_y), offset) + } + fn set_pixel(&mut self, x: i32, y: i32, pixel: Pixel) { - self.pixels.insert((x, y), pixel); + self.mats.insert(pixel.material); + let (chunk_coord, offset) = Self::to_chunk_coords(x, y); + let chunk = self + .chunks + .entry(chunk_coord) + .or_insert_with(Default::default); + chunk.pixels[offset] = pixel; } fn get_pixel(&self, x: i32, y: i32) -> Pixel { - self.pixels.get(&(x, y)).copied().unwrap_or_default() + let (chunk_coord, offset) = Self::to_chunk_coords(x, y); + self.chunks + .get(&chunk_coord) + .map(|chunk| chunk.pixels[offset]) + .unwrap_or_default() } + pub fn new() -> Self { Self { - pixels: Default::default(), + chunks: Default::default(), + mats: Default::default(), + palette: MatPalette::new(), } } @@ -52,16 +112,21 @@ impl WorldModel { } pub fn get_start(&self) -> (i32, i32) { - let x = self.pixels.keys().map(|v| v.0).min().unwrap_or_default(); - let y = self.pixels.keys().map(|v| v.1).min().unwrap_or_default(); - (x, y) + let cx = self.chunks.keys().map(|v| v.0).min().unwrap_or_default(); + let cy = self.chunks.keys().map(|v| v.1).min().unwrap_or_default(); + (cx * CHUNK_SIZE as i32, cy * CHUNK_SIZE as i32) } pub fn gen_image(&self, x: i32, y: i32, w: u32, h: u32) -> RgbImage { RgbImage::from_fn(w, h, |lx, ly| { let pixel = self.get_pixel(x + lx as i32, y + ly as i32); - let b = if pixel.material != 0 { 255 } else { 0 }; - Rgb([pixel.material as u8, (pixel.material >> 8) as u8, b]) + // let b = if pixel.material != 0 { 255 } else { 0 }; + // Rgb([pixel.material as u8, (pixel.material >> 8) as u8, b]) + self.palette + .colors + .get(usize::try_from(pixel.material).unwrap_or_default()) + .copied() + .unwrap_or(Rgb([0, 0, 0])) }) } } diff --git a/quant.ew/files/src/system/world_sync2.lua b/quant.ew/files/src/system/world_sync2.lua index d80a2361..36c913ca 100644 --- a/quant.ew/files/src/system/world_sync2.lua +++ b/quant.ew/files/src/system/world_sync2.lua @@ -18,6 +18,9 @@ local bandwidth_bucket_max = 29000 local KEY_WORLD_FRAME = 0 local KEY_WORLD_END = 1 +local initialized_chunks = {} +local CHUNK_SIZE = 256 + function world_sync.on_world_update_host() local grid_world = world_ffi.get_grid_world() @@ -28,7 +31,6 @@ function world_sync.on_world_update_host() local end_ = begin + thread_impl.chunk_update_count local count = thread_impl.chunk_update_count - -- GamePrint("w update "..count) for i = 0, count - 1 do local it = begin[i] @@ -42,11 +44,8 @@ function world_sync.on_world_update_host() end_x = end_x + 1 end_y = end_y + 2 - -- if i < 9 then - -- GamePrint(start_x.." "..start_y.." "..end_x.." "..end_y) - -- end local rectangle = rect.Rectangle(start_x, start_y, end_x, end_y) - rect_optimiser:submit(rectangle) + -- rect_optimiser:submit(rectangle) end for i = 0, tonumber(thread_impl.world_update_params_count) - 1 do local wup = thread_impl.world_update_params.begin[i] @@ -56,11 +55,28 @@ function world_sync.on_world_update_host() local end_y = wup.update_region.bottom_right.y local rectangle = rect.Rectangle(start_x, start_y, end_x, end_y) - rect_optimiser:submit(rectangle) + -- rect_optimiser:submit(rectangle) end + + local px, py = EntityGetTransform(ctx.my_player.entity) + local ocx, ocy = math.floor(px / CHUNK_SIZE), math.floor(py / CHUNK_SIZE) + for cx = ocx-1,ocx+1 do + for cy = ocy-1,ocy+1 do + local chunk_id = cx.." "..cy + if initialized_chunks[chunk_id] == nil then + local crect = rect.Rectangle(cx * CHUNK_SIZE, cy * CHUNK_SIZE, (cx+1) * CHUNK_SIZE, (cy+1) * CHUNK_SIZE) + if DoesWorldExistAt(crect.left, crect.top, crect.right, crect.bottom) then + GamePrint("Sending chunk "..chunk_id) + initialized_chunks[chunk_id] = true + rect_optimiser:submit(crect) + end + end + end + end + if GameGetFrameNum() % 1 == 0 then rect_optimiser:scan() - + for crect in rect.parts(rect_optimiser:iterate(), 256) do local area = nil -- Make sure we don't send chunks that aren't loaded yet, like holy mountains before host got there. diff --git a/scripts/mat_colors.py b/scripts/mat_colors.py new file mode 100644 index 00000000..299b1c7b --- /dev/null +++ b/scripts/mat_colors.py @@ -0,0 +1,29 @@ +mats_path = "/home/quant/.local/share/Steam/steamapps/compatdata/881100/pfx/dosdevices/c:/users/steamuser/AppData/LocalLow/Nolla_Games_Noita/data/materials.xml" + +import xml.etree.ElementTree as ET + +tree = ET.parse(mats_path) + +root = tree.getroot() + +colors = [] + +mat_data = open("mat_data.txt", "w") + +for i, child in enumerate(root): + if child.tag not in ["CellData", "CellDataChild"]: + continue + name = child.get("name") + print(i, name, file=mat_data) + graphics = child.find("Graphics") + if graphics is not None: + color = graphics.get("color") + if color is not None: + colors.append(int(color[2:], 16)) + else: + colors.append(0) + else: + colors.append(0) + +with open("mat_colors.txt", "w") as f: + print(*colors, sep=" ", file=f) \ No newline at end of file