From 1d5a07324d88ec6bada3aeaf2fd55aa48b01a65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Rom=C3=A0n?= Date: Sun, 30 Aug 2026 14:45:26 +0200 Subject: [PATCH] Integrate basic attack script debug. --- src/fighter/animation.rs | 4 + src/fighter/attack.rs | 31 +++++++- src/fighter/baked_animation.rs | 122 ++++++++++++++++++++++++++++- src/fighter/debug.rs | 44 ++++++++++- src/fighter/mod.rs | 28 ++++--- src/fighter/motion.rs | 14 +++- src/fighter/state/ground.rs | 6 +- src/fighter/state/ground_attack.rs | 48 ++++++++++++ src/fighter/state/mod.rs | 33 +++++++- src/fighter/visual.rs | 2 +- src/input/buffer.rs | 11 +++ src/input/debug.rs | 2 +- src/input/frame.rs | 1 + src/input/gamepad.rs | 1 + src/input/keyboard.rs | 1 + src/input/map.rs | 1 + src/input/mod.rs | 10 +++ src/match_loading.rs | 22 +++--- src/math/vec3.rs | 15 +++- 19 files changed, 353 insertions(+), 43 deletions(-) create mode 100644 src/fighter/state/ground_attack.rs diff --git a/src/fighter/animation.rs b/src/fighter/animation.rs index bf9713a..5309f47 100644 --- a/src/fighter/animation.rs +++ b/src/fighter/animation.rs @@ -149,4 +149,8 @@ pub enum AnimKind { AirDodge, Fall, Turn, + + // Attacks + + AttackJab1, } diff --git a/src/fighter/attack.rs b/src/fighter/attack.rs index 49f458b..7dd3000 100644 --- a/src/fighter/attack.rs +++ b/src/fighter/attack.rs @@ -1,7 +1,9 @@ -use bevy::reflect::Reflect; +use std::collections::HashMap; + +use bevy::{asset::Handle, ecs::component::Component, reflect::Reflect}; use serde::{Deserialize, Serialize}; -use crate::math::{int::FGi32, vec3::FGVec3}; +use crate::{fighter::animation::AnimKind, math::{int::FGi32, vec3::FGVec3}, scripting::FighterAttackScript}; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct FighterDamage(FGi32); @@ -59,9 +61,19 @@ impl AttackAngle { } } -#[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone)] +#[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone, Debug)] pub enum AttackKind { - NAir + NAir, + Jab +} + +impl AttackKind { + pub fn get_animation(&self) -> AnimKind { + match self { + AttackKind::NAir => AnimKind::AttackJab1, + AttackKind::Jab => AnimKind::AttackJab1, + } + } } #[derive(Debug, Clone, Reflect, Serialize, Deserialize)] @@ -78,4 +90,15 @@ pub struct AttackHitbox { pub knockback_growth: FGi32, pub start_frame: u32, pub end_frame: u32 +} + +pub fn update_active_hitbox_list(script: &FighterAttackScript, list: &mut Vec, frame: u32) { + *list = script.hitboxes.iter().enumerate().filter(|(i, hb)| { + hb.start_frame <= frame && hb.end_frame > frame + }).map(|(i, _)| i).collect() +} + +#[derive(Component)] +pub struct FighterAttackScriptAssets { + pub scripts: HashMap> } \ No newline at end of file diff --git a/src/fighter/baked_animation.rs b/src/fighter/baked_animation.rs index 7664bb4..fd5556a 100644 --- a/src/fighter/baked_animation.rs +++ b/src/fighter/baked_animation.rs @@ -20,7 +20,10 @@ use serde::{Deserialize, Serialize}; use crate::{ fighter::{animation::AnimKind, manifest::FighterManifest}, - math::int::{FGWide, FGi32}, + math::{ + int::{FGWide, FGi32}, + vec3::FGVec3, + }, }; pub const BAKED_ANIMATION_FPS: u32 = 60; @@ -33,6 +36,14 @@ pub struct FixedMat4 { } impl FixedMat4 { + pub const IDENTITY: Self = Self { + cols: [ + [FGi32::ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE], + ] + }; fn from_mat4(matrix: Mat4) -> io::Result { let values = matrix.to_cols_array(); let mut cols = [[FGi32::ZERO; 4]; 4]; @@ -47,7 +58,7 @@ impl FixedMat4 { Ok(Self { cols }) } - fn mul(self, rhs: Self) -> Self { + pub fn mul(self, rhs: Self) -> Self { let mut cols = [[FGi32::ZERO; 4]; 4]; for column in 0..4 { for row in 0..4 { @@ -61,6 +72,67 @@ impl FixedMat4 { } Self { cols } } + + pub fn transform_point(self, point: FGVec3) -> FGVec3 { + let input = [point.x, point.y, point.z, FGi32::ONE]; + let mut output = [FGi32::ZERO; 3]; + for row in 0..3 { + let mut value = FGWide::ZERO; + for (column, input) in input.into_iter().enumerate() { + value += FGWide::from_num(self.cols[column][row]) * FGWide::from_num(input); + } + output[row] = FGi32::from_num(value); + } + FGVec3 { + x: output[0], + y: output[1], + z: output[2], + } + } + + pub fn translate(&mut self, translation: FGVec3) { + self.cols[3][0] = translation.x; + self.cols[3][1] = translation.y; + self.cols[3][2] = translation.z; + } + + pub fn rotate_y(self, quarter_turns: u8) -> Self { + let rotation = match quarter_turns % 4 { + 0 => Self { + cols: [ + [FGi32::ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE], + ], + }, + 1 => Self { + cols: [ + [FGi32::ZERO, FGi32::ZERO, FGi32::NEG_ONE, FGi32::ZERO], + [FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO], + [FGi32::ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE], + ], + }, + 2 => Self { + cols: [ + [FGi32::NEG_ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::NEG_ONE, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE], + ], + }, + _ => Self { + cols: [ + [FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO], + [FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO], + [FGi32::NEG_ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE], + ], + }, + }; + self.mul(rotation) + } } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -525,4 +597,50 @@ mod tests { baked.bone_names.len() ); } + + #[test] + fn fixed_matrix_transforms_points() { + let transform = FixedMat4 { + cols: [ + [FGi32::lit("2"), FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::lit("3"), FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::lit("4"), FGi32::ZERO], + [ + FGi32::lit("10"), + FGi32::lit("-5"), + FGi32::lit("1.5"), + FGi32::ONE, + ], + ], + }; + + assert_eq!( + transform.transform_point(FGVec3::lit("1.25", "-2", "0.5")), + FGVec3::lit("12.5", "-11", "3.5") + ); + } + + #[test] + fn fixed_matrix_rotates_points_around_y() { + let transform = FixedMat4 { + cols: [ + [FGi32::ONE, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ONE, FGi32::ZERO, FGi32::ZERO], + [FGi32::ZERO, FGi32::ZERO, FGi32::ONE, FGi32::ZERO], + [ + FGi32::lit("10"), + FGi32::lit("2"), + FGi32::lit("-3"), + FGi32::ONE, + ], + ], + }; + + assert_eq!( + transform + .rotate_y(5) + .transform_point(FGVec3::lit("1", "0", "0")), + FGVec3::lit("10", "2", "-4") + ); + } } diff --git a/src/fighter/debug.rs b/src/fighter/debug.rs index 1445ef0..23be78d 100644 --- a/src/fighter/debug.rs +++ b/src/fighter/debug.rs @@ -10,10 +10,11 @@ use crate::fighter::ecb::FighterPreviousECB; use crate::fighter::state::StateNameDebug; use crate::fighter::visual::FighterAnimations; use crate::fighter::{ - FighterECB, FighterPreviousTranslation, FighterTranslation, - FighterVelocity, + FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation, FighterTranslation, FighterVelocity, }; use crate::input::FighterInput; +use crate::math::vec3::FGVec3; +use crate::scripting::FighterAttackScript; pub fn debug_draw_ecb( ecbs: Query<( @@ -60,14 +61,51 @@ pub fn animation_debug( .get(&anims.baked) .expect("Baked anims should be in"); for bone in &baked.bone_names { - let out = anims.sample_bone(&baked_anims, *frame, bone); + if bone != "Bone_30" { + continue; + } + let out = anims.sample_bone(&baked_anims, frame, bone); if let Some(out) = out { let pos = global_transform.transform_point(Vec3::new( out.cols[3][0].to_num(), out.cols[3][1].to_num(), out.cols[3][2].to_num(), )); + //gizmos.text(pos, bone, 1.0, Vec2::new(0.0, 0.0), bevy::color::palettes::css::BLACK); gizmos.cross(pos, 0.5, bevy::color::palettes::css::RED); + gizmos.axes(*global_transform, 1.0); + } + } + } +} + +pub fn attack_debug( + query: Query<(&FighterHitboxes, &FighterAnimationFrame, &FighterAnimations, &FighterTranslation, &FighterFacingDirection)>, + baked_anims: Res>, + attack_scripts: Res>, + mut gizmos: Gizmos, +) { + for (hitboxes, frame, animations, translation, facing_direction) in query { + if let Some(script) = &hitboxes.attack_script { + let script = attack_scripts.get(script).expect("Script ref should be valid"); + let player_trf = translation.get_3d_transform(facing_direction); + + for i in hitboxes.active_hitboxes.iter() { + let offset = script.hitboxes[*i].offset; + let radius = script.hitboxes[*i].radius; + let bone = &script.hitboxes[*i].bone; + + let trf = animations.sample_bone(&baked_anims, frame, bone); + if let Some(trf) = trf { + let trf = player_trf.mul(trf); + let start = player_trf.transform_point(FGVec3::lit("0.0", "1.0", "0.0")); + let end = player_trf.transform_point(FGVec3::lit("0.0", "1.0", "1.0")); + let pos = trf.transform_point(offset); + let pos_draw = Vec3::new(pos.x.to_num(), pos.y.to_num(), pos.z.to_num()); + gizmos.sphere(pos_draw, radius.to_num(), bevy::color::palettes::css::RED); + + gizmos.arrow(start.to_vec3(), end.to_vec3(),bevy::color::palettes::css::RED); + } } } } diff --git a/src/fighter/mod.rs b/src/fighter/mod.rs index ae7e684..3761254 100644 --- a/src/fighter/mod.rs +++ b/src/fighter/mod.rs @@ -29,16 +29,8 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity use crate::{ fighter::{ - animation::animation_init, - manifest::FighterManifest, - state::{FighterState, fall::FallState}, - visual::FighterAnimations, - }, - input::FighterInput, - math::{int::FGi32, vec::FGVec2}, - player::Player, - schedule::GameplaySet, - stage::line::StageCollision, + animation::animation_init, attack::FighterAttackScriptAssets, manifest::FighterManifest, state::{FighterState, fall::FallState}, visual::FighterAnimations, + }, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision, }; use state::state_interrupt_system; @@ -158,20 +150,29 @@ impl Plugin for FighterPlugin { .add_systems(FixedPostUpdate, debug::debug_draw_ecb) .add_systems(EguiPrimaryContextPass, debug::fighter_debug) .add_systems(FixedPostUpdate, debug::animation_debug) + .add_systems(FixedPostUpdate, debug::attack_debug) .add_systems(EguiPrimaryContextPass, debug::update_config) .rollback_component_with_clone::() + .rollback_component_with_clone::() .checksum_component_with_hash::() .checksum_component_with_hash::() .register_type::(); } } +#[derive(Component, Clone)] +pub struct FighterHitboxes { + pub attack_script: Option>, + pub active_hitboxes: Vec +} + pub fn spawn_fighter( commands: &mut Commands, player_handle: usize, spawn_position: FGVec2, manifest: Handle, animations: FighterAnimations, + attack_scripts: FighterAttackScriptAssets, visual_root: WorldAssetRoot, ) { commands.spawn(( @@ -183,14 +184,19 @@ pub fn spawn_fighter( manifest: manifest, }, FighterFacingDirection::Right, - FighterECB { + (FighterECB { vertical_half: FGi32::lit("5.0"), horizontal_half: FGi32::lit("2.5"), }, + FighterHitboxes { + attack_script: None, + active_hitboxes: vec![] + }), FighterVelocity::default(), FighterTranslation(spawn_position), visual_root, animations, + attack_scripts, FighterState::Fall(FallState), animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true), FighterVisual, diff --git a/src/fighter/motion.rs b/src/fighter/motion.rs index b6d9876..b4764d9 100644 --- a/src/fighter/motion.rs +++ b/src/fighter/motion.rs @@ -2,8 +2,9 @@ //! this module is what turns that into a position. use bevy::prelude::*; +use fixed::types::I16F16; -use crate::math::vec::FGVec2; +use crate::{fighter::{FighterFacingDirection, baked_animation::FixedMat4}, math::{int::FGi32, vec::FGVec2, vec3::FGVec3}}; #[derive(Component, Clone, Copy)] pub struct Grounded; @@ -18,6 +19,17 @@ pub struct FighterPreviousTranslation(pub FGVec2); #[require(FighterPreviousTranslation)] pub struct FighterTranslation(pub FGVec2); +impl FighterTranslation { + pub fn get_3d_transform(&self, facing_dir: &FighterFacingDirection) -> FixedMat4 { + let mut o = match facing_dir { + FighterFacingDirection::Left => FixedMat4::IDENTITY.rotate_y(3), + FighterFacingDirection::Right => FixedMat4::IDENTITY.rotate_y(1), + }; + o.translate(FGVec3::new(self.0.x, self.0.y, FGi32::ZERO)); + o + } +} + pub fn apply_air_motion( fighters: Query<( &FighterVelocity, diff --git a/src/fighter/state/ground.rs b/src/fighter/state/ground.rs index 6eff413..a210091 100644 --- a/src/fighter/state/ground.rs +++ b/src/fighter/state/ground.rs @@ -2,7 +2,7 @@ //! ECS types so it stays cheap to reason about and to unit-test — it has to be //! bit-for-bit deterministic for rollback. -use crate::fighter::state::{FighterState, FighterStateContext, dash, turn, wait, walk}; +use crate::fighter::state::{FighterState, FighterStateContext, dash, ground_attack, turn, wait, walk}; use crate::fighter::{ FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity, }; @@ -151,7 +151,9 @@ pub fn grounded_movement_standstill_common_interrupts( state_context: &FighterStateContext, ground_common: &GroundedStateCommon, ) -> Option { - if let Some(state) = dash::check_input(state_context, &ground_common) { + if let Some(state) = ground_attack::check_interrupt(state_context, &ground_common) { + Some(state) + } else if let Some(state) = dash::check_input(state_context, &ground_common) { Some(state) } else if let Some(state) = turn::check_input(state_context, &ground_common) { Some(state) diff --git a/src/fighter/state/ground_attack.rs b/src/fighter/state/ground_attack.rs new file mode 100644 index 0000000..d06075b --- /dev/null +++ b/src/fighter/state/ground_attack.rs @@ -0,0 +1,48 @@ +use crate::fighter::{attack::{AttackKind, update_active_hitbox_list}, state::{FighterState, FighterStateImpl, ground::GroundedStateCommon, wait::WaitState}}; + +use bevy::prelude::*; + +#[derive(Clone, Debug, Hash)] +pub struct GroundAttackState { + pub attack_kind: AttackKind, + pub grounded_common: GroundedStateCommon, +} + +impl FighterStateImpl for GroundAttackState { + const NAME: &'static str = "GroundAttack"; + + fn check_interrupt(&self, state_context: &mut super::FighterStateContext) -> Option { + if state_context.is_current_animation_finished() { + Some(FighterState::Wait(WaitState { + grounded_common: self.grounded_common.clone() + })) + } else { + None + } + } + + fn update(&mut self, state_context: &mut super::FighterStateContext) { + let script = state_context.attack_scripts.get(&state_context.attack_script_assets.scripts[&self.attack_kind]); + if let Some(script) = script { + let animation_frame = state_context.get_current_animation_frame(); + update_active_hitbox_list(script, &mut state_context.hitboxes.active_hitboxes, animation_frame); + } + } + + fn on_enter(&mut self, state_context: &mut super::FighterStateContext) { + state_context.play_animation(self.attack_kind.get_animation(), false); + state_context.hitboxes.attack_script = Some(state_context.attack_script_assets.scripts[&self.attack_kind].clone()); + state_context.hitboxes.active_hitboxes.clear(); + } +} + +pub fn check_interrupt(state_context: &super::FighterStateContext, grounded_common: &GroundedStateCommon) -> Option { + if state_context.input.has_command(crate::input::FighterCommands::Attack) { + Some(FighterState::GroundAttack(GroundAttackState { + attack_kind: AttackKind::Jab, + grounded_common: grounded_common.clone() + })) + } else { + None + } +} \ No newline at end of file diff --git a/src/fighter/state/mod.rs b/src/fighter/state/mod.rs index f146689..fba26c1 100644 --- a/src/fighter/state/mod.rs +++ b/src/fighter/state/mod.rs @@ -6,16 +6,18 @@ use bevy::ecs::query::QueryData; use bevy::prelude::*; use crate::fighter::animation::{AnimKind, FighterAnimationFrame, FighterAnimationPlayerLink}; +use crate::fighter::attack::FighterAttackScriptAssets; use crate::fighter::baked_animation::{BakedFighterAnimations, FixedMat4}; use crate::fighter::ecb::FighterPreviousECB; use crate::fighter::manifest::FighterManifest; +use crate::fighter::state::ground_attack::GroundAttackState; use crate::fighter::visual::FighterAnimations; use crate::fighter::{ - Fighter, FighterECB, FighterFacingDirection, FighterPreviousTranslation, - FighterTranslation, FighterVelocity, + Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterPreviousTranslation, FighterTranslation, FighterVelocity, }; use crate::game_settings::GameSettings; use crate::input::FighterInput; +use crate::scripting::FighterAttackScript; use crate::stage::line::StageCollision; pub mod air; @@ -29,6 +31,7 @@ pub mod run; pub mod turn; pub mod wait; pub mod walk; +pub mod ground_attack; macro_rules! fighter_states { ($($variant:ident => $ty:ty),* $(,)?) => { @@ -80,6 +83,7 @@ pub struct FighterStateContext<'a> { pub translation: &'a mut FighterTranslation, pub prev_translation: &'a mut FighterPreviousTranslation, pub input: &'a mut FighterInput, + pub hitboxes: &'a mut FighterHitboxes, pub velocity: &'a mut FighterVelocity, pub ecb: &'a mut FighterECB, pub prev_ecb: &'a mut FighterPreviousECB, @@ -91,7 +95,9 @@ pub struct FighterStateContext<'a> { pub animation_transitions: &'a mut AnimationTransitions, pub animation_frame: &'a mut FighterAnimationFrame, pub baked_animations: &'a Assets, + pub attack_scripts: &'a Assets, pub fighter_manifest: &'a FighterManifest, + pub attack_script_assets: &'a FighterAttackScriptAssets, } impl FighterStateContext<'_> { @@ -124,7 +130,7 @@ impl FighterStateContext<'_> { pub fn sample_bone(&self, bone: &str) -> Option { self.animations - .sample_bone(self.baked_animations, *self.animation_frame, bone) + .sample_bone(self.baked_animations, self.animation_frame, bone) } } @@ -138,7 +144,8 @@ fighter_states! { Jump => jump::JumpState, Land => land::LandingState, AirDodge => air_dodge::AirDodgeState, - Turn => turn::TurnState + Turn => turn::TurnState, + GroundAttack => ground_attack::GroundAttackState, } #[derive(QueryData)] @@ -147,6 +154,7 @@ pub struct FighterFrameQuery { pub translation: &'static mut FighterTranslation, pub prev_translation: &'static mut FighterPreviousTranslation, pub input: &'static mut FighterInput, + pub hitboxes: &'static mut FighterHitboxes, pub velocity: &'static mut FighterVelocity, pub ecb: &'static mut FighterECB, pub prev_ecb: &'static mut FighterPreviousECB, @@ -164,12 +172,15 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> { animation_player: &'a mut AnimationPlayer, animation_transitions: &'a mut AnimationTransitions, baked_animations: &'a Assets, + attack_scripts: &'a Assets, + attack_script_assets: &'a FighterAttackScriptAssets, manifest: &'a FighterManifest, ) -> FighterStateContext<'a> { FighterStateContext { translation: &mut self.translation, prev_translation: &mut self.prev_translation, input: &mut self.input, + hitboxes: &mut self.hitboxes, velocity: &mut self.velocity, ecb: &mut self.ecb, prev_ecb: &mut self.prev_ecb, @@ -181,6 +192,8 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> { animation_transitions, animation_frame: &mut self.animation_frame, baked_animations, + attack_scripts, + attack_script_assets, fighter_manifest: manifest, } } @@ -195,6 +208,7 @@ pub struct StatefulFighterQuery { pub animations: &'static FighterAnimations, pub animation_player_link: &'static FighterAnimationPlayerLink, pub fighter: &'static Fighter, + pub attack_script_assets: &'static FighterAttackScriptAssets, } #[derive(Component)] @@ -212,6 +226,7 @@ pub fn state_update_system( stage_collision: Res, game_settings: Res, baked_animations: Res>, + attack_scripts: Res>, manifests: Res>, ) { for fighter in &mut query { @@ -235,6 +250,8 @@ pub fn state_update_system( &mut animation_player, &mut animation_transitions, &baked_animations, + &attack_scripts, + &fighter.attack_script_assets, &manifests .get(&(fighter.fighter.manifest)) .expect("Fighter manifest should be valid"), @@ -250,6 +267,7 @@ pub fn state_interrupt_system( game_settings: Res, baked_animations: Res>, fighter_manifests: Res>, + attack_scripts: Res>, mut commands: Commands, ) { for fighter in &mut query { @@ -260,6 +278,7 @@ pub fn state_interrupt_system( animations, animation_player_link, fighter, + attack_script_assets } = fighter; let Ok((mut animation_player, mut animation_transitions)) = animation_players.get_mut(animation_player_link.player()) @@ -273,6 +292,8 @@ pub fn state_interrupt_system( &mut animation_player, &mut animation_transitions, &baked_animations, + &attack_scripts, + attack_script_assets, fighter_manifests .get(&fighter.manifest) .expect("Fighter manifest should be valid"), @@ -297,6 +318,7 @@ pub fn state_collision_interrupt_system( game_settings: Res, baked_animations: Res>, fighter_manifests: Res>, + attack_scripts: Res>, mut commands: Commands, ) { for fighter in &mut query { @@ -307,6 +329,7 @@ pub fn state_collision_interrupt_system( animations, animation_player_link, fighter, + attack_script_assets, } = fighter; let Ok((mut animation_player, mut animation_transitions)) = animation_players.get_mut(animation_player_link.player()) @@ -320,6 +343,8 @@ pub fn state_collision_interrupt_system( &mut animation_player, &mut animation_transitions, &baked_animations, + &attack_scripts, + &attack_script_assets, fighter_manifests .get(&fighter.manifest) .expect("Fighter manifest should be valid"), diff --git a/src/fighter/visual.rs b/src/fighter/visual.rs index 7ea08a0..64380be 100644 --- a/src/fighter/visual.rs +++ b/src/fighter/visual.rs @@ -16,7 +16,7 @@ impl FighterAnimations { pub fn sample_bone( &self, baked_assets: &Assets, - frame: FighterAnimationFrame, + frame: &FighterAnimationFrame, bone: &str, ) -> Option { let baked = baked_assets.get(&self.baked)?; diff --git a/src/input/buffer.rs b/src/input/buffer.rs index 9bf2806..b964065 100644 --- a/src/input/buffer.rs +++ b/src/input/buffer.rs @@ -20,6 +20,7 @@ pub enum FighterCommands { SmashMoveRight, Jump, Shield, + Attack, } /// Frames of life remaining for each buffered command. Indexed by @@ -30,6 +31,7 @@ pub struct FighterCommandLifetimes { smash_move_right: u8, jump: u8, shield: u8, + attack: u8, } impl Index for FighterCommandLifetimes { @@ -41,6 +43,7 @@ impl Index for FighterCommandLifetimes { FighterCommands::SmashMoveRight => &self.smash_move_right, FighterCommands::Jump => &self.jump, FighterCommands::Shield => &self.shield, + FighterCommands::Attack => &self.attack, } } } @@ -52,6 +55,7 @@ impl IndexMut for FighterCommandLifetimes { FighterCommands::SmashMoveRight => &mut self.smash_move_right, FighterCommands::Jump => &mut self.jump, FighterCommands::Shield => &mut self.shield, + FighterCommands::Attack => &mut self.attack, } } } @@ -148,6 +152,13 @@ pub fn postprocess_input( ); } + if !input.prev_frame.attack && input.current_frame.attack { + input.set_lifetime( + FighterCommands::Attack, + game_settings.input_common.input_buffer_size, + ); + } + if x_abs >= axis_threshold && input.frames_in_smash_move_deadzone <= frame_threshold { input.clear_command(FighterCommands::SmashMoveLeft); input.clear_command(FighterCommands::SmashMoveRight); diff --git a/src/input/debug.rs b/src/input/debug.rs index 4ee399b..b5d3140 100644 --- a/src/input/debug.rs +++ b/src/input/debug.rs @@ -1,6 +1,6 @@ use bevy_egui::prelude::*; -use crate::input::FighterInput; +use crate::{fighter::{FighterHitboxes, animation::FighterAnimationFrame, visual::FighterAnimations}, input::FighterInput}; use bevy::prelude::*; pub fn input_debug(mut contexts: EguiContexts, query: Query<&FighterInput>) -> Result { diff --git a/src/input/frame.rs b/src/input/frame.rs index c556ce0..ef926d2 100644 --- a/src/input/frame.rs +++ b/src/input/frame.rs @@ -10,4 +10,5 @@ pub struct FighterInputFrame { pub directional_attack: FGVec2, pub jump: bool, pub shield: bool, + pub attack: bool, } diff --git a/src/input/gamepad.rs b/src/input/gamepad.rs index 0dcc8dc..819833d 100644 --- a/src/input/gamepad.rs +++ b/src/input/gamepad.rs @@ -17,6 +17,7 @@ pub fn sample_gamepad(pad: &Gamepad, input_map: &BaseInputMap) -> FighterInputFr match gamepad_element.action { InputMapAction::Jump => input_frame.jump |= pressed, InputMapAction::Shield => input_frame.shield |= pressed, + InputMapAction::Attack => input_frame.attack |= pressed, // Digital buttons driving movement isn't used by the // default bindings (the stick covers that), but stay // consistent with the keyboard backend if one is added. diff --git a/src/input/keyboard.rs b/src/input/keyboard.rs index 8e9a913..dffa7b6 100644 --- a/src/input/keyboard.rs +++ b/src/input/keyboard.rs @@ -32,6 +32,7 @@ pub fn sample_keyboard(key: &ButtonInput, input_map: &BaseInputMap) -> } InputMapAction::Jump => input_frame.jump = action_strength != FGi32::ZERO, InputMapAction::Shield => input_frame.shield = action_strength != FGi32::ZERO, + InputMapAction::Attack => input_frame.attack = action_strength != FGi32::ZERO, } } diff --git a/src/input/map.rs b/src/input/map.rs index d18e471..92487cd 100644 --- a/src/input/map.rs +++ b/src/input/map.rs @@ -16,6 +16,7 @@ pub enum InputMapAction { MovementYDir(i32), Jump, Shield, + Attack, } #[derive(Debug)] diff --git a/src/input/mod.rs b/src/input/mod.rs index ed4caba..e9cc003 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -67,6 +67,11 @@ impl Plugin for FighterInputPlugin { key: KeyCode::ShiftLeft, action: InputMapAction::Shield, }, + // attack + KeyboardInputMapElement { + key: KeyCode::KeyJ, + action: InputMapAction::Attack, + }, ], gamepad: vec![ // left stick @@ -92,6 +97,11 @@ impl Plugin for FighterInputPlugin { binding: GamepadBinding::Button(GamepadButton::RightTrigger), action: InputMapAction::Shield, }, + // attack + GamepadInputMapElement { + binding: GamepadBinding::Button(GamepadButton::East), + action: InputMapAction::Attack, + }, ], }) .add_plugins(gamecube::GamecubeAdapterPlugin) diff --git a/src/match_loading.rs b/src/match_loading.rs index 7f657b3..b13b6ca 100644 --- a/src/match_loading.rs +++ b/src/match_loading.rs @@ -8,7 +8,7 @@ use bevy_ggrs::prelude::Session; use crate::{ AppState, args::Args, fighter::{ - FighterId, animation::AnimKind, attack::AttackKind, baked_animation::BakedFighterAnimations, manifest::{FighterManifest, FighterManifestRegistry}, spawn_fighter, visual::FighterAnimations, + FighterId, animation::AnimKind, attack::{AttackKind, FighterAttackScriptAssets}, baked_animation::BakedFighterAnimations, manifest::{FighterManifest, FighterManifestRegistry}, spawn_fighter, visual::FighterAnimations, }, math::{int::FGi32, vec::FGVec2}, netcode::{GGRSCfg, session::create_session}, scripting::FighterAttackScript, stage::{ self, manifest::{StageId, StageManifest, StageManifestRegistry}, @@ -163,7 +163,6 @@ pub fn prepare_match( } let mut prepared = Vec::with_capacity(assets.fighters.len()); - let mut shared_animations = HashMap::::new(); for character in &assets.fighters { let manifest_handle = registry @@ -189,15 +188,6 @@ pub fn prepare_match( return; }; - if let Some(animations) = shared_animations.get(&character.fighter) { - prepared.push(( - scene, - animations.clone(), - manifest_handle, - )); - continue; - } - let mut graph = AnimationGraph::new(); let mut clips = HashMap::::new(); @@ -235,10 +225,15 @@ pub fn prepare_match( clips, baked: character.baked_animations.clone(), }; - shared_animations.insert(character.fighter, animations.clone()); + + let attack_scripts = FighterAttackScriptAssets { + scripts: character.attack_scripts.clone() + }; + prepared.push(( scene, animations, + attack_scripts, manifest_handle, )); } @@ -272,7 +267,7 @@ pub fn prepare_match( for ( index, - (player, (scene, animations, manifest_handle)), + (player, (scene, animations, attack_scripts, manifest_handle)), ) in request.players.iter().zip(prepared).enumerate() { let spawn_x = (index as i32 * 2 + 1 - request.players.len() as i32) * 5; @@ -282,6 +277,7 @@ pub fn prepare_match( FGVec2::new(FGi32::from_num(spawn_x), FGi32::lit("12.5")), manifest_handle.clone(), animations, + attack_scripts, WorldAssetRoot(scene), ); } diff --git a/src/math/vec3.rs b/src/math/vec3.rs index 3cf3091..004a280 100644 --- a/src/math/vec3.rs +++ b/src/math/vec3.rs @@ -12,6 +12,15 @@ pub struct FGVec3 { } impl FGVec3 { + + pub fn new(x: FGi32, y: FGi32, z: FGi32) -> Self { + Self { + x, + y, + z + } + } + pub fn lit(x: &str, y: &str, z: &str) -> Self { Self { x: FGi32::lit(x), @@ -19,4 +28,8 @@ impl FGVec3 { z: FGi32::lit(z), } } -} \ No newline at end of file + + pub fn to_vec3(&self) -> Vec3 { + Vec3::new(self.x.to_num(), self.y.to_num(), self.z.to_num()) + } +}