use bevy::ecs::lifecycle::HookContext;
use bevy::ecs::world::DeferredWorld;
use bevy::prelude::*;
use crate::attributes::Attributes;
use crate::expr::Expr;
#[derive(Clone, Debug)]
pub struct AttributeRequirement {
source: String,
compiled: Option<Expr>,
}
impl AttributeRequirement {
pub fn new(source: impl Into<String>) -> Self {
Self {
source: source.into(),
compiled: None,
}
}
#[cfg(test)]
fn compiled(source: impl Into<String>) -> Self {
let source = source.into();
Self {
compiled: Self::compile_internal(source.as_str()),
source,
}
}
pub fn compile(&mut self) {
if self.compiled.is_some() {
return;
}
if let Some(expr) = Self::compile_internal(&self.source) {
self.compiled = Some(expr);
}
}
fn compile_internal(source: &str) -> Option<Expr> {
match Expr::compile(source, None) {
Ok(expr) => Some(expr),
Err(err) => {
warn!(
"AttributeRequirement compile error for '{}': {}",
source, err
);
None
}
}
}
pub fn met(&self, attrs: &Attributes) -> bool {
match &self.compiled {
Some(expr) => expr.evaluate(&attrs.context) != 0.0,
None => {
warn!(
"AttributeRequirement::check called before compile for '{}'",
self.source
);
false
}
}
}
pub fn source(&self) -> &str {
&self.source
}
}
impl<S: Into<String>> From<S> for AttributeRequirement {
fn from(value: S) -> Self {
Self::new(value)
}
}
#[derive(Component, Debug, Default, Clone)]
#[component(on_add = compile_requirements_hook)]
pub struct AttributeRequirements(pub Vec<AttributeRequirement>);
fn compile_requirements_hook(mut world: DeferredWorld, ctx: HookContext) {
let Some(mut reqs) = world.get_mut::<AttributeRequirements>(ctx.entity) else {
return;
};
reqs.compile();
}
impl AttributeRequirements {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, expr: impl Into<String>) {
self.0.push(AttributeRequirement::new(expr));
}
pub fn combine(&mut self, other: &AttributeRequirements) {
self.0.extend(other.0.iter().cloned());
}
pub fn compile(&mut self) {
for req in self.0.iter_mut() {
req.compile();
}
}
pub fn met(&self, attrs: &Attributes) -> bool {
self.0.iter().all(|req| req.met(attrs))
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<I, S> From<I> for AttributeRequirements
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
fn from(value: I) -> Self {
Self(value.into_iter().map(AttributeRequirement::new).collect())
}
}
#[macro_export]
macro_rules! requires {
{ $( $expr:literal ),* $(,)? } => {{
let mut _reqs = $crate::requirements::AttributeRequirements::new();
$(
_reqs.add($expr);
)*
_reqs
}};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attribute_id::Interner;
use crate::modifier::Modifier;
use crate::node::ReduceFn;
fn test_interner() -> Interner {
let i = Interner::new();
i.set_global();
Interner::global()
}
fn make_attrs(interner: &Interner, attributes: &[(&str, f32)]) -> Attributes {
let mut attrs = Attributes::new();
for &(name, value) in attributes {
let id = interner.get_or_intern(name);
let node = attrs.ensure_node(id, ReduceFn::Sum);
node.add_modifier(Modifier::Flat(value));
attrs.evaluate_and_cache(id);
}
attrs
}
#[test]
fn single_requirement_met() {
let interner = test_interner();
let attrs = make_attrs(&interner, &[("Strength", 25.0)]);
let req = AttributeRequirement::compiled("Strength >= 10");
assert!(req.met(&attrs));
}
#[test]
fn single_requirement_not_met() {
let interner = test_interner();
let attrs = make_attrs(&interner, &[("Strength", 5.0)]);
let req = AttributeRequirement::compiled("Strength >= 10");
assert!(!req.met(&attrs));
}
#[test]
fn requirements_all_met() {
let interner = test_interner();
let attrs = make_attrs(&interner, &[("Strength", 25.0), ("Level", 10.0)]);
let reqs = {
let mut reqs = AttributeRequirements::from(vec!["Strength >= 10", "Level >= 5"]);
reqs.compile();
reqs
};
assert!(reqs.met(&attrs));
}
#[test]
fn requirements_partial_met() {
let interner = test_interner();
let attrs = make_attrs(&interner, &[("Strength", 25.0), ("Level", 3.0)]);
let reqs = {
let mut reqs = AttributeRequirements::from(vec!["Strength >= 10", "Level >= 5"]);
reqs.compile();
reqs
};
assert!(!reqs.met(&attrs));
}
#[test]
fn empty_requirements_are_met() {
let attrs = Attributes::new();
let reqs = AttributeRequirements::new();
assert!(reqs.met(&attrs));
}
#[test]
fn le_zero_check() {
let interner = test_interner();
let attrs = make_attrs(&interner, &[("ProjectileLife", 0.0)]);
let req = AttributeRequirement::compiled("ProjectileLife <= 0");
assert!(req.met(&attrs));
}
#[test]
fn combine_merges() {
let a = AttributeRequirements::from(vec!["A >= 1"]);
let b = AttributeRequirements::from(vec!["B >= 2", "C >= 3"]);
let mut combined = a;
combined.combine(&b);
assert_eq!(combined.len(), 3);
}
}