diff --git a/Cargo.lock b/Cargo.lock index 96abaf1..0f1d989 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,6 +109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", "getrandom 0.3.4", "once_cell", "version_check", @@ -2670,6 +2671,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + [[package]] name = "const_panic" version = "0.2.15" @@ -3529,6 +3550,7 @@ dependencies = [ "gc-adapter-neo", "gltf", "jackdaw_runtime", + "rhai", "ron 0.12.2", "serde", "strum", @@ -5440,6 +5462,9 @@ name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -6187,6 +6212,34 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rhai" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6e1be9d697d537ce450766df42ad6adf1a93f25f21d73217354ad7e3d3dae1" +dependencies = [ + "ahash", + "bitflags 2.13.1", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", + "thin-vec", + "web-time", +] + +[[package]] +name = "rhai_codegen" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd3a7535e50bf36857e7be7bec276d334e8c2dfa469c2201226fd01638ea5ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "rle-decode-fast" version = "1.0.3" @@ -6547,6 +6600,17 @@ dependencies = [ "serde", ] +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + [[package]] name = "smithay-client-toolkit" version = "0.19.2" @@ -6850,6 +6914,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thin-vec" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79def32ffcd477db1ff26f76dab9e3a91f0bd42a85ca96577089b24623056f9d" + [[package]] name = "thiserror" version = "1.0.69" @@ -6933,6 +7003,15 @@ dependencies = [ "tikv-jemalloc-sys", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny-skia" version = "0.11.4" diff --git a/Cargo.toml b/Cargo.toml index 617e55f..6825d47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ strum_macros = "0.28.0" bevy_common_assets = { version = "0.17.0", features = ["ron"] } bevy_asset_loader = "0.27.0" gc-adapter-neo = { version = "1.0.0", features = ["libusb"] } +rhai = "1.26.0" # Enable a large amount of optimization in the dev profile for dependencies. [profile.dev.package."*"] diff --git a/src/fighter/attack.rs b/src/fighter/attack.rs new file mode 100644 index 0000000..bec1dff --- /dev/null +++ b/src/fighter/attack.rs @@ -0,0 +1,79 @@ +use crate::math::{int::FGi32, vec3::FGVec3}; + + +pub struct FighterMoveManifest { + script_path: String, + +} + +#[derive(Debug, Default, Clone)] +pub struct FighterDamage(FGi32); + +impl FighterDamage { + pub fn lit(text: &str) -> Self { + Self(FGi32::lit(text)) + } +} + +#[derive(Clone, Debug)] +pub enum KnockbackType { + Normal, + Fixed +} + +impl KnockbackType { + pub fn make_normal() -> Self { + Self::Normal + } + + pub fn make_fixed() -> Self { + Self::Fixed + } +} + +#[derive(Debug, Default, Clone)] +pub struct Knockback(FGi32); + +impl Knockback { + pub fn lit(val: &str) -> Self { + Self(FGi32::lit(val)) + } +} + +#[derive(Debug, Clone)] +pub enum AttackAngle { + Normal(FGi32), + Sakurai +} + +impl Default for AttackAngle { + fn default() -> Self { + AttackAngle::Normal(FGi32::lit("0")) + } +} + +impl AttackAngle { + + pub fn make_normal(angle: FGi32) -> Self { + Self::Normal(angle) + } + + pub fn make_sakurai() -> Self { + Self::Sakurai + } +} + +#[derive(Debug)] +pub struct AttackHitbox { + pub id: u32, + pub bone: String, + pub damage: FighterDamage, + pub offset: FGVec3, + pub radius: FGi32, + pub angle: AttackAngle, + pub knockback_type: KnockbackType, + pub knockback: Knockback, + pub knockback_growth: FGi32, + pub start_frame: u32, + pub end_frame: u32 +} \ No newline at end of file diff --git a/src/fighter/mod.rs b/src/fighter/mod.rs index cc2dd69..ae7e684 100644 --- a/src/fighter/mod.rs +++ b/src/fighter/mod.rs @@ -16,6 +16,7 @@ pub mod manifest; pub mod motion; pub mod state; pub mod visual; +pub mod attack; // The fighter components are re-exported so the rest of the crate can say // `fighter::FighterVelocity` without caring which file it lives in. Named diff --git a/src/lib.rs b/src/lib.rs index 0b2614f..99b71db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ pub mod netcode; pub mod player; pub mod schedule; pub mod stage; +pub mod scripting; #[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] pub enum AppState { diff --git a/src/math/mod.rs b/src/math/mod.rs index 3d47442..37e9cbd 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -1,3 +1,4 @@ pub mod int; pub mod segment; pub mod vec; +pub mod vec3; diff --git a/src/math/vec.rs b/src/math/vec.rs index d7adb24..d33fe14 100644 --- a/src/math/vec.rs +++ b/src/math/vec.rs @@ -239,6 +239,15 @@ impl MulAssign for FGVec2 { } } +impl From<(FGi32, FGi32)> for FGVec2 { + fn from(value: (FGi32, FGi32)) -> Self { + Self { + x: value.0, + y: value.1, + } + } +} + #[cfg(test)] mod is_normalized_tests { use super::*; diff --git a/src/math/vec3.rs b/src/math/vec3.rs new file mode 100644 index 0000000..3cf3091 --- /dev/null +++ b/src/math/vec3.rs @@ -0,0 +1,22 @@ +use bevy::prelude::*; +use serde::{Deserialize, Serialize}; + +use crate::math::int::FGi32; + +#[derive(Clone, Copy, PartialEq, Default, Debug, Reflect, Serialize, Deserialize, Hash)] +#[reflect(opaque)] +pub struct FGVec3 { + pub x: FGi32, + pub y: FGi32, + pub z: FGi32, +} + +impl FGVec3 { + pub fn lit(x: &str, y: &str, z: &str) -> Self { + Self { + x: FGi32::lit(x), + y: FGi32::lit(y), + z: FGi32::lit(z), + } + } +} \ No newline at end of file diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs new file mode 100644 index 0000000..4811aec --- /dev/null +++ b/src/scripting/mod.rs @@ -0,0 +1,23 @@ +use bevy::ecs::name::HashedStr; + +use crate::math::int::FGi32; + +pub mod move_compiler; + +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) +} \ No newline at end of file diff --git a/src/scripting/move_compiler.rs b/src/scripting/move_compiler.rs new file mode 100644 index 0000000..196563a --- /dev/null +++ b/src/scripting/move_compiler.rs @@ -0,0 +1,177 @@ +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}, +}; + +#[derive(Debug, Default)] +struct MoveCompiler { + hitboxes: HashMap, + active_hitboxes: Vec, + cursor: u32, +} + +impl MoveCompiler { + fn frame(&mut self, new_frame: u32) -> Result<(), Box> { + if new_frame < self.cursor { + return Err(format!( + "Tried to go back from frame {} -> {}, rolling back is not supported", + self.cursor, new_frame + ) + .into()); + } + + self.cursor = new_frame; + + Ok(()) + } + + fn remove_hitbox(&mut self, id: u32) -> Result<(), Box> { + let hb = self + .hitboxes + .get_mut(&id) + .ok_or(format!("Can't remove hitbox with {id}: Hitbox not found"))?; + + let active_hbox_position = + self.active_hitboxes + .iter() + .position(|i| *i == id) + .ok_or(format!( + "Can't remove hitbox with ID {id}: Hitbox was not active" + ))?; + + hb.end_frame = self.cursor; + + self.active_hitboxes.remove(active_hbox_position); + + Ok(()) + } + fn remove_all_hitboxes(&mut self) -> Result<(), Box> { + // We are gonna modify the array, so take a copy... + for hitbox_id in self.active_hitboxes.clone() { + self.remove_hitbox(hitbox_id)?; + } + + self.active_hitboxes.clear(); + + Ok(()) + } +} + +// scripting functions + +pub fn register_common_types(engine: &mut rhai::Engine) { + engine + .register_type_with_name::("KnockbackType") + .register_fn("knockback_normal", KnockbackType::make_normal) + .register_fn("knockback_fixed", KnockbackType::make_fixed) + .register_type_with_name::("Angle") + .register_fn("angle", AttackAngle::make_normal) + .register_fn("angle_sakurai", AttackAngle::make_sakurai) + .register_type_with_name::("Knockback") + .register_fn("knockback", Knockback::lit) + .register_type_with_name::("FGi32") + .register_fn("fg32", FGi32::lit) + .register_type_with_name::("Vec2") + .register_fn("vec2", FGVec2::lit) + .register_type_with_name::("Vec3") + .register_fn("vec3", FGVec3::lit) + .register_type_with_name::("FighterDamage") + .register_fn("damage", FighterDamage::lit); +} + +pub fn compile_script(text: &str) -> Result { + let mc = Rc::new(RefCell::new(MoveCompiler { + cursor: 0, + ..Default::default() + })); + let mut engine = rhai::Engine::new(); + + 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( + "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( + "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; + + 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, + }; + + b.hitboxes.insert(id, hitbox); + b.active_hitboxes.push(id); + + Ok(()) + }, + ); + } + + engine + .run(&text) + .map(|_| Rc::into_inner(mc).unwrap().into_inner()) + .map_err(|err| err.to_string()) +}