use bevy::prelude::*;
use log::warn;
use smallvec::{SmallVec, smallvec};
use crate::prelude::*;
#[derive(Component, Debug, Clone)]
#[cfg_attr(feature = "reflect", derive(Reflect), reflect(Clone, Component, Debug))]
pub struct Chord {
pub actions: SmallVec<[Entity; 2]>,
}
impl Chord {
#[must_use]
pub fn single(action: Entity) -> Self {
Self::new(smallvec![action])
}
#[must_use]
pub fn new(actions: impl Into<SmallVec<[Entity; 2]>>) -> Self {
Self {
actions: actions.into(),
}
}
}
impl InputCondition for Chord {
fn evaluate(
&mut self,
actions: &ActionsQuery,
_time: &ContextTime,
_value: ActionValue,
) -> TriggerState {
let mut max_state = Default::default();
let mut all_fired = true;
for &action in &self.actions {
let Ok((_, &state, ..)) = actions.get(action) else {
warn!("`{action}` is not a valid action");
continue;
};
if state != TriggerState::Fired {
all_fired = false;
}
if state > max_state {
max_state = state;
}
}
if !all_fired {
max_state = max_state.min(TriggerState::Ongoing);
}
max_state
}
fn kind(&self) -> ConditionKind {
ConditionKind::Implicit
}
}
#[cfg(test)]
mod tests {
use bevy_enhanced_input_macros::InputAction;
use super::*;
use crate::context;
#[test]
fn fired() {
let (mut world, mut state) = context::init_world();
let action = world
.spawn((Action::<Test>::new(), TriggerState::Fired))
.id();
let (time, actions) = state.get(&world);
let mut condition = Chord::single(action);
assert_eq!(
condition.evaluate(&actions, &time, true.into()),
TriggerState::Fired,
);
}
#[test]
fn ongoing() {
let (mut world, mut state) = context::init_world();
let action1 = world
.spawn((Action::<Test>::new(), TriggerState::Fired))
.id();
let action2 = world
.spawn((Action::<Test>::new(), TriggerState::None))
.id();
let (time, actions) = state.get(&world);
let mut condition = Chord::new([action1, action2]);
assert_eq!(
condition.evaluate(&actions, &time, true.into()),
TriggerState::Ongoing,
);
}
#[test]
fn none() {
let (mut world, mut state) = context::init_world();
let action1 = world
.spawn((Action::<Test>::new(), TriggerState::None))
.id();
let action2 = world
.spawn((Action::<Test>::new(), TriggerState::None))
.id();
let (time, actions) = state.get(&world);
let mut condition = Chord::new([action1, action2]);
assert_eq!(
condition.evaluate(&actions, &time, true.into()),
TriggerState::None,
);
}
#[test]
fn missing_action() {
let (world, mut state) = context::init_world();
let (time, actions) = state.get(&world);
let mut condition = Chord::single(Entity::PLACEHOLDER);
assert_eq!(
condition.evaluate(&actions, &time, true.into()),
TriggerState::None,
);
}
#[derive(InputAction)]
#[action_output(bool)]
struct Test;
}