diff --git a/Cargo.lock b/Cargo.lock index 3c9c21a..6ff4055 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,6 +356,30 @@ dependencies = [ "bevy_internal", ] +[[package]] +name = "bevy-trait-query" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40775521e471d7bee8e5571ac706ed4d2df896a85242201f8491eb0d02a8e44a" +dependencies = [ + "bevy-trait-query-impl", + "bevy_app", + "bevy_ecs", + "tracing", +] + +[[package]] +name = "bevy-trait-query-impl" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec6ac32b12f325ab3781e8998c1f8e99e7ec6f73e934988427fde68e324b751d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bevy_a11y" version = "0.18.1" @@ -588,6 +612,8 @@ name = "bevy_combat_proto" version = "0.1.0" dependencies = [ "bevy", + "bevy-trait-query", + "bevy_ecs", "bevy_rapier2d", "leafwing-input-manager", "petgraph", diff --git a/Cargo.toml b/Cargo.toml index 148865f..c87c5c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2024" [dependencies] bevy = { version = "0.18.0", features = ["bevy_remote"] } +bevy-trait-query = "0.18.0" +bevy_ecs = "0.18.1" bevy_rapier2d = "0.33.0" leafwing-input-manager = "0.20.0" petgraph = { version = "0.8.3" } diff --git a/src/combat/attack.rs b/src/combat/attack.rs index 0c89016..95ead23 100644 --- a/src/combat/attack.rs +++ b/src/combat/attack.rs @@ -15,6 +15,7 @@ pub struct AttackArea { accuracy: f32, damage: f32, timer: Timer, + half_area: Vec2, max_distance: f32, } @@ -32,24 +33,23 @@ impl AttackArea { accuracy, damage, timer: Timer::new(duration, TimerMode::Once), + half_area, max_distance, } } /// Returns attack area bundle with everything needed - pub fn bundle( - damage: f32, - accuracy: f32, - duration: Duration, + pub fn into_bundle( + self, position: Vec2, - half_area: Vec2, facing_left: bool, affinity: Group, ) -> impl Bundle { + let half_area = self.half_area; let origin_x = if facing_left { position.x - half_area.x } else { position.x + half_area.x }; ( // Basic - AttackArea::new(accuracy, damage, duration, half_area), + self, // Collision Transform::from_xyz(position.x, position.y, 0.), diff --git a/src/graph/action/mod.rs b/src/graph/action/mod.rs index 352ed4d..2508542 100644 --- a/src/graph/action/mod.rs +++ b/src/graph/action/mod.rs @@ -103,7 +103,7 @@ pub trait PhysicalAction: NodeTrait { /// Graph that defines relations between physical actions /// See module docs for usage example -#[derive(Clone, Debug)] +#[derive(Component, Clone, Debug)] pub struct ActionGraph where Phys: NodeTrait, Log: Eq { graph: DiGraphMap, Log>, diff --git a/src/input.rs b/src/input.rs index b2e4a42..8d8da5e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -33,9 +33,9 @@ impl DefaultInputMap for PlayerInput { .with(Self::DodgeBlock, GamepadButton::South) .with(Self::LightAttack, MouseButton::Left) - .with(Self::HeavyAttack, GamepadButton::North) + .with(Self::LightAttack, GamepadButton::North) - .with(Self::LightAttack, MouseButton::Right) + .with(Self::HeavyAttack, MouseButton::Right) .with(Self::HeavyAttack, GamepadButton::East) } } diff --git a/src/lib.rs b/src/lib.rs index 82c458a..fba163b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ pub mod graph; pub mod input; pub mod player; pub mod plugin; -pub mod states; pub mod timer; use bevy::prelude::*; @@ -38,5 +37,6 @@ pub const GROUP_ATTACK: Group = Group::GROUP_5; /// Temporary function to setup world pub fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Player::bundle(&asset_server, Vec2::ZERO)); - commands.spawn(BpmTimer::new(120.)); + commands.spawn(BpmTimer::new(120.)) + .observe(player::systems::on_timer_tick); } diff --git a/src/player/mod.rs b/src/player/mod.rs index 9c486f0..6c7e446 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -3,15 +3,22 @@ use bevy::{camera::ScalingMode, prelude::*}; use bevy_rapier2d::prelude::*; use leafwing_input_manager::prelude::*; +use seldom_state::prelude::*; use crate::{ - GROUP_ATTACK, GROUP_ENEMY, GROUP_FRIENDLY, GROUP_INTERACTIVE, GROUP_STATIC, input::{ + GROUP_ATTACK, GROUP_ENEMY, GROUP_FRIENDLY, GROUP_INTERACTIVE, GROUP_STATIC, graph::action::ActionGraph, input::{ DefaultInputMap, PlayerInput, - }, meters + }, meters, player::weapon::{AttackType, Weapon} }; +pub mod states; pub mod systems; +pub mod triggers; +pub mod weapon; + +type AttackGraph = ActionGraph; +type InputState = ActionState; /// Player component #[derive(Component, Clone, Copy, PartialEq, Debug, Reflect)] @@ -47,19 +54,58 @@ impl Player { PlayerInput::input_map(), // Collision - RigidBody::KinematicPositionBased, - KinematicCharacterController::default(), - ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC, - CollisionGroups::new( - GROUP_FRIENDLY, - GROUP_STATIC | GROUP_INTERACTIVE | GROUP_ENEMY | GROUP_ATTACK, + ( + RigidBody::KinematicPositionBased, + KinematicCharacterController::default(), + ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC, + CollisionGroups::new( + GROUP_FRIENDLY, + GROUP_STATIC | GROUP_INTERACTIVE | GROUP_ENEMY | GROUP_ATTACK, + ), + Collider::cuboid(meters(0.3), meters(0.9)), + ActiveEvents::COLLISION_EVENTS, + Sleeping::disabled(), ), - Collider::cuboid(meters(0.3), meters(0.9)), - ActiveEvents::COLLISION_EVENTS, - Sleeping::disabled(), - // State - crate::states::Free, + // State Machine + states::Free, + StateMachine::default() + // Reset graph before Free + .on_enter::(|c| { c.entry::().and_modify(|mut g| g.reset()); }) + // Free -> Choosing + .trans_builder( + triggers::any_action_just_pressed(vec![ + PlayerInput::HeavyAttack, + PlayerInput::LightAttack, + ]), + triggers::from_free_to_choosing, + ) + // Choosing -> Free + .trans::(done(None), states::Free) + // Choosing -> Attacking + .trans_builder( + triggers::attack_queued, + triggers::from_choosing_to_attacking, + ) + // Remove NextAttack before Attacking + .on_enter::(|c| { c.remove::(); } ) + // Attacking -> Awaiting + .trans::(done(None), states::Awaiting { beats_remaining: 1 }) + // Awaiting -> Free + .trans::(done(None), states::Free) + // Awaiting -> Choosing + .trans_builder( + triggers::any_action_just_pressed(vec![ + PlayerInput::HeavyAttack, + PlayerInput::LightAttack, + ]), + triggers::from_awaiting_to_choosing, + ) + .set_trans_logging(true), + + // Weapon + weapon::knife::Knife::default(), + weapon::knife::Knife::default().graph(), Children::spawn(( // Camera diff --git a/src/player/states.rs b/src/player/states.rs new file mode 100644 index 0000000..7991e2b --- /dev/null +++ b/src/player/states.rs @@ -0,0 +1,47 @@ +//! Commonly used entity states + +use std::fmt::Debug; + +use bevy::prelude::*; + +use crate::{graph::action::ActionNode, input::PlayerInput, player::weapon::AttackType}; + +/// Player is not doing anything special +#[derive(Component, Clone, Copy, PartialEq, Eq, Debug, Default, Reflect)] +#[reflect(Component, Clone, PartialEq, Debug, Default)] +#[component(storage = "SparseSet")] +pub struct Free; + +/// Player is choosing next action +#[derive(Component, Clone, Copy, PartialEq, Eq, Debug, Reflect)] +#[reflect(Component, Clone, PartialEq, Debug)] +#[component(storage = "SparseSet")] +pub struct Choosing { + /// Which logical action was sent + pub log: PlayerInput, +} + +/// Player is acting and cannot do something else +#[derive(Component, Clone, Copy, PartialEq, Eq, Debug, Reflect)] +#[reflect(Component, Clone, PartialEq, Debug)] +#[component(storage = "SparseSet")] +pub struct Attacking { + /// Which physical action was chosen + pub phys: ActionNode, + /// How much time left before action ends + pub beats_remaining: u32, +} + +/// Next attack to be performed +#[derive(Component, Clone, Copy, PartialEq, Eq, Debug, Reflect)] +#[reflect(Component, Clone, PartialEq, Debug)] +pub struct NextAttack(pub Attacking); + +/// Player is awaiting next action +#[derive(Component, Clone, Copy, PartialEq, Eq, Debug, Default, Reflect)] +#[reflect(Component, Clone, PartialEq, Debug, Default)] +#[component(storage = "SparseSet")] +pub struct Awaiting { + /// How much time left before transitioning to Free + pub beats_remaining: u32, +} diff --git a/src/player/systems.rs b/src/player/systems.rs index 1456791..3274a08 100644 --- a/src/player/systems.rs +++ b/src/player/systems.rs @@ -1,25 +1,99 @@ //! Player systems +use crate::timer::TickEvent; + use super::*; /// Do something based on [PlayerInput] pub fn handle_input( - time: Res