use bevy::prelude::*;
use evalexpr::{ContextWithMutableVariables, Value};
use crate::prelude::{Expression, ModifierType, StatPath, Stats, StatsMutator};
#[derive(Component, Clone, Debug, Default)]
pub struct InstantModifierSet(pub(crate) Vec<InstantEntry>);
#[derive(Clone, Debug)]
pub struct InstantEntry {
pub path: String,
pub op: InstantOp,
pub value: ModifierType,
}
#[derive(Clone, Debug)]
pub enum InstantOp { Set, Add, Sub }
impl InstantModifierSet {
pub fn add_set<V: Into<ModifierType>>(&mut self, path: &str, value: V) {
self.0.push(InstantEntry { path: path.to_string(), op: InstantOp::Set, value: value.into() });
}
pub fn add_add<V: Into<ModifierType>>(&mut self, path: &str, value: V) {
self.0.push(InstantEntry { path: path.to_string(), op: InstantOp::Add, value: value.into() });
}
pub fn add_sub<V: Into<ModifierType>>(&mut self, path: &str, value: V) {
self.0.push(InstantEntry { path: path.to_string(), op: InstantOp::Sub, value: value.into() });
}
}
pub type RoleMap<'a> = &'a [(&'a str, Entity)];
fn resolve_identifier_value(
identifier: &str,
roles: RoleMap,
q_stats: &Query<&Stats>,
default_role: &str,
) -> f32 {
let path = StatPath::parse(identifier);
let role_key = path.target().unwrap_or(default_role);
let stat_name = path.name();
let Some((_, entity)) = roles.iter().find(|(k, _)| *k == role_key) else { return 0.0 };
q_stats.get(*entity).unwrap().get(stat_name)
}
pub fn evaluate_expression_with_roles_ctx(
expr: &Expression,
roles: RoleMap,
q_stats: &Query<&Stats>,
default_role: &str,
context: Option<&[(&str, f32)]>,
) -> f32 {
let mut ctx = evalexpr::HashMapContext::new();
for var in expr.compiled.iter_identifiers() {
let value = resolve_identifier_value(var, roles, q_stats, default_role) as f64;
let _ = ctx.set_value(var.to_string(), Value::Float(value));
}
if let Some(pairs) = context {
for (k, v) in pairs {
let _ = ctx.set_value((*k).to_string(), Value::Float(*v as f64));
}
}
expr.evaluate(&ctx)
}
pub fn evaluate_expression_with_roles(
expr: &Expression,
roles: RoleMap,
q_stats: &Query<&Stats>,
default_role: &str,
) -> f32 {
evaluate_expression_with_roles_ctx(expr, roles, q_stats, default_role, None)
}
#[derive(Clone, Debug)]
pub struct EvaluatedInstantEntry {
pub path: String,
pub op: InstantOp,
pub value: f32,
}
pub fn evaluate_instant_values(
instant: &InstantModifierSet,
roles: RoleMap,
sm: &mut StatsMutator,
default_role: &str,
) -> Vec<EvaluatedInstantEntry> {
let mut out: Vec<EvaluatedInstantEntry> = Vec::with_capacity(instant.0.len());
for entry in instant.0.iter() {
let value = match &entry.value {
ModifierType::Literal(v) => *v,
ModifierType::Expression(expr) => {
sm.with_stats_query(|q_stats| {
evaluate_expression_with_roles(expr, roles, &q_stats, default_role)
})
}
};
out.push(EvaluatedInstantEntry {
path: entry.path.clone(),
op: entry.op.clone(),
value,
});
}
out
}
pub fn apply_evaluated_instant_to_entity(
evaluated: &[EvaluatedInstantEntry],
sm: &mut crate::prelude::StatsMutator,
target_entity: Entity,
) {
for op in evaluated.iter() {
match op.op {
InstantOp::Set => {
sm.set(target_entity, &op.path, op.value);
}
InstantOp::Add => {
let current = sm.get(target_entity, &op.path);
sm.set(target_entity, &op.path, current + op.value);
}
InstantOp::Sub => {
let current = sm.get(target_entity, &op.path);
sm.set(target_entity, &op.path, current - op.value);
}
}
}
}
pub fn apply_instant(
instant: &InstantModifierSet,
roles: RoleMap,
default_role: &str,
sm: &mut crate::prelude::StatsMutator,
target_entity: Entity,
) {
let evaluated = evaluate_instant_values(instant, roles, sm, default_role);
apply_evaluated_instant_to_entity(&evaluated, sm, target_entity);
}