diff --git a/src/fighter/attack.rs b/src/fighter/attack.rs index bec1dff..a1c54d2 100644 --- a/src/fighter/attack.rs +++ b/src/fighter/attack.rs @@ -1,12 +1,8 @@ +use bevy::reflect::Reflect; +use serde::{Deserialize, Serialize}; + use crate::math::{int::FGi32, vec3::FGVec3}; - - -pub struct FighterMoveManifest { - script_path: String, - -} - -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct FighterDamage(FGi32); impl FighterDamage { @@ -15,7 +11,7 @@ impl FighterDamage { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum KnockbackType { Normal, Fixed @@ -31,7 +27,7 @@ impl KnockbackType { } } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Knockback(FGi32); impl Knockback { @@ -40,7 +36,7 @@ impl Knockback { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum AttackAngle { Normal(FGi32), Sakurai @@ -63,7 +59,8 @@ impl AttackAngle { } } -#[derive(Debug)] +#[derive(Debug, Clone, Reflect, Serialize, Deserialize)] +#[reflect(opaque)] pub struct AttackHitbox { pub id: u32, pub bone: String, diff --git a/src/lib.rs b/src/lib.rs index 99b71db..4d2f484 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,7 @@ impl Plugin for GamePlugin { input::FighterInputPlugin, fighter::FighterPlugin, fighter::baked_animation::BakedAnimationPlugin, + scripting::importer::FighterScriptImportPlugin, stage::StagePlugin, MatchCameraPlugin, bevy_common_assets::ron::RonAssetPlugin::::new(&["fighter.ron"]), diff --git a/src/scripting/importer.rs b/src/scripting/importer.rs new file mode 100644 index 0000000..2221e90 --- /dev/null +++ b/src/scripting/importer.rs @@ -0,0 +1,121 @@ + +use std::io; + +use bevy::{asset::{AssetLoader, AssetPath, AsyncReadExt, AsyncWriteExt, LoadContext, io::{Reader, Writer}, processor::LoadTransformAndSave, saver::{AssetSaver, SavedAsset}, transformer::{AssetTransformer, TransformedAsset}}, prelude::*}; + +use crate::scripting::{FighterAttackScript, move_compiler}; + +#[derive(Default, TypePath)] +struct FighterAttackScriptSourceLoader; + +#[derive(Asset, TypePath)] +struct FighterAttackScriptContents(String); + +impl AssetLoader for FighterAttackScriptSourceLoader { + type Asset = FighterAttackScriptContents; + type Settings = (); + type Error = io::Error; + + async fn load( + &self, + reader: &mut dyn Reader, + _: &Self::Settings, + _: &mut LoadContext<'_>, + ) -> Result { + let mut contents = String::default(); + reader.read_to_string(&mut contents).await?; + Ok(FighterAttackScriptContents(contents)) + } + + fn extensions(&self) -> &[&str] { + &["attack.rhai"] + } +} + +#[derive(Default, TypePath)] +struct FighterAttackScriptTransformer; + +impl AssetTransformer for FighterAttackScriptTransformer { + type AssetInput = FighterAttackScriptContents; + type AssetOutput = FighterAttackScript; + type Settings = (); + type Error = String; + + async fn transform<'a>( + &'a self, + source: TransformedAsset, + _: &'a Self::Settings, + ) -> Result, Self::Error> { + let output = move_compiler::compile_script(&source.0)?; + Ok(source.replace_asset(output)) + } +} + + +#[derive(Default, TypePath)] +struct FighterAttackScriptLoader; + +impl AssetLoader for FighterAttackScriptLoader { + type Asset = FighterAttackScript; + type Settings = (); + type Error = io::Error; + + async fn load( + &self, + reader: &mut dyn Reader, + _: &Self::Settings, + _: &mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + bincode::deserialize(&bytes) + .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error)) + } + + fn extensions(&self) -> &[&str] { + &["attack.rhai"] + } +} + +#[derive(Default, TypePath)] +struct FighterAttackScriptSaver; + +impl AssetSaver for FighterAttackScriptSaver { + type Asset = FighterAttackScript; + type Settings = (); + type OutputLoader = FighterAttackScriptLoader; + type Error = io::Error; + + async fn save( + &self, + writer: &mut Writer, + asset: SavedAsset<'_, '_, Self::Asset>, + _: &Self::Settings, + _: AssetPath<'_>, + ) -> Result<(), Self::Error> { + let bytes = bincode::serialize(&*asset) + .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; + writer.write_all(&bytes).await + } +} + +type FighterAttackScriptProcessor = LoadTransformAndSave< + FighterAttackScriptSourceLoader, + FighterAttackScriptTransformer, + FighterAttackScriptSaver, +>; + +pub struct FighterScriptImportPlugin; + +impl Plugin for FighterScriptImportPlugin { + fn build(&self, app: &mut App) { + app.init_asset::() + .register_asset_loader(FighterAttackScriptSourceLoader) + .register_asset_loader(FighterAttackScriptLoader) + .register_asset_processor(FighterAttackScriptProcessor::new( + FighterAttackScriptTransformer, + FighterAttackScriptSaver, + )) + .set_default_asset_processor::("attack.rhai"); + } +} \ No newline at end of file diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index 4811aec..dffda8e 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -1,23 +1,17 @@ -use bevy::ecs::name::HashedStr; +use bevy::{prelude::*}; +use serde::{Deserialize, Serialize}; -use crate::math::int::FGi32; +use crate::{fighter::attack::AttackHitbox, math::int::FGi32}; pub mod move_compiler; +pub mod importer; pub enum MoveAngleKind { Normal(FGi32), Sakurai } -pub struct MoveEventHurtbox { - pub priority: u32, - pub bone: HashedStr, - pub radius: FGi32, - pub damage: FGi32, - pub knockback_scaling: FGi32, - pub angle: MoveAngleKind, -} - -pub enum MoveEvent { - Hurtbox(MoveEventHurtbox) +#[derive(Asset, Reflect, Serialize, Deserialize)] +pub struct FighterAttackScript { + pub hitboxes: Vec } \ No newline at end of file diff --git a/src/scripting/move_compiler.rs b/src/scripting/move_compiler.rs index 196563a..d113ef4 100644 --- a/src/scripting/move_compiler.rs +++ b/src/scripting/move_compiler.rs @@ -3,8 +3,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use rhai::EvalAltResult; use crate::{ - fighter::attack::{self, AttackAngle, FighterDamage, Knockback, KnockbackType}, - math::{int::FGi32, vec::FGVec2, vec3::FGVec3}, + fighter::attack::{self, AttackAngle, FighterDamage, Knockback, KnockbackType}, math::{int::FGi32, vec::FGVec2, vec3::FGVec3}, scripting::FighterAttackScript, }; #[derive(Debug, Default)] @@ -59,6 +58,14 @@ impl MoveCompiler { Ok(()) } + + fn into_attack_script(mut self) -> Result { + self.remove_all_hitboxes().map_err(|err| err.to_string())?; + let hitboxes = self.hitboxes.into_iter().map(|(_, hitbox)| hitbox).collect(); + Ok(FighterAttackScript { + hitboxes + }) + } } // scripting functions @@ -83,95 +90,97 @@ pub fn register_common_types(engine: &mut rhai::Engine) { .register_fn("damage", FighterDamage::lit); } -pub fn compile_script(text: &str) -> Result { +pub fn compile_script(text: &str) -> Result { let mc = Rc::new(RefCell::new(MoveCompiler { cursor: 0, ..Default::default() })); - let mut engine = rhai::Engine::new(); + let result = { + let mut engine = rhai::Engine::new(); - register_common_types(&mut engine); + register_common_types(&mut engine); - { - let mc = mc.clone(); - engine.register_fn( - "frame", - move |frame: i64| -> Result<(), Box> { - let mut b = mc.borrow_mut(); - b.frame(frame as u32) - }, - ); - } + { + let mc = mc.clone(); + engine.register_fn( + "frame", + move |frame: i64| -> Result<(), Box> { + let mut b = mc.borrow_mut(); + b.frame(frame as u32) + }, + ); + } - { - let mc = mc.clone(); - engine.register_fn( - "remove_all_hitboxes", - move || -> Result<(), Box> { - let mut b = mc.borrow_mut(); - b.remove_all_hitboxes() - }, - ); - } + { + let mc = mc.clone(); + engine.register_fn( + "remove_all_hitboxes", + move || -> Result<(), Box> { + let mut b = mc.borrow_mut(); + b.remove_all_hitboxes() + }, + ); + } - { - let mc = mc.clone(); - engine.register_fn( - "remove_all_hitboxes", - move |id: i64| -> Result<(), Box> { - let mut b = mc.borrow_mut(); - b.remove_hitbox(id as u32) - }, - ); - } + { + let mc = mc.clone(); + engine.register_fn( + "remove_all_hitboxes", + move |id: i64| -> Result<(), Box> { + let mut b = mc.borrow_mut(); + b.remove_hitbox(id as u32) + }, + ); + } - { - let mc = mc.clone(); - engine.register_fn( - "hitbox", - move |id: i64, - bone: &str, - damage: FighterDamage, - offset: FGVec3, - radius: FGi32, - angle: AttackAngle, - knockback_type: KnockbackType, - knockback: Knockback, - knockback_growth: FGi32| - -> Result<(), Box> { - let mut b = mc.borrow_mut(); + { + let mc = mc.clone(); + engine.register_fn( + "hitbox", + move |id: i64, + bone: &str, + damage: FighterDamage, + offset: FGVec3, + radius: FGi32, + angle: AttackAngle, + knockback_type: KnockbackType, + knockback: Knockback, + knockback_growth: FGi32| + -> Result<(), Box> { + let mut b = mc.borrow_mut(); - // Rhai does not have first class u32 support, so we have to do this. - let id = id as u32; + // Rhai does not have first class u32 support, so we have to do this. + let id = id as u32; - if b.hitboxes.contains_key(&id) { - return Err(format!("Hitbox with ID {} already existed!", id).into()); - } + if b.hitboxes.contains_key(&id) { + return Err(format!("Hitbox with ID {} already existed!", id).into()); + } - let hitbox = attack::AttackHitbox { - id, - bone: bone.to_owned(), - damage, - offset, - radius, - angle, - knockback_type, - knockback, - knockback_growth, - start_frame: b.cursor, - end_frame: b.cursor, - }; + let hitbox = attack::AttackHitbox { + id, + bone: bone.to_owned(), + damage, + offset, + radius, + angle, + knockback_type, + knockback, + knockback_growth, + start_frame: b.cursor, + end_frame: b.cursor, + }; - b.hitboxes.insert(id, hitbox); - b.active_hitboxes.push(id); + b.hitboxes.insert(id, hitbox); + b.active_hitboxes.push(id); - Ok(()) - }, - ); - } + Ok(()) + }, + ); + } + engine.run(&text) + }; - engine - .run(&text) - .map(|_| Rc::into_inner(mc).unwrap().into_inner()) - .map_err(|err| err.to_string()) + + result.map(|_| Rc::into_inner(mc).unwrap().into_inner().into_attack_script()) + .map_err(|err| err.to_string())? }