bevy_stat_query 0.5.0

Blazing fast and versatile RPG stat system for the bevy engine.
Documentation
use bevy::{
    asset::{Asset, AssetApp, AssetPlugin, Assets, Handle},
    prelude::{ResMut, Single},
};
use bevy_app::App;
use bevy_ecs::{
    component::Component,
    entity::Entity,
    query::{QueryData, With},
    system::{Commands, Res, RunSystemOnce},
};
use bevy_reflect::TypePath;
use bevy_stat_query::{
    types::StatFloat, ChildQuery, QualifierQuery, Querier, QueryStream, Stat, StatEntities,
    StatEntity, StatValue, StatValuePair,
};

#[derive(Debug, Clone, Copy, Stat)]
#[stat(value = "StatFloat<f32>")]
pub enum Stats {
    Damage,
    Defense,
}

#[derive(Asset, TypePath)]
pub struct Weapon {
    pub damage: f32,
}

#[derive(Debug, Component)]
pub struct WeaponHandle(Handle<Weapon>);

#[derive(Component)]
pub struct WeaponState {
    pub durability: f32,
}

#[derive(Component)]
pub struct A;

#[derive(Component)]
pub struct B;

#[derive(QueryData)]
pub struct WeaponQuery {
    weapon: &'static WeaponHandle,
    state: &'static WeaponState,
}

impl QueryStream for WeaponQuery {
    type Qualifier = u32;
    type Context = Res<'static, Assets<Weapon>>;
    type Query = WeaponQuery;

    fn stream_stat(
        query: WeaponQueryItem,
        context: &Res<Assets<Weapon>>,
        _: Entity,
        _: &QualifierQuery<Self::Qualifier>,
        stat_value: &mut StatValuePair,
        _: Querier<Self::Qualifier>,
    ) {
        if let Some(value) = stat_value.is_then_cast(&Stats::Damage) {
            let Some(weapon) = context.get(query.weapon.0.id()) else {
                return;
            };
            value.add(weapon.damage * query.state.durability)
        }
    }
}

#[test]
pub fn asset_test() {
    let mut app = App::new();
    app.add_plugins(AssetPlugin::default())
        .init_asset::<Weapon>();
    app.world_mut().run_system_once(init).unwrap();
    app.world_mut().flush();
    app.world_mut().run_system_once(query).unwrap();
}

fn init(mut commands: Commands, mut assets: ResMut<Assets<Weapon>>) {
    commands.spawn((StatEntity, A)).with_children(|x| {
        x.spawn((
            WeaponHandle(assets.add(Weapon { damage: 4.0 })),
            WeaponState { durability: 0.5 },
        ));
    });
    commands.spawn((StatEntity, B)).with_children(|x| {
        x.spawn((
            WeaponHandle(assets.add(Weapon { damage: 8.0 })),
            WeaponState { durability: 1.0 },
        ));
        x.spawn((
            WeaponHandle(assets.add(Weapon { damage: 6.0 })),
            WeaponState { durability: 2.0 },
        ));
    });
}

fn query(
    querier: StatEntities<u32>,
    weapon_query: ChildQuery<WeaponQuery>,
    a: Single<Entity, (With<StatEntity>, With<A>)>,
    b: Single<Entity, (With<StatEntity>, With<B>)>,
) {
    let querier = querier.join(&weapon_query);
    assert_eq!(
        querier.eval_stat(*a, &QualifierQuery::Aggregate(0u32), &Stats::Damage),
        Some(2.0)
    );
    assert_eq!(
        querier.eval_stat(*a, &QualifierQuery::Aggregate(0u32), &Stats::Defense),
        Some(0.0)
    );

    assert_eq!(
        querier.eval_stat(*b, &QualifierQuery::Aggregate(0u32), &Stats::Damage),
        Some(20.0)
    );
    assert_eq!(
        querier.eval_stat(*b, &QualifierQuery::Aggregate(0u32), &Stats::Defense),
        Some(0.0)
    );
}