use std::io::Read; use std::ptr::null_mut; use crate::framework::context::Context; use crate::framework::error::{GameResult, GameError}; use lua_ffi::{c_int, State, ThreadStatus}; use crate::scene::game_scene::GameScene; use crate::scripting::doukutsu::Doukutsu; use crate::shared_game_state::SharedGameState; use crate::framework::filesystem::File; use crate::framework::filesystem; use lua_ffi::types::LuaValue; use crate::common::Rect; use lua_ffi::ffi::lua_State; mod doukutsu; mod scene; pub struct LuaScriptingState { state: Option, state_ptr: *mut SharedGameState, ctx_ptr: *mut Context, game_scene: *mut GameScene, } pub(in crate::scripting) static REF_ERROR: &str = "Reference went out of scope. DO NOT store/use references to game objects outside the event."; pub(in crate::scripting) static DRS_API_GLOBAL: &str = "__doukutsu_rs"; pub(in crate::scripting) static DRS_RUNTIME_GLOBAL: &str = "__doukutsu_rs_runtime_dont_touch"; static BOOT_SCRIPT: &str = include_str!("boot.lua"); pub(in crate::scripting) fn check_status(status: ThreadStatus, state: &mut State) -> GameResult { match status { ThreadStatus::Ok | ThreadStatus::Yield => { return Ok(()); } _ => {} } let error = state.to_str(-1).unwrap_or("???"); match status { ThreadStatus::RuntimeError => Err(GameError::EventLoopError(format!("Lua Runtime Error: {}", error))), ThreadStatus::SyntaxError => Err(GameError::EventLoopError(format!("Lua Syntax Error: {}", error))), ThreadStatus::MemoryError => Err(GameError::EventLoopError(format!("Lua Memory Error: {}", error))), ThreadStatus::MsgHandlerError => Err(GameError::EventLoopError(format!("Lua Message Handler Error: {}", error))), ThreadStatus::FileError => Err(GameError::EventLoopError(format!("Lua File Error: {}", error))), ThreadStatus::Unknown => Err(GameError::EventLoopError(format!("Unknown Error: {}", error))), _ => Ok(()) } } fn print(state: &mut State) -> c_int { if let Some(msg) = state.to_str(1) { log::info!("[Lua] {}", msg); } 0 } impl LuaScriptingState { pub fn new() -> LuaScriptingState { LuaScriptingState { state: None, state_ptr: null_mut(), ctx_ptr: null_mut(), game_scene: null_mut(), } } pub fn update_refs(&mut self, state: *mut SharedGameState, ctx: *mut Context) { self.state_ptr = state; self.ctx_ptr = ctx; } pub fn set_game_scene(&mut self, game_scene: *mut GameScene) { self.game_scene = game_scene; } fn load_script(mut state: &mut State, path: &str, mut script: File) -> bool { let mut buf = Vec::new(); let res = script.read_to_end(&mut buf); if let Err(err) = res { log::warn!("Error reading script {}: {}", path, err); return false; } let name = format!("@{}", path); let res = state.load_buffer(&buf, &name); let res = check_status(res, &mut state); if let Err(err) = res { log::warn!("Error loading script {}: {}", path, err); return false; } state.get_global(DRS_RUNTIME_GLOBAL); state.get_field(-1, "_initializeScript"); state.push_value(-3); let res = state.pcall(1, 0, 0); if let Err((_, err)) = res { log::warn!("Error evaluating script {}: {}", path, err); return false; } log::info!("Successfully loaded Lua script: {}", path); true } pub fn reload_scripts(&mut self, ctx: &mut Context) -> GameResult { let mut state = State::new(); state.open_libs(); state.push(lua_fn!(print)); state.set_global("print"); state.push(Doukutsu { ptr: self as *mut LuaScriptingState }); state.set_global(DRS_API_GLOBAL); log::info!("Initializing Lua scripting engine..."); let res = state.do_string(BOOT_SCRIPT); check_status(res, &mut state)?; if filesystem::exists(ctx, "/drs-scripts/") { let mut script_count = 0; let files = filesystem::read_dir(ctx, "/drs-scripts/")? .filter(|f| f.to_string_lossy().to_lowercase().ends_with(".lua")); for file in files { let path = file.clone(); match filesystem::open(ctx, file) { Ok(script) => { if LuaScriptingState::load_script(&mut state, path.to_string_lossy().as_ref(), script) { script_count += 1; } } Err(err) => { log::warn!("Error opening script {:?}: {}", path, err); } } } if script_count > 0 { log::info!("{} Lua scripts have been loaded.", script_count); } } let modcs_path = "/Scripts/main.lua"; if filesystem::exists(ctx, modcs_path) { log::info!("Loading ModCS main script..."); match filesystem::open(ctx, modcs_path) { Ok(script) => { if !LuaScriptingState::load_script(&mut state, modcs_path, script) { log::warn!("Error loading ModCS main script."); } } Err(err) => { log::warn!("Error opening script {:?}: {}", modcs_path, err); } } } self.state = Some(state); Ok(()) } } impl LuaValue for Rect { fn push_val(self, l: *mut lua_State) { unsafe { lua_ffi::ffi::lua_newtable(l); lua_ffi::ffi::lua_pushinteger(l, self.left as isize); lua_ffi::ffi::lua_rawseti(l, -2, 1); lua_ffi::ffi::lua_pushinteger(l, self.top as isize); lua_ffi::ffi::lua_rawseti(l, -2, 2); lua_ffi::ffi::lua_pushinteger(l, self.right as isize); lua_ffi::ffi::lua_rawseti(l, -2, 3); lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize); lua_ffi::ffi::lua_rawseti(l, -2, 4); } } } impl LuaValue for Rect { fn push_val(self, l: *mut lua_State) { unsafe { lua_ffi::ffi::lua_newtable(l); lua_ffi::ffi::lua_pushinteger(l, self.left as isize); lua_ffi::ffi::lua_rawseti(l, -2, 1); lua_ffi::ffi::lua_pushinteger(l, self.top as isize); lua_ffi::ffi::lua_rawseti(l, -2, 2); lua_ffi::ffi::lua_pushinteger(l, self.right as isize); lua_ffi::ffi::lua_rawseti(l, -2, 3); lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize); lua_ffi::ffi::lua_rawseti(l, -2, 4); } } } impl LuaValue for Rect { fn push_val(self, l: *mut lua_State) { unsafe { lua_ffi::ffi::lua_newtable(l); lua_ffi::ffi::lua_pushinteger(l, self.left as isize); lua_ffi::ffi::lua_rawseti(l, -2, 1); lua_ffi::ffi::lua_pushinteger(l, self.top as isize); lua_ffi::ffi::lua_rawseti(l, -2, 2); lua_ffi::ffi::lua_pushinteger(l, self.right as isize); lua_ffi::ffi::lua_rawseti(l, -2, 3); lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize); lua_ffi::ffi::lua_rawseti(l, -2, 4); } } } impl LuaValue for Rect { fn push_val(self, l: *mut lua_State) { unsafe { lua_ffi::ffi::lua_newtable(l); lua_ffi::ffi::lua_pushnumber(l, self.left as f64); lua_ffi::ffi::lua_rawseti(l, -2, 1); lua_ffi::ffi::lua_pushnumber(l, self.top as f64); lua_ffi::ffi::lua_rawseti(l, -2, 2); lua_ffi::ffi::lua_pushnumber(l, self.right as f64); lua_ffi::ffi::lua_rawseti(l, -2, 3); lua_ffi::ffi::lua_pushnumber(l, self.bottom as f64); lua_ffi::ffi::lua_rawseti(l, -2, 4); } } }