mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
World replays
This commit is contained in:
parent
67ebe63fa3
commit
a7f52e42d1
5 changed files with 146 additions and 25 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
||||||
target
|
target
|
||||||
|
mat_colors.txt
|
||||||
|
mat_data.txt
|
|
@ -33,7 +33,7 @@ struct ByteParser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WorldManager {
|
pub struct WorldManager {
|
||||||
// writer: BufWriter<File>,
|
writer: BufWriter<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ByteParser<'a> {
|
impl<'a> ByteParser<'a> {
|
||||||
|
@ -81,16 +81,16 @@ pub enum WorldUpdateKind {
|
||||||
impl WorldManager {
|
impl WorldManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
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) {
|
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) {
|
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);
|
model.apply_update(&entry);
|
||||||
// println!("{:?}", entry.header)
|
// println!("{:?}", entry.header)
|
||||||
entry_id += 1;
|
entry_id += 1;
|
||||||
if entry_id > 1000000 {
|
// if entry_id > 1000000 {
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
if entry_id % 10000 == 0 {
|
if entry_id % 10000 == 0 {
|
||||||
println!("{}", model.pixels.len());
|
|
||||||
let (x, y) = model.get_start();
|
let (x, y) = model.get_start();
|
||||||
let img = model.gen_image(x, y, 2048, 2048);
|
let img = model.gen_image(x, y, 2048, 2048);
|
||||||
img.save(format!("/tmp/img_{}.png", entry_id)).unwrap();
|
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::<Vec<_>>();
|
||||||
|
mats.sort();
|
||||||
|
for mat in mats {
|
||||||
|
println!("{}", mat)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,88 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use image::{Rgb, RgbImage};
|
use image::{Rgb, RgbImage};
|
||||||
|
|
||||||
use super::RunLengthUpdate;
|
use super::RunLengthUpdate;
|
||||||
|
|
||||||
|
const CHUNK_SIZE: usize = 256;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct Pixel {
|
pub struct Pixel {
|
||||||
material: i16,
|
material: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Chunk {
|
||||||
|
pixels: [Pixel; CHUNK_SIZE * CHUNK_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WorldModel {
|
pub struct WorldModel {
|
||||||
pub pixels: HashMap<(i32, i32), Pixel>,
|
chunks: HashMap<(i32, i32), Chunk>,
|
||||||
|
pub mats: HashSet<i16>,
|
||||||
|
palette: MatPalette,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MatPalette {
|
||||||
|
colors: Vec<Rgb<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
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) {
|
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 {
|
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 {
|
pub fn new() -> Self {
|
||||||
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) {
|
pub fn get_start(&self) -> (i32, i32) {
|
||||||
let x = self.pixels.keys().map(|v| v.0).min().unwrap_or_default();
|
let cx = self.chunks.keys().map(|v| v.0).min().unwrap_or_default();
|
||||||
let y = self.pixels.keys().map(|v| v.1).min().unwrap_or_default();
|
let cy = self.chunks.keys().map(|v| v.1).min().unwrap_or_default();
|
||||||
(x, y)
|
(cx * CHUNK_SIZE as i32, cy * CHUNK_SIZE as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_image(&self, x: i32, y: i32, w: u32, h: u32) -> RgbImage {
|
pub fn gen_image(&self, x: i32, y: i32, w: u32, h: u32) -> RgbImage {
|
||||||
RgbImage::from_fn(w, h, |lx, ly| {
|
RgbImage::from_fn(w, h, |lx, ly| {
|
||||||
let pixel = self.get_pixel(x + lx as i32, y + ly as i32);
|
let pixel = self.get_pixel(x + lx as i32, y + ly as i32);
|
||||||
let b = if pixel.material != 0 { 255 } else { 0 };
|
// let b = if pixel.material != 0 { 255 } else { 0 };
|
||||||
Rgb([pixel.material as u8, (pixel.material >> 8) as u8, b])
|
// 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]))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ local bandwidth_bucket_max = 29000
|
||||||
local KEY_WORLD_FRAME = 0
|
local KEY_WORLD_FRAME = 0
|
||||||
local KEY_WORLD_END = 1
|
local KEY_WORLD_END = 1
|
||||||
|
|
||||||
|
local initialized_chunks = {}
|
||||||
|
local CHUNK_SIZE = 256
|
||||||
|
|
||||||
function world_sync.on_world_update_host()
|
function world_sync.on_world_update_host()
|
||||||
|
|
||||||
local grid_world = world_ffi.get_grid_world()
|
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 end_ = begin + thread_impl.chunk_update_count
|
||||||
|
|
||||||
local count = thread_impl.chunk_update_count
|
local count = thread_impl.chunk_update_count
|
||||||
-- GamePrint("w update "..count)
|
|
||||||
for i = 0, count - 1 do
|
for i = 0, count - 1 do
|
||||||
local it = begin[i]
|
local it = begin[i]
|
||||||
|
|
||||||
|
@ -42,11 +44,8 @@ function world_sync.on_world_update_host()
|
||||||
end_x = end_x + 1
|
end_x = end_x + 1
|
||||||
end_y = end_y + 2
|
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)
|
local rectangle = rect.Rectangle(start_x, start_y, end_x, end_y)
|
||||||
rect_optimiser:submit(rectangle)
|
-- rect_optimiser:submit(rectangle)
|
||||||
end
|
end
|
||||||
for i = 0, tonumber(thread_impl.world_update_params_count) - 1 do
|
for i = 0, tonumber(thread_impl.world_update_params_count) - 1 do
|
||||||
local wup = thread_impl.world_update_params.begin[i]
|
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 end_y = wup.update_region.bottom_right.y
|
||||||
|
|
||||||
local rectangle = rect.Rectangle(start_x, start_y, end_x, end_y)
|
local rectangle = rect.Rectangle(start_x, start_y, end_x, end_y)
|
||||||
rect_optimiser:submit(rectangle)
|
-- rect_optimiser:submit(rectangle)
|
||||||
end
|
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
|
if GameGetFrameNum() % 1 == 0 then
|
||||||
rect_optimiser:scan()
|
rect_optimiser:scan()
|
||||||
|
|
||||||
for crect in rect.parts(rect_optimiser:iterate(), 256) do
|
for crect in rect.parts(rect_optimiser:iterate(), 256) do
|
||||||
local area = nil
|
local area = nil
|
||||||
-- Make sure we don't send chunks that aren't loaded yet, like holy mountains before host got there.
|
-- Make sure we don't send chunks that aren't loaded yet, like holy mountains before host got there.
|
||||||
|
|
29
scripts/mat_colors.py
Normal file
29
scripts/mat_colors.py
Normal file
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue