//! Everything fighter-shaped: the components that describe a fighter, the //! state machine that drives it, and the plugin that schedules it all. use bevy::prelude::*; use bevy_egui::EguiPrimaryContextPass; use bevy_ggrs::prelude::*; pub mod animation; pub mod attributes; pub mod collision; pub mod debug; pub mod ecb; pub mod manifest; pub mod motion; pub mod state; pub mod visual; // The fighter components are re-exported so the rest of the crate can say // `fighter::FighterVelocity` without caring which file it lives in. Named // explicitly rather than globbed, so moving a type between submodules can't // silently change what this module exports. pub use attributes::FighterAttributes; pub use ecb::FighterECB; pub use manifest::FighterId; pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity, Grounded}; use crate::{ fighter::{ animation::animation_init, state::{FighterState, fall::FallState}, visual::FighterAnimations, }, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, stage::line::StageCollision, }; use state::state_interrupt_system; /// Owns fighter simulation, the rollback registrations for fighter /// components, and the fighter debug overlays. #[derive(Default)] pub struct FighterPlugin; #[derive(Component, Copy, Clone, Hash, Debug, PartialEq, Reflect)] pub enum FighterFacingDirection { Left, Right, } impl FighterFacingDirection { pub fn to_sign(&self) -> FGi32 { match self { FighterFacingDirection::Left => FGi32::NEG_ONE, FighterFacingDirection::Right => FGi32::ONE, } } } #[derive(Component)] #[require(Transform)] pub struct FighterVisual; impl Plugin for FighterPlugin { fn build(&self, app: &mut App) { app.add_systems( Update, (animation_init, animation::setup_fighter_animation_player), ) // Every state gets a chance to interrupt itself, in priority order. .add_systems( GgrsSchedule, ( ecb::snapshot_fighter_ecb.before(state_interrupt_system), state::state_interrupt_system, ) .in_set(GameplaySet::Interrupt), ) .add_systems( GgrsSchedule, (state::state_update_system,).in_set(GameplaySet::StateUpdate), ) /*.add_systems( GgrsSchedule, (motion::integrate_gravity, motion::apply_grounded_motion) .chain() .in_set(GameplaySet::Physics), )*/ .add_systems( GgrsSchedule, state::state_collision_interrupt_system.in_set(GameplaySet::Collision), ) .add_systems( GgrsSchedule, ( animation::apply_fighter_translation_to_visuals, animation::apply_animation, ) .chain() .in_set(GameplaySet::Animation), ) // Rollback registration lives next to the systems that write these // components; a component simulated here but missing from this list // is a desync waiting to happen. .rollback_component_with_copy::() .rollback_component_with_copy::() .rollback_component_with_copy::() .rollback_component_with_copy::() .rollback_component_with_copy::() .rollback_component_with_copy::() .rollback_component_with_copy::() .rollback_resource_with_reflect::() // Debug views. .add_systems(FixedPostUpdate, debug::debug_draw_ecb) .add_systems(EguiPrimaryContextPass, debug::fighter_debug) .add_systems(EguiPrimaryContextPass, debug::update_config) .rollback_component_with_clone::() .checksum_component_with_hash::() .register_type::(); } } pub fn spawn_fighter( commands: &mut Commands, player_handle: usize, spawn_position: FGVec2, attributes: FighterAttributes, animations: FighterAnimations, visual_root: WorldAssetRoot, ) { commands.spawn(( Name(format!("Fighter {player_handle}").into()), Player { handle: player_handle, }, FighterFacingDirection::Right, FighterECB { vertical_half: FGi32::lit("5.0"), horizontal_half: FGi32::lit("2.5"), }, FighterVelocity::default(), FighterTranslation(spawn_position), attributes, visual_root, animations, FighterState::Fall(FallState), FighterVisual, FighterInput::default(), )); }