mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 15:13:16 +00:00
Wip stuff
This commit is contained in:
parent
c781187e57
commit
a830e0f33c
6 changed files with 227 additions and 83 deletions
|
@ -37,6 +37,14 @@ impl Typ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
enum Typ2 {
|
||||||
|
#[serde(rename = "int")]
|
||||||
|
Int,
|
||||||
|
#[serde(other)]
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Field {
|
struct Field {
|
||||||
field: String,
|
field: String,
|
||||||
|
@ -49,6 +57,21 @@ struct Component {
|
||||||
name: String,
|
name: String,
|
||||||
fields: Vec<Field>,
|
fields: Vec<Field>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct FnArg {
|
||||||
|
name: String,
|
||||||
|
typ: Typ2, // TODO
|
||||||
|
default: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ApiFn {
|
||||||
|
fn_name: String,
|
||||||
|
desc: String,
|
||||||
|
args: Vec<FnArg>,
|
||||||
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn generate_components(_item: TokenStream) -> TokenStream {
|
pub fn generate_components(_item: TokenStream) -> TokenStream {
|
||||||
let components: Vec<Component> = serde_json::from_str(include_str!("components.json")).unwrap();
|
let components: Vec<Component> = serde_json::from_str(include_str!("components.json")).unwrap();
|
||||||
|
@ -97,6 +120,23 @@ fn generate_code_for_component(com: Component) -> proc_macro2::TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_code_for_api_fn(api_fn: ApiFn) -> proc_macro2::TokenStream {
|
||||||
|
let fn_name = format_ident!("{}", api_fn.fn_name.to_snek_case());
|
||||||
|
quote! {
|
||||||
|
pub(crate) fn #fn_name() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn generate_api(_item: TokenStream) -> TokenStream {
|
||||||
|
let api_fns: Vec<ApiFn> = serde_json::from_str(include_str!("lua_api.json")).unwrap();
|
||||||
|
|
||||||
|
let res = api_fns.into_iter().map(generate_code_for_api_fn);
|
||||||
|
quote! {#(#res)*}.into()
|
||||||
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn add_lua_fn(item: TokenStream) -> TokenStream {
|
pub fn add_lua_fn(item: TokenStream) -> TokenStream {
|
||||||
let mut tokens = item.into_iter();
|
let mut tokens = item.into_iter();
|
||||||
|
|
1
ewext/noita_api_macro/src/lua_api.json
Normal file
1
ewext/noita_api_macro/src/lua_api.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -9,7 +9,7 @@ use addr_grabber::{grab_addrs, grabbed_fns, grabbed_globals};
|
||||||
use eyre::bail;
|
use eyre::bail;
|
||||||
use lua_bindings::{lua_State, Lua51};
|
use lua_bindings::{lua_State, Lua51};
|
||||||
use lua_state::{LuaState, ValuesOnStack};
|
use lua_state::{LuaState, ValuesOnStack};
|
||||||
use noita::{ntypes::Entity, NoitaPixelRun, ParticleWorldState};
|
use noita::{ntypes::Entity, pixel::NoitaPixelRun, ParticleWorldState};
|
||||||
use noita_api_macro::add_lua_fn;
|
use noita_api_macro::add_lua_fn;
|
||||||
|
|
||||||
mod lua_bindings;
|
mod lua_bindings;
|
||||||
|
|
|
@ -1,95 +1,18 @@
|
||||||
use std::{ffi::c_void, mem};
|
use std::{ffi::c_void, mem};
|
||||||
|
|
||||||
pub(crate) mod ntypes;
|
pub(crate) mod ntypes;
|
||||||
|
pub(crate) mod pixel;
|
||||||
|
|
||||||
mod api {
|
mod api {
|
||||||
noita_api_macro::generate_components!();
|
noita_api_macro::generate_components!();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(packed)]
|
|
||||||
pub(crate) struct NoitaPixelRun {
|
|
||||||
length: u16,
|
|
||||||
material: u16,
|
|
||||||
flags: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from proxy.
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
||||||
pub(crate) struct RawPixel {
|
|
||||||
pub material: u16,
|
|
||||||
pub flags: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from proxy.
|
|
||||||
/// Stores a run of pixels.
|
|
||||||
/// Not specific to Noita side - length is an actual length
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct PixelRun<Pixel> {
|
|
||||||
pub length: u32,
|
|
||||||
pub data: Pixel,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from proxy.
|
|
||||||
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
|
||||||
pub(crate) struct PixelRunner<Pixel> {
|
|
||||||
current_pixel: Option<Pixel>,
|
|
||||||
current_run_len: u32,
|
|
||||||
runs: Vec<PixelRun<Pixel>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Pixel: Eq + Copy> Default for PixelRunner<Pixel> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Pixel: Eq + Copy> PixelRunner<Pixel> {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
current_pixel: None,
|
|
||||||
current_run_len: 0,
|
|
||||||
runs: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn put_pixel(&mut self, pixel: Pixel) {
|
|
||||||
if let Some(current) = self.current_pixel {
|
|
||||||
if pixel != current {
|
|
||||||
self.runs.push(PixelRun {
|
|
||||||
length: self.current_run_len,
|
|
||||||
data: current,
|
|
||||||
});
|
|
||||||
self.current_pixel = Some(pixel);
|
|
||||||
self.current_run_len = 1;
|
|
||||||
} else {
|
|
||||||
self.current_run_len += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.current_pixel = Some(pixel);
|
|
||||||
self.current_run_len = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn build(&mut self) -> &[PixelRun<Pixel>] {
|
|
||||||
if self.current_run_len > 0 {
|
|
||||||
self.runs.push(PixelRun {
|
|
||||||
length: self.current_run_len,
|
|
||||||
data: self.current_pixel.expect("has current pixel"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
&mut self.runs
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self) {
|
|
||||||
self.current_pixel = None;
|
|
||||||
self.current_run_len = 0;
|
|
||||||
self.runs.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct ParticleWorldState {
|
pub(crate) struct ParticleWorldState {
|
||||||
pub(crate) _world_ptr: *mut c_void,
|
pub(crate) _world_ptr: *mut c_void,
|
||||||
pub(crate) chunk_map_ptr: *mut c_void,
|
pub(crate) chunk_map_ptr: *mut c_void,
|
||||||
pub(crate) material_list_ptr: *const c_void,
|
pub(crate) material_list_ptr: *const c_void,
|
||||||
|
|
||||||
pub(crate) runner: PixelRunner<RawPixel>,
|
pub(crate) runner: pixel::PixelRunner<pixel::RawPixel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParticleWorldState {
|
impl ParticleWorldState {
|
||||||
|
@ -130,7 +53,7 @@ impl ParticleWorldState {
|
||||||
start_y: i32,
|
start_y: i32,
|
||||||
end_x: i32,
|
end_x: i32,
|
||||||
end_y: i32,
|
end_y: i32,
|
||||||
mut pixel_runs: *mut NoitaPixelRun,
|
mut pixel_runs: *mut pixel::NoitaPixelRun,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
// Allow compiler to generate better code.
|
// Allow compiler to generate better code.
|
||||||
assert_eq!(start_x % 128, 0);
|
assert_eq!(start_x % 128, 0);
|
||||||
|
@ -140,7 +63,7 @@ impl ParticleWorldState {
|
||||||
|
|
||||||
for y in start_y..end_y {
|
for y in start_y..end_y {
|
||||||
for x in start_x..end_x {
|
for x in start_x..end_x {
|
||||||
let mut raw_pixel = RawPixel {
|
let mut raw_pixel = pixel::RawPixel {
|
||||||
material: 0,
|
material: 0,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
|
|
78
ewext/src/noita/pixel.rs
Normal file
78
ewext/src/noita/pixel.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#[repr(packed)]
|
||||||
|
pub(crate) struct NoitaPixelRun {
|
||||||
|
pub(crate) length: u16,
|
||||||
|
pub(crate) material: u16,
|
||||||
|
pub(crate) flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct RawPixel {
|
||||||
|
pub material: u16,
|
||||||
|
pub flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
/// Stores a run of pixels.
|
||||||
|
/// Not specific to Noita side - length is an actual length
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PixelRun<Pixel> {
|
||||||
|
pub length: u32,
|
||||||
|
pub data: Pixel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
||||||
|
pub(crate) struct PixelRunner<Pixel> {
|
||||||
|
pub(crate) current_pixel: Option<Pixel>,
|
||||||
|
pub(crate) current_run_len: u32,
|
||||||
|
pub(crate) runs: Vec<PixelRun<Pixel>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pixel: Eq + Copy> Default for PixelRunner<Pixel> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pixel: Eq + Copy> PixelRunner<Pixel> {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
current_pixel: None,
|
||||||
|
current_run_len: 0,
|
||||||
|
runs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn put_pixel(&mut self, pixel: Pixel) {
|
||||||
|
if let Some(current) = self.current_pixel {
|
||||||
|
if pixel != current {
|
||||||
|
self.runs.push(PixelRun {
|
||||||
|
length: self.current_run_len,
|
||||||
|
data: current,
|
||||||
|
});
|
||||||
|
self.current_pixel = Some(pixel);
|
||||||
|
self.current_run_len = 1;
|
||||||
|
} else {
|
||||||
|
self.current_run_len += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.current_pixel = Some(pixel);
|
||||||
|
self.current_run_len = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn build(&mut self) -> &[PixelRun<Pixel>] {
|
||||||
|
if self.current_run_len > 0 {
|
||||||
|
self.runs.push(PixelRun {
|
||||||
|
length: self.current_run_len,
|
||||||
|
data: self.current_pixel.expect("has current pixel"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
&mut self.runs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear(&mut self) {
|
||||||
|
self.current_pixel = None;
|
||||||
|
self.current_run_len = 0;
|
||||||
|
self.runs.clear();
|
||||||
|
}
|
||||||
|
}
|
102
scripts/parse_lua_api.py
Normal file
102
scripts/parse_lua_api.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
path = "/home/quant/.local/share/Steam/steamapps/common/Noita/tools_modding/lua_api_documentation.html"
|
||||||
|
|
||||||
|
lines = open(path).readlines()
|
||||||
|
|
||||||
|
lines_iter = iter(lines)
|
||||||
|
|
||||||
|
parsed = []
|
||||||
|
|
||||||
|
def parse_arg(arg_s):
|
||||||
|
if "|" in arg_s:
|
||||||
|
raise ValueError("multiple argument types not supported")
|
||||||
|
other, *default = arg_s.split(" = ", maxsplit=1)
|
||||||
|
if default:
|
||||||
|
default = default[0]
|
||||||
|
else:
|
||||||
|
default = None
|
||||||
|
name, typ = other.split(":", maxsplit=1)
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"typ": typ,
|
||||||
|
"default": default,
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse_ret(ret_s):
|
||||||
|
if not ret_s:
|
||||||
|
return None
|
||||||
|
|
||||||
|
optional = ret_s.endswith("|nil")
|
||||||
|
ret_s = ret_s.removesuffix("|nil")
|
||||||
|
|
||||||
|
if "|" in ret_s:
|
||||||
|
raise ValueError("multiple return types not supported")
|
||||||
|
if "{" in ret_s:
|
||||||
|
raise ValueError("tables in returns not supported")
|
||||||
|
|
||||||
|
typ = other
|
||||||
|
name = None
|
||||||
|
if ":" in other:
|
||||||
|
name, typ = other.split(":", maxsplit=1)
|
||||||
|
|
||||||
|
if name == "entity_id":
|
||||||
|
typ = "entity_id"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"typ": typ,
|
||||||
|
"optional": optional
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ignore = {
|
||||||
|
|
||||||
|
}
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
# 2 lazy 2 parse xml properly
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
line = next(lines_iter)
|
||||||
|
if line.startswith('<th><span class="function">'):
|
||||||
|
fn_line = line.strip()
|
||||||
|
ret_line = next(lines_iter).strip()
|
||||||
|
desc_line = next(lines_iter).strip()
|
||||||
|
|
||||||
|
fn_name, other = fn_line.removeprefix('<th><span class="function">').split('</span>(<span class="field_name">', maxsplit=1)
|
||||||
|
args = other.removesuffix('</span><span class="func">)</span></th>').strip().split(", ")
|
||||||
|
try:
|
||||||
|
args = [parse_arg(arg) for arg in args if ":" in arg]
|
||||||
|
except ValueError as e:
|
||||||
|
skipped += 1
|
||||||
|
print(f"Skipping {fn_name}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
ret = ret_line.removeprefix('<th><span class="field_name">').removesuffix('</span></th></th>').strip()
|
||||||
|
try:
|
||||||
|
ret = parse_ret(ret)
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Skipping {fn_name}: {e}")
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
desc = desc_line.removeprefix('<th><span class="description">').removesuffix('</span></th></th>').strip().replace("</br>", "\n")
|
||||||
|
if not desc:
|
||||||
|
desc = "Nolla forgot to include a description :("
|
||||||
|
|
||||||
|
#print(fn_line, ret_line, desc_line)
|
||||||
|
|
||||||
|
if fn_name not in ignore:
|
||||||
|
#print(fn_name, args, "->", ret)
|
||||||
|
parsed.append({
|
||||||
|
"fn_name": fn_name,
|
||||||
|
"args": args,
|
||||||
|
"desc": desc,
|
||||||
|
})
|
||||||
|
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
print("Total skipped:", skipped)
|
||||||
|
json.dump(parsed, open("ewext/noita_api_macro/src/lua_api.json", "w"), indent=None)
|
Loading…
Add table
Add a link
Reference in a new issue