mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03: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)]
|
||||
struct Field {
|
||||
field: String,
|
||||
|
@ -49,6 +57,21 @@ struct Component {
|
|||
name: String,
|
||||
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]
|
||||
pub fn generate_components(_item: TokenStream) -> TokenStream {
|
||||
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]
|
||||
pub fn add_lua_fn(item: TokenStream) -> TokenStream {
|
||||
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 lua_bindings::{lua_State, Lua51};
|
||||
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;
|
||||
|
||||
mod lua_bindings;
|
||||
|
|
|
@ -1,95 +1,18 @@
|
|||
use std::{ffi::c_void, mem};
|
||||
|
||||
pub(crate) mod ntypes;
|
||||
pub(crate) mod pixel;
|
||||
|
||||
mod api {
|
||||
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) _world_ptr: *mut c_void,
|
||||
pub(crate) chunk_map_ptr: *mut c_void,
|
||||
pub(crate) material_list_ptr: *const c_void,
|
||||
|
||||
pub(crate) runner: PixelRunner<RawPixel>,
|
||||
pub(crate) runner: pixel::PixelRunner<pixel::RawPixel>,
|
||||
}
|
||||
|
||||
impl ParticleWorldState {
|
||||
|
@ -130,7 +53,7 @@ impl ParticleWorldState {
|
|||
start_y: i32,
|
||||
end_x: i32,
|
||||
end_y: i32,
|
||||
mut pixel_runs: *mut NoitaPixelRun,
|
||||
mut pixel_runs: *mut pixel::NoitaPixelRun,
|
||||
) -> usize {
|
||||
// Allow compiler to generate better code.
|
||||
assert_eq!(start_x % 128, 0);
|
||||
|
@ -140,7 +63,7 @@ impl ParticleWorldState {
|
|||
|
||||
for y in start_y..end_y {
|
||||
for x in start_x..end_x {
|
||||
let mut raw_pixel = RawPixel {
|
||||
let mut raw_pixel = pixel::RawPixel {
|
||||
material: 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