wowlab_parsers/parsers/spell_desc/renderer/
evaluate.rs1use super::{
2 BINARY_ARGS, FLOAT_EPSILON, RenderContext, TERNARY_ARGS, build_misc_key, build_spell_level_key,
3};
4use crate::parsers::{
5 formatting::{NearIntegerStrategy, format_decimal_trimmed},
6 spell_desc::{
7 resolver::SpellDescResolver,
8 types::{
9 BinaryOperator, ConditionalPredicateNode, ExpressionNode, SingleConditionNode,
10 UnaryOperator, VariableNode,
11 },
12 },
13};
14
15const DEFAULT_DECIMAL_PLACES: u8 = 2;
16
17impl<R> RenderContext<'_, R>
18where
19 R: SpellDescResolver,
20{
21 pub(super) fn evaluate_predicate(&self, predicate: &ConditionalPredicateNode) -> bool {
22 predicate
23 .conditions
24 .iter()
25 .any(|c| self.evaluate_condition(c))
26 }
27
28 pub(super) fn evaluate_expression(&self, expr: &ExpressionNode) -> f64 {
30 match expr {
31 ExpressionNode::Number(num) => num.value(),
32
33 ExpressionNode::Variable(var) => self.evaluate_variable(var),
34
35 ExpressionNode::Binary(binary) => {
36 let left = self.evaluate_expression(&binary.left);
37 let right = self.evaluate_expression(&binary.right);
38
39 match binary.operator {
40 BinaryOperator::Add => left + right,
41 BinaryOperator::Sub => left - right,
42 BinaryOperator::Mul => left * right,
43 BinaryOperator::Div => {
44 if right == 0.0 {
45 0.0
46 } else {
47 left / right
48 }
49 }
50 }
51 }
52
53 ExpressionNode::Unary(unary) => {
54 let operand = self.evaluate_expression(&unary.operand);
55
56 match unary.operator {
57 UnaryOperator::Neg => -operand,
58 }
59 }
60
61 ExpressionNode::FunctionCall(call) => {
62 self.evaluate_function(&call.func_name, &call.args)
63 }
64
65 ExpressionNode::Paren(paren) => self.evaluate_expression(&paren.expression),
66 }
67 }
68
69 fn evaluate_condition(&self, condition: &SingleConditionNode) -> bool {
70 match condition {
71 SingleConditionNode::SpellKnown(spell_known) => {
72 self.resolver.knows_spell(spell_known.spell_id())
73 }
74
75 SingleConditionNode::Aura(aura) => self.resolver.has_aura(aura.aura_id()),
76
77 SingleConditionNode::Specialization(spec) => {
78 self.resolver.is_specialization(spec.spec_index())
79 }
80
81 SingleConditionNode::PlayerCondition(_) => false,
83
84 SingleConditionNode::Expression(expr) => {
85 self.evaluate_condition_expression(&expr.func_name, &expr.args)
86 }
87 }
88 }
89
90 fn evaluate_condition_expression(&self, func_name: &str, args: &[ExpressionNode]) -> bool {
92 let values: Vec<f64> = args
93 .iter()
94 .map(|arg| self.evaluate_expression(arg))
95 .collect();
96
97 match func_name {
98 "gt" => values.len() >= BINARY_ARGS && values[0] > values[1],
99 "gte" => values.len() >= BINARY_ARGS && values[0] >= values[1],
100 "lt" => values.len() >= BINARY_ARGS && values[0] < values[1],
101 "lte" => values.len() >= BINARY_ARGS && values[0] <= values[1],
102 "eq" => values.len() >= BINARY_ARGS && (values[0] - values[1]).abs() < f64::EPSILON,
103 "cond" => !values.is_empty() && values[0] != 0.0,
104 _ => false,
105 }
106 }
107
108 fn evaluate_variable(&self, var: &VariableNode) -> f64 {
109 match var {
110 VariableNode::Effect(effect) => self
111 .resolver
112 .get_effect_value(self.self_spell_id, effect.effect_index, &effect.var_type)
113 .unwrap_or(0.0),
114
115 VariableNode::SpellLevel(spell_level) => {
116 let key = build_spell_level_key(&spell_level.var_type, spell_level.index);
117
118 self.resolver
119 .get_spell_value(self.self_spell_id, &key)
120 .and_then(|s| s.split_whitespace().next()?.parse().ok())
121 .unwrap_or(0.0)
122 }
123
124 VariableNode::Player(player) => self
125 .resolver
126 .get_player_stat(player.var_name())
127 .unwrap_or(0.0),
128
129 VariableNode::CrossSpell(cross) => {
130 if let Some(idx) = cross.effect_index {
131 self.resolver
132 .get_effect_value(cross.spell_id, idx, &cross.var_type)
133 .unwrap_or(0.0)
134 } else {
135 self.resolver
136 .get_spell_value(cross.spell_id, &cross.var_type)
137 .and_then(|s| s.split_whitespace().next()?.parse().ok())
138 .unwrap_or(0.0)
139 }
140 }
141
142 VariableNode::Custom(custom) => self
143 .resolver
144 .get_custom_var(custom.var_name())
145 .unwrap_or(0.0),
146
147 VariableNode::At(at) => self.resolver.get_player_stat(&at.var_type).unwrap_or(0.0),
148
149 VariableNode::Enchant(enchant) => self
150 .resolver
151 .get_spell_value(self.self_spell_id, enchant.var_type())
152 .and_then(|s| s.split_whitespace().next()?.parse().ok())
153 .or_else(|| self.resolver.get_player_stat(enchant.var_type()))
154 .unwrap_or(0.0),
155
156 VariableNode::Misc(misc) => {
157 let key = build_misc_key(&misc.var_name, misc.id);
158
159 self.resolver
160 .get_spell_value(self.self_spell_id, &key)
161 .and_then(|s| s.split_whitespace().next()?.parse().ok())
162 .or_else(|| self.resolver.get_player_stat(&key))
163 .unwrap_or(0.0)
164 }
165 }
166 }
167
168 fn evaluate_function(&self, func_name: &str, args: &[ExpressionNode]) -> f64 {
171 let values: Vec<f64> = args
172 .iter()
173 .map(|arg| self.evaluate_expression(arg))
174 .collect();
175
176 match func_name {
177 "max" => values.iter().copied().fold(f64::NEG_INFINITY, f64::max),
178 "min" => values.iter().copied().fold(f64::INFINITY, f64::min),
179 "floor" => values.first().map_or(0.0, |v| v.floor()),
180 "ceil" => values.first().map_or(0.0, |v| v.ceil()),
181 "abs" => values.first().map_or(0.0, |v| v.abs()),
182 "clamp" => {
184 if values.len() >= TERNARY_ARGS {
185 values[0].clamp(values[1], values[2])
186 } else {
187 values.first().copied().unwrap_or(0.0)
188 }
189 }
190 "cond" => {
192 if values.len() >= TERNARY_ARGS {
193 if values[0] == 0.0 {
194 values[2]
195 } else {
196 values[1]
197 }
198 } else {
199 0.0
200 }
201 }
202 "gt" if values.len() >= BINARY_ARGS && values[0] > values[1] => 1.0,
203 "gte" if values.len() >= BINARY_ARGS && values[0] >= values[1] => 1.0,
204 "lt" if values.len() >= BINARY_ARGS && values[0] < values[1] => 1.0,
205 "lte" if values.len() >= BINARY_ARGS && values[0] <= values[1] => 1.0,
206 _ => 0.0,
207 }
208 }
209}
210
211pub(super) fn format_rendered_number(value: f64, decimal_places: Option<u8>) -> String {
212 match decimal_places {
213 Some(places) => format!("{:.1$}", value, places as usize),
214 None => format_decimal_trimmed(
215 value,
216 DEFAULT_DECIMAL_PLACES,
217 NearIntegerStrategy::NearestSaturating {
218 epsilon: FLOAT_EPSILON,
219 },
220 ),
221 }
222}
223
224pub(super) fn format_effect_value(value: f64, var_type: &str) -> String {
225 match var_type {
226 "t" => format!("{value}"),
227 "bc" => format!("{}%", format_rendered_number(value * 100.0, Some(1))),
228 _ => format_rendered_number(value, None),
229 }
230}