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 proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{format_ident, quote};
use serde::Deserialize;
@ -48,15 +49,13 @@ enum Typ2 {
#[serde(rename = "bool")]
Bool,
#[serde(rename = "entity_id")]
EntityId,
EntityID,
#[serde(rename = "component_id")]
ComponentId,
ComponentID,
#[serde(rename = "obj")]
Obj,
#[serde(rename = "color")]
Color,
// #[serde(other)]
// Other,
}
impl Typ2 {
@ -64,14 +63,47 @@ impl Typ2 {
match self {
Typ2::Int => quote! {i32},
Typ2::Number => quote! {f64},
Typ2::String => quote! {String},
Typ2::String => quote! {Cow<str>},
Typ2::Bool => quote! {bool},
Typ2::EntityId => quote! {EntityID},
Typ2::ComponentId => quote!(ComponentID),
Typ2::EntityID => quote! {EntityID},
Typ2::ComponentID => quote!(ComponentID),
Typ2::Obj => quote! {Obj},
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)]
@ -90,8 +122,15 @@ struct Component {
#[derive(Deserialize)]
struct FnArg {
name: String,
typ: Typ2, // TODO
default: Option<String>,
typ: Typ2,
// default: Option<String>,
}
#[derive(Deserialize)]
struct FnRet {
// name: String,
typ: Typ2,
// optional: bool,
}
#[derive(Deserialize)]
@ -99,6 +138,7 @@ struct ApiFn {
fn_name: String,
desc: String,
args: Vec<FnArg>,
rets: Vec<FnRet>,
}
#[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! {
#[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_ident = format_ident!("{fn_name}");
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! {
unsafe extern "C" fn #bridge_fn_name(lua: *mut lua_State) -> c_int {
let lua_state = LuaState::new(lua);
@ -198,3 +275,7 @@ pub fn add_lua_fn(item: TokenStream) -> TokenStream {
}
.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",
"args": [
@ -1128,34 +1100,6 @@
"desc": "Nolla forgot to include a description :(",
"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",
"args": [
@ -1173,57 +1117,6 @@
"desc": "Nolla forgot to include a description :(",
"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",
"args": [],
@ -1661,8 +1554,8 @@
"rets": [
{
"name": null,
"typ": "bool -",
"optional": false
"typ": "bool",
"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",
"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",
"args": [
@ -4030,7 +3872,7 @@
"rets": [
{
"name": null,
"typ": "name",
"typ": "string",
"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",
"args": [
@ -4833,24 +4617,6 @@
"desc": "Nolla forgot to include a description :(",
"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",
"args": [
@ -5074,7 +4840,7 @@
"rets": [
{
"name": null,
"typ": "bool_is_new",
"typ": "bool",
"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",
"args": [
@ -6826,7 +6539,7 @@
"rets": [
{
"name": null,
"typ": "boolean",
"typ": "bool",
"optional": false
}
]

View file

@ -99,6 +99,15 @@ fn on_world_initialized(lua: LuaState) {
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
///
/// 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!(make_ephemerial);
add_lua_fn!(on_world_initialized);
add_lua_fn!(test_fn);
}
println!("Initializing ewext - Ok");
1

View file

@ -1,10 +1,10 @@
use std::{
cell::Cell,
ffi::{c_char, c_int, CStr},
mem,
mem, slice,
};
use eyre::OptionExt;
use eyre::{bail, Context, OptionExt};
use crate::{
lua_bindings::{lua_CFunction, lua_State, LUA_GLOBALSINDEX},
@ -20,6 +20,7 @@ pub(crate) struct LuaState {
lua: *mut lua_State,
}
#[expect(dead_code)]
impl LuaState {
pub(crate) fn new(lua: *mut lua_State) -> Self {
Self { lua }
@ -44,16 +45,52 @@ impl LuaState {
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 {
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) {
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) {
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 pixel;
mod api {
struct EntityID(u32);
struct ComponentID(u32);
pub(crate) mod api {
pub(crate) struct EntityID(isize);
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!();
mod raw {
pub(crate) mod raw {
use super::{Color, ComponentID, EntityID, Obj};
use std::borrow::Cow;
use crate::LuaState;
noita_api_macro::generate_api!();
}