use bevy::{ecs::event::Events, prelude::*}; use bevy_console::{AddConsoleCommand, ConsoleCommand, ConsolePlugin, PrintConsoleLine}; use bevy_mod_scripting::prelude::*; use clap::Parser; use std::sync::Mutex; #[derive(Default)] pub struct LuaAPIProvider; /// the custom Lua api, world is provided via a global pointer, /// and callbacks are defined only once at script creation impl APIProvider for LuaAPIProvider { type APITarget = Mutex; type DocTarget = LuaDocFragment; type ScriptContext = Mutex; fn attach_api(&mut self, ctx: &mut Self::APITarget) -> Result<(), ScriptError> { // callbacks can receive any `ToLuaMulti` arguments, here '()' and // return any `FromLuaMulti` arguments, here a `usize` // check the Rlua documentation for more details let ctx = ctx.get_mut().unwrap(); ctx.globals() .set( "print_to_console", ctx.create_function(|ctx, msg: String| { // retrieve the world pointer let world = ctx.get_world()?; let mut world = world.write(); let mut events: Mut> = world.get_resource_mut().unwrap(); events.send(PrintConsoleLine { line: msg }); // return something Ok(()) }) .map_err(ScriptError::new_other)?, ) .map_err(ScriptError::new_other)?; Ok(()) } fn setup_script( &mut self, _: &ScriptData, _: &mut Self::ScriptContext, ) -> Result<(), ScriptError> { Ok(()) } } /// sends updates to script host which are then handled by the scripts /// in their designated system sets pub fn trigger_on_update_lua(mut w: PriorityEventWriter>) { let event = LuaEvent { hook_name: "on_update".to_string(), args: (), recipients: Recipients::All, }; w.send(event, 0); } pub fn forward_script_err_to_console( mut r: EventReader, mut w: EventWriter, ) { for e in r.read() { w.send(PrintConsoleLine { line: format!("ERROR:{}", e.error), }); } } // we use bevy-debug-console to demonstrate how this can fit in in the runtime of a game // note that using just the entity id instead of the full Entity has issues, // but since we aren't despawning/spawning entities this works in our case #[derive(Parser, ConsoleCommand)] #[command(name = "run_script")] ///Runs a Lua script from the `assets/scripts` directory pub struct RunScriptCmd { /// the relative path to the script, e.g.: `/hello.lua` for a script located in `assets/scripts/hello.lua` pub path: String, /// the entity id to attach this script to pub entity: Option, } pub fn run_script_cmd( mut log: ConsoleCommand, server: Res, mut commands: Commands, mut existing_scripts: Query<&mut ScriptCollection>, ) { if let Some(Ok(RunScriptCmd { path, entity })) = log.take() { let handle = server.load::(&format!("scripts/{}", &path)); match entity { Some(e) => { if let Ok(mut scripts) = existing_scripts.get_mut(Entity::from_raw(e)) { info!("Creating script: scripts/{} {:?}", &path, e); scripts.scripts.push(Script::::new(path, handle)); } else { log.reply_failed("Something went wrong".to_string()); }; } None => { info!("Creating script: scripts/{}", &path); commands.spawn(()).insert(ScriptCollection:: { scripts: vec![Script::::new(path, handle)], }); } }; } } pub fn delete_script_cmd( mut log: ConsoleCommand, mut scripts: Query<(Entity, &mut ScriptCollection)>, ) { if let Some(Ok(DeleteScriptCmd { name, entity_id })) = log.take() { for (e, mut s) in scripts.iter_mut() { if e.index() == entity_id { let old_len = s.scripts.len(); s.scripts.retain(|s| s.name() != name); if old_len > s.scripts.len() { log.reply_ok(format!("Deleted script {}, on entity: {}", name, entity_id)); } else { log.reply_failed(format!( "Entity {} did own a script named: {}", entity_id, name )) }; return; } } log.reply_failed("Could not find given entity ID with a script") } } #[derive(Parser, ConsoleCommand)] #[command(name = "delete_script")] ///Runs a Lua script from the `assets/scripts` directory pub struct DeleteScriptCmd { /// the name of the script pub name: String, /// the entity the script is attached to pub entity_id: u32, } fn main() -> std::io::Result<()> { let mut app = App::new(); app.add_plugins(DefaultPlugins) .add_plugins(ScriptingPlugin) .add_plugins(ConsolePlugin) // register bevy_console commands .add_console_command::(run_script_cmd) .add_console_command::(delete_script_cmd) // choose and register the script hosts you want to use .add_script_host::>(PostUpdate) .add_api_provider::>(Box::new(LuaAPIProvider)) .add_api_provider::>(Box::new(LuaCoreBevyAPIProvider)) .add_script_handler::, 0, 0>(PostUpdate) // add your systems .add_systems(Update, trigger_on_update_lua) .add_systems(Update, forward_script_err_to_console); info!("press '~' to open the console. Type in `run_script \"console_integration.lua\"` to run example script!"); app.run(); Ok(()) }