Initial work on attack scripting system
This commit is contained in:
Generated
+79
@@ -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"
|
||||
|
||||
@@ -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."*"]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod int;
|
||||
pub mod segment;
|
||||
pub mod vec;
|
||||
pub mod vec3;
|
||||
|
||||
@@ -239,6 +239,15 @@ impl MulAssign<FGi32> 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::*;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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<u32, attack::AttackHitbox>,
|
||||
active_hitboxes: Vec<u32>,
|
||||
cursor: u32,
|
||||
}
|
||||
|
||||
impl MoveCompiler {
|
||||
fn frame(&mut self, new_frame: u32) -> Result<(), Box<EvalAltResult>> {
|
||||
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<EvalAltResult>> {
|
||||
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<EvalAltResult>> {
|
||||
// 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>("KnockbackType")
|
||||
.register_fn("knockback_normal", KnockbackType::make_normal)
|
||||
.register_fn("knockback_fixed", KnockbackType::make_fixed)
|
||||
.register_type_with_name::<AttackAngle>("Angle")
|
||||
.register_fn("angle", AttackAngle::make_normal)
|
||||
.register_fn("angle_sakurai", AttackAngle::make_sakurai)
|
||||
.register_type_with_name::<Knockback>("Knockback")
|
||||
.register_fn("knockback", Knockback::lit)
|
||||
.register_type_with_name::<FGi32>("FGi32")
|
||||
.register_fn("fg32", FGi32::lit)
|
||||
.register_type_with_name::<FGVec2>("Vec2")
|
||||
.register_fn("vec2", FGVec2::lit)
|
||||
.register_type_with_name::<FGVec3>("Vec3")
|
||||
.register_fn("vec3", FGVec3::lit)
|
||||
.register_type_with_name::<FighterDamage>("FighterDamage")
|
||||
.register_fn("damage", FighterDamage::lit);
|
||||
}
|
||||
|
||||
pub fn compile_script(text: &str) -> Result<MoveCompiler, String> {
|
||||
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<EvalAltResult>> {
|
||||
let mut b = mc.borrow_mut();
|
||||
b.frame(frame as u32)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mc = mc.clone();
|
||||
engine.register_fn(
|
||||
"remove_all_hitboxes",
|
||||
move || -> Result<(), Box<EvalAltResult>> {
|
||||
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<EvalAltResult>> {
|
||||
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<EvalAltResult>> {
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user