Some things seem to work

This commit is contained in:
IQuant 2024-11-24 23:12:35 +03:00
parent c7b389a389
commit 2a09cbbfaa
7 changed files with 177 additions and 312 deletions

View file

@ -2,6 +2,7 @@ use std::ffi::CString;
use heck::ToSnekCase; use heck::ToSnekCase;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
@ -48,15 +49,13 @@ enum Typ2 {
#[serde(rename = "bool")] #[serde(rename = "bool")]
Bool, Bool,
#[serde(rename = "entity_id")] #[serde(rename = "entity_id")]
EntityId, EntityID,
#[serde(rename = "component_id")] #[serde(rename = "component_id")]
ComponentId, ComponentID,
#[serde(rename = "obj")] #[serde(rename = "obj")]
Obj, Obj,
#[serde(rename = "color")] #[serde(rename = "color")]
Color, Color,
// #[serde(other)]
// Other,
} }
impl Typ2 { impl Typ2 {
@ -64,14 +63,47 @@ impl Typ2 {
match self { match self {
Typ2::Int => quote! {i32}, Typ2::Int => quote! {i32},
Typ2::Number => quote! {f64}, Typ2::Number => quote! {f64},
Typ2::String => quote! {String}, Typ2::String => quote! {Cow<str>},
Typ2::Bool => quote! {bool}, Typ2::Bool => quote! {bool},
Typ2::EntityId => quote! {EntityID}, Typ2::EntityID => quote! {EntityID},
Typ2::ComponentId => quote!(ComponentID), Typ2::ComponentID => quote!(ComponentID),
Typ2::Obj => quote! {Obj}, Typ2::Obj => quote! {Obj},
Typ2::Color => quote!(Color), Typ2::Color => quote!(Color),
} }
} }
fn as_rust_type_return(&self) -> proc_macro2::TokenStream {
match self {
Typ2::String => quote! {Cow<'static, str>},
_ => self.as_rust_type(),
}
}
fn generate_lua_push(&self, arg_name: Ident) -> proc_macro2::TokenStream {
match self {
Typ2::Int => quote! {lua.push_integer(#arg_name as isize)},
Typ2::Number => quote! {lua.push_number(#arg_name)},
Typ2::String => quote! {lua.push_string(&#arg_name)},
Typ2::Bool => quote! {lua.push_bool(#arg_name)},
Typ2::EntityID => quote! {lua.push_integer(#arg_name.0 as isize)},
Typ2::ComponentID => quote! {lua.push_integer(#arg_name.0 as isize)},
Typ2::Obj => quote! { todo!() },
Typ2::Color => quote! { todo!() },
}
}
fn generate_lua_get(&self, index: i32) -> proc_macro2::TokenStream {
match self {
Typ2::Int => quote! {lua.to_integer(#index) as i32},
Typ2::Number => quote! {lua.to_number(#index)},
Typ2::String => quote! { lua.to_string(#index)?.into() },
Typ2::Bool => quote! {lua.to_bool(#index)},
Typ2::EntityID => quote! {EntityID(lua.to_integer(#index))},
Typ2::ComponentID => quote! {ComponentID(lua.to_integer(#index))},
Typ2::Obj => quote! { todo!() },
Typ2::Color => quote! { todo!() },
}
}
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -90,8 +122,15 @@ struct Component {
#[derive(Deserialize)] #[derive(Deserialize)]
struct FnArg { struct FnArg {
name: String, name: String,
typ: Typ2, // TODO typ: Typ2,
default: Option<String>, // default: Option<String>,
}
#[derive(Deserialize)]
struct FnRet {
// name: String,
typ: Typ2,
// optional: bool,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -99,6 +138,7 @@ struct ApiFn {
fn_name: String, fn_name: String,
desc: String, desc: String,
args: Vec<FnArg>, args: Vec<FnArg>,
rets: Vec<FnRet>,
} }
#[proc_macro] #[proc_macro]
@ -162,10 +202,47 @@ fn generate_code_for_api_fn(api_fn: ApiFn) -> proc_macro2::TokenStream {
} }
}); });
let put_args = api_fn.args.iter().map(|arg| {
let arg_name = format_ident!("{}", arg.name);
arg.typ.generate_lua_push(arg_name)
});
let ret_type = if api_fn.rets.is_empty() {
quote! { () }
} else {
// TODO support for more than one return value.
// if api_fn.rets.len() == 1 {
let ret = api_fn.rets.first().unwrap();
ret.typ.as_rust_type_return()
// } else {
// quote! { ( /* todo */) }
// }
};
let ret_expr = if api_fn.rets.is_empty() {
quote! { () }
} else {
// TODO support for more than one return value.
let ret = api_fn.rets.first().unwrap();
ret.typ.generate_lua_get(1)
};
let fn_name_c = name_to_c_literal(api_fn.fn_name);
let arg_count = api_fn.args.len() as i32;
let ret_count = api_fn.rets.len() as i32;
quote! { quote! {
#[doc = #fn_doc] #[doc = #fn_doc]
pub(crate) fn #fn_name(#(#args,)*) { pub(crate) fn #fn_name(#(#args,)*) -> eyre::Result<#ret_type> {
let lua = LuaState::current()?;
lua.get_global(#fn_name_c);
#(#put_args;)*
lua.call(#arg_count, #ret_count);
Ok(#ret_expr)
} }
} }
} }
@ -185,7 +262,7 @@ pub fn add_lua_fn(item: TokenStream) -> TokenStream {
let fn_name = tokens.next().unwrap().to_string(); let fn_name = tokens.next().unwrap().to_string();
let fn_name_ident = format_ident!("{fn_name}"); let fn_name_ident = format_ident!("{fn_name}");
let bridge_fn_name = format_ident!("{fn_name}_lua_bridge"); let bridge_fn_name = format_ident!("{fn_name}_lua_bridge");
let fn_name_c = proc_macro2::Literal::c_string(CString::new(fn_name).unwrap().as_c_str()); let fn_name_c = name_to_c_literal(fn_name);
quote! { quote! {
unsafe extern "C" fn #bridge_fn_name(lua: *mut lua_State) -> c_int { unsafe extern "C" fn #bridge_fn_name(lua: *mut lua_State) -> c_int {
let lua_state = LuaState::new(lua); let lua_state = LuaState::new(lua);
@ -198,3 +275,7 @@ pub fn add_lua_fn(item: TokenStream) -> TokenStream {
} }
.into() .into()
} }
fn name_to_c_literal(name: String) -> proc_macro2::Literal {
proc_macro2::Literal::c_string(CString::new(name).unwrap().as_c_str())
}

View file

@ -737,34 +737,6 @@
} }
] ]
}, },
{
"fn_name": "ComponentObjectGetValue2",
"args": [
{
"name": "component_id",
"typ": "component_id",
"default": null
},
{
"name": "object_name",
"typ": "string",
"default": null
},
{
"name": "field_name",
"typ": "string",
"default": null
}
],
"desc": "Returns one or many values matching the type or subtypes of the requested field in a component subobject. Reports error and returns nil if the field type is not supported or 'object_name' is not a metaobject.",
"rets": [
{
"name": null,
"typ": "multiple types",
"optional": true
}
]
},
{ {
"fn_name": "ComponentGetVectorSize", "fn_name": "ComponentGetVectorSize",
"args": [ "args": [
@ -1128,34 +1100,6 @@
"desc": "Nolla forgot to include a description :(", "desc": "Nolla forgot to include a description :(",
"rets": [] "rets": []
}, },
{
"fn_name": "GetParallelWorldPosition",
"args": [
{
"name": "world_pos_x",
"typ": "number",
"default": null
},
{
"name": "world_pos_y",
"typ": "number",
"default": null
}
],
"desc": "x = 0 normal world, -1 is first west world, +1 is first east world, if y < 0 it is sky, if y > 0 it is hell",
"rets": [
{
"name": null,
"typ": "x",
"optional": false
},
{
"name": null,
"typ": " y",
"optional": false
}
]
},
{ {
"fn_name": "BiomeMapLoad_KeepPlayer", "fn_name": "BiomeMapLoad_KeepPlayer",
"args": [ "args": [
@ -1173,57 +1117,6 @@
"desc": "Nolla forgot to include a description :(", "desc": "Nolla forgot to include a description :(",
"rets": [] "rets": []
}, },
{
"fn_name": "BiomeGetValue",
"args": [
{
"name": "filename",
"typ": "string",
"default": null
},
{
"name": "field_name",
"typ": "string",
"default": null
}
],
"desc": "Can be used to read biome configs. Returns one or many values matching the type or subtypes of the requested field. Reports error and returns nil if the field type is not supported or field was not found.",
"rets": [
{
"name": null,
"typ": "multiple types",
"optional": true
}
]
},
{
"fn_name": "BiomeMaterialGetValue",
"args": [
{
"name": "filename",
"typ": "string",
"default": null
},
{
"name": "material_name",
"typ": "string",
"default": null
},
{
"name": "field_name",
"typ": "string",
"default": null
}
],
"desc": "Can be used to read biome config MaterialComponents during initialization. Returns the given value in the first found MaterialComponent with matching material_name. See biome_modifiers.lua for an usage example.",
"rets": [
{
"name": null,
"typ": "multiple types",
"optional": true
}
]
},
{ {
"fn_name": "GameIsIntroPlaying", "fn_name": "GameIsIntroPlaying",
"args": [], "args": [],
@ -1661,8 +1554,8 @@
"rets": [ "rets": [
{ {
"name": null, "name": null,
"typ": "bool -", "typ": "bool",
"optional": false "optional": true
} }
] ]
}, },
@ -2415,29 +2308,6 @@
} }
] ]
}, },
{
"fn_name": "EntityGetFirstHitboxCenter",
"args": [
{
"name": "entity_id",
"typ": "entity_id",
"default": null
}
],
"desc": "Returns the centroid of first enabled HitboxComponent found in entity, the position of the entity if no hitbox is found, or nil if the entity does not exist. All returned positions are in world coordinates.",
"rets": [
{
"name": "(x",
"typ": "number",
"optional": false
},
{
"name": "y",
"typ": "number)",
"optional": true
}
]
},
{ {
"fn_name": "Raytrace", "fn_name": "Raytrace",
"args": [ "args": [
@ -3331,34 +3201,6 @@
} }
] ]
}, },
{
"fn_name": "InputGetJoystickAnalogStick",
"args": [
{
"name": "joystick_index",
"typ": "int",
"default": null
},
{
"name": "stick_id",
"typ": "int",
"default": "0"
}
],
"desc": "Debugish function - returns analog stick positions (-1, +1). stick_id 0 = left, 1 = right, Does not depend on state. E.g. player could be in menus. See data/scripts/debug/keycodes.lua for the constants",
"rets": [
{
"name": null,
"typ": "float x",
"optional": false
},
{
"name": null,
"typ": " float y",
"optional": false
}
]
},
{ {
"fn_name": "IsPlayer", "fn_name": "IsPlayer",
"args": [ "args": [
@ -4030,7 +3872,7 @@
"rets": [ "rets": [
{ {
"name": null, "name": null,
"typ": "name", "typ": "string",
"optional": false "optional": false
} }
] ]
@ -4217,64 +4059,6 @@
} }
] ]
}, },
{
"fn_name": "PhysicsAddBodyImage",
"args": [
{
"name": "entity_id",
"typ": "entity_id",
"default": null
},
{
"name": "image_file",
"typ": "string",
"default": null
},
{
"name": "material",
"typ": "string",
"default": "\"\""
},
{
"name": "offset_x",
"typ": "number",
"default": "0"
},
{
"name": "offset_y",
"typ": "number",
"default": "0"
},
{
"name": "centered",
"typ": "bool",
"default": "false"
},
{
"name": "is_circle",
"typ": "bool",
"default": "false"
},
{
"name": "material_image_file",
"typ": "string",
"default": "\"\""
},
{
"name": "use_image_as_colors",
"typ": "bool",
"default": "true"
}
],
"desc": "Does not work with PhysicsBody2Component. Returns the id of the created physics body.",
"rets": [
{
"name": null,
"typ": "int_body_id",
"optional": false
}
]
},
{ {
"fn_name": "PhysicsAddBodyCreateBox", "fn_name": "PhysicsAddBodyCreateBox",
"args": [ "args": [
@ -4833,24 +4617,6 @@
"desc": "Nolla forgot to include a description :(", "desc": "Nolla forgot to include a description :(",
"rets": [] "rets": []
}, },
{
"fn_name": "PhysicsBodyIDGetBodyAABB",
"args": [
{
"name": "physics_body_id",
"typ": "int",
"default": null
}
],
"desc": "Nolla forgot to include a description :(",
"rets": [
{
"name": null,
"typ": "nil",
"optional": false
}
]
},
{ {
"fn_name": "PhysicsBody2InitFromComponents", "fn_name": "PhysicsBody2InitFromComponents",
"args": [ "args": [
@ -5074,7 +4840,7 @@
"rets": [ "rets": [
{ {
"name": null, "name": null,
"typ": "bool_is_new", "typ": "bool",
"optional": false "optional": false
} }
] ]
@ -6041,59 +5807,6 @@
} }
] ]
}, },
{
"fn_name": "GuiTextInput",
"args": [
{
"name": "gui",
"typ": "obj",
"default": null
},
{
"name": "id",
"typ": "int",
"default": null
},
{
"name": "x",
"typ": "number",
"default": null
},
{
"name": "y",
"typ": "number",
"default": null
},
{
"name": "text",
"typ": "string",
"default": null
},
{
"name": "width",
"typ": "number",
"default": null
},
{
"name": "max_length",
"typ": "int",
"default": null
},
{
"name": "allowed_characters",
"typ": "string",
"default": "\"\""
}
],
"desc": "'allowed_characters' should consist only of ASCII characters. This is not intended to be outside mod settings menu, and might bug elsewhere.",
"rets": [
{
"name": null,
"typ": "new_text",
"optional": false
}
]
},
{ {
"fn_name": "GuiBeginAutoBox", "fn_name": "GuiBeginAutoBox",
"args": [ "args": [
@ -6826,7 +6539,7 @@
"rets": [ "rets": [
{ {
"name": null, "name": null,
"typ": "boolean", "typ": "bool",
"optional": false "optional": false
} }
] ]

View file

@ -99,6 +99,15 @@ fn on_world_initialized(lua: LuaState) {
grab_addrs(lua); grab_addrs(lua);
} }
fn test_fn(_lua: LuaState) -> eyre::Result<()> {
let player = noita::api::raw::entity_get_closest_with_tag(0.0, 0.0, "player_unit".into())?;
noita::api::raw::entity_set_transform(player, 0.0, 0.0, 0.0, 1.0, 1.0)?;
// noita::api::raw::game_print("Test game print".into())?;
Ok(())
}
/// # Safety /// # Safety
/// ///
/// Only gets called by lua when loading a module. /// Only gets called by lua when loading a module.
@ -112,6 +121,7 @@ pub unsafe extern "C" fn luaopen_ewext0(lua: *mut lua_State) -> c_int {
add_lua_fn!(encode_area); add_lua_fn!(encode_area);
add_lua_fn!(make_ephemerial); add_lua_fn!(make_ephemerial);
add_lua_fn!(on_world_initialized); add_lua_fn!(on_world_initialized);
add_lua_fn!(test_fn);
} }
println!("Initializing ewext - Ok"); println!("Initializing ewext - Ok");
1 1

View file

@ -1,10 +1,10 @@
use std::{ use std::{
cell::Cell, cell::Cell,
ffi::{c_char, c_int, CStr}, ffi::{c_char, c_int, CStr},
mem, mem, slice,
}; };
use eyre::OptionExt; use eyre::{bail, Context, OptionExt};
use crate::{ use crate::{
lua_bindings::{lua_CFunction, lua_State, LUA_GLOBALSINDEX}, lua_bindings::{lua_CFunction, lua_State, LUA_GLOBALSINDEX},
@ -20,6 +20,7 @@ pub(crate) struct LuaState {
lua: *mut lua_State, lua: *mut lua_State,
} }
#[expect(dead_code)]
impl LuaState { impl LuaState {
pub(crate) fn new(lua: *mut lua_State) -> Self { pub(crate) fn new(lua: *mut lua_State) -> Self {
Self { lua } Self { lua }
@ -44,16 +45,52 @@ impl LuaState {
unsafe { LUA.lua_tointeger(self.lua, index) } unsafe { LUA.lua_tointeger(self.lua, index) }
} }
pub(crate) fn to_number(&self, index: i32) -> f64 {
unsafe { LUA.lua_tonumber(self.lua, index) }
}
pub(crate) fn to_bool(&self, index: i32) -> bool {
unsafe { LUA.lua_toboolean(self.lua, index) > 0 }
}
pub(crate) fn to_string(&self, index: i32) -> eyre::Result<String> {
let mut size = 0;
let buf = unsafe { LUA.lua_tolstring(self.lua, index, &mut size) };
if buf.is_null() {
bail!("Expected a string, but got a null pointer");
}
let slice = unsafe { slice::from_raw_parts(buf as *const u8, size) };
Ok(String::from_utf8(slice.to_owned())
.context("Attempting to get lua string, expecting it to be utf-8")?)
}
pub(crate) fn to_cfunction(&self, index: i32) -> lua_CFunction { pub(crate) fn to_cfunction(&self, index: i32) -> lua_CFunction {
unsafe { LUA.lua_tocfunction(self.lua, index) } unsafe { LUA.lua_tocfunction(self.lua, index) }
} }
pub(crate) fn push_number(&self, val: f64) {
unsafe { LUA.lua_pushnumber(self.lua, val) };
}
pub(crate) fn push_integer(&self, val: isize) {
unsafe { LUA.lua_pushinteger(self.lua, val) };
}
pub(crate) fn push_bool(&self, val: bool) {
unsafe { LUA.lua_pushboolean(self.lua, val as i32) };
}
pub(crate) fn push_string(&self, s: &str) { pub(crate) fn push_string(&self, s: &str) {
unsafe { unsafe {
LUA.lua_pushstring(self.lua, s.as_bytes().as_ptr() as *const c_char); LUA.lua_pushlstring(self.lua, s.as_bytes().as_ptr() as *const c_char, s.len());
} }
} }
pub(crate) fn call(&self, nargs: i32, nresults: i32) {
unsafe { LUA.lua_call(self.lua, nargs, nresults) };
}
pub(crate) fn get_global(&self, name: &CStr) { pub(crate) fn get_global(&self, name: &CStr) {
unsafe { LUA.lua_getfield(self.lua, LUA_GLOBALSINDEX, name.as_ptr()) }; unsafe { LUA.lua_getfield(self.lua, LUA_GLOBALSINDEX, name.as_ptr()) };
} }

View file

@ -3,18 +3,21 @@ use std::{ffi::c_void, mem};
pub(crate) mod ntypes; pub(crate) mod ntypes;
pub(crate) mod pixel; pub(crate) mod pixel;
mod api { pub(crate) mod api {
struct EntityID(u32); pub(crate) struct EntityID(isize);
struct ComponentID(u32); pub(crate) struct ComponentID(isize);
struct Obj(usize); pub(crate) struct Obj(usize);
struct Color(u32); pub(crate) struct Color(u32);
noita_api_macro::generate_components!(); noita_api_macro::generate_components!();
mod raw { pub(crate) mod raw {
use super::{Color, ComponentID, EntityID, Obj}; use super::{Color, ComponentID, EntityID, Obj};
use std::borrow::Cow;
use crate::LuaState;
noita_api_macro::generate_api!(); noita_api_macro::generate_api!();
} }

View file

@ -33,6 +33,7 @@ function module.on_local_player_spawn()
end end
end end
EntitySetTransform(GameGetWorldStateEntity(), 0, 0) EntitySetTransform(GameGetWorldStateEntity(), 0, 0)
end end
function module.on_world_update() function module.on_world_update()
@ -40,6 +41,7 @@ function module.on_world_update()
oh_another_world_state(GameGetWorldStateEntity()) oh_another_world_state(GameGetWorldStateEntity())
initial_world_state_entity = GameGetWorldStateEntity() initial_world_state_entity = GameGetWorldStateEntity()
end end
ewext.test_fn()
end end
return module return module

View file

@ -9,6 +9,8 @@ lines_iter = iter(lines)
parsed = [] parsed = []
def maybe_map_types(name, typ): def maybe_map_types(name, typ):
if typ == "multiple types":
raise ValueError("no 'multiple types' either")
if name == "entity_id": if name == "entity_id":
typ = "entity_id" typ = "entity_id"
if name == "component_id": if name == "component_id":
@ -19,6 +21,12 @@ def maybe_map_types(name, typ):
typ = "color" typ = "color"
if typ == "uint32": if typ == "uint32":
typ = "color" typ = "color"
if typ == "name":
typ = "string"
if typ == "bool_is_new":
typ = "bool"
if typ == "boolean":
typ = "bool"
return typ return typ
def parse_arg(arg_s): def parse_arg(arg_s):
@ -63,6 +71,10 @@ def parse_ret(ret_s):
if ":" in ret_s: if ":" in ret_s:
name, typ = ret_s.split(":", maxsplit=1) name, typ = ret_s.split(":", maxsplit=1)
if typ.endswith(" -"):
optional = True
typ = typ.removesuffix(" -")
typ = maybe_map_types(name, typ) typ = maybe_map_types(name, typ)
return { return {
@ -73,8 +85,15 @@ def parse_ret(ret_s):
ignore = { ignore = {
# Those have some specifics that make generic way of handling things not work on them
"PhysicsApplyForceOnArea", "PhysicsApplyForceOnArea",
"GetRandomActionWithType", "GetRandomActionWithType",
"GetParallelWorldPosition",
"EntityGetFirstHitboxCenter",
"InputGetJoystickAnalogStick",
"PhysicsAddBodyImage",
"PhysicsBodyIDGetBodyAABB",
"GuiTextInput",
} }
skipped = 0 skipped = 0
deprecated = 0 deprecated = 0