wowlab_parsers/parsers/spell_desc/renderer/
render.rs1use wowlab_types::spell_desc::SpellDescFragment;
2
3use super::{
4 MAX_EMBED_DEPTH, RenderContext, build_misc_key, build_spell_level_key, capitalize_if_needed,
5 evaluate::{format_effect_value, format_rendered_number},
6 merge_text_fragments, parse_duration_ms,
7};
8use crate::parsers::spell_desc::{
9 parser::parse,
10 resolver::SpellDescResolver,
11 types::{
12 AtVariableNode, ConditionalNode, GenderNode, PluralizationNode, SpellDescriptionNode,
13 VariableNode,
14 },
15};
16
17impl<R> RenderContext<'_, R>
18where
19 R: SpellDescResolver,
20{
21 pub(super) fn render_nodes(
22 &mut self,
23 nodes: &[SpellDescriptionNode],
24 ) -> Vec<SpellDescFragment> {
25 let mut fragments = Vec::new();
26
27 for node in nodes {
28 fragments.extend(self.render_node(node));
29 }
30
31 merge_text_fragments(&mut fragments);
32
33 fragments
34 }
35
36 fn render_node(&mut self, node: &SpellDescriptionNode) -> Vec<SpellDescFragment> {
37 match node {
38 SpellDescriptionNode::Text(text) => {
39 vec![SpellDescFragment::Text {
41 value: text.value().replace('\r', ""),
42 }]
43 }
44
45 SpellDescriptionNode::ColorCode(color) => {
46 if color.code() == "r" {
47 vec![SpellDescFragment::ColorEnd]
48 } else {
49 vec![SpellDescFragment::ColorStart {
50 color: color.code().to_owned(),
51 }]
52 }
53 }
54
55 SpellDescriptionNode::Variable(var) => self.render_variable(var),
56
57 SpellDescriptionNode::ExpressionBlock(block) => {
58 let value = self.evaluate_expression(&block.expression);
59
60 self.last_number = Some(value);
61
62 vec![SpellDescFragment::Value {
63 value: format_rendered_number(value, block.decimal_places),
64 raw: value,
65 }]
66 }
67
68 SpellDescriptionNode::Conditional(cond) => self.render_conditional(cond),
69
70 SpellDescriptionNode::Pluralization(plural) => self.render_pluralization(plural),
71
72 SpellDescriptionNode::Gender(gender) => self.render_gender(gender),
73 }
74 }
75
76 fn render_variable(&mut self, var: &VariableNode) -> Vec<SpellDescFragment> {
78 match var {
79 VariableNode::Effect(effect) => {
80 if let Some(value) = self.resolver.get_effect_value(
81 self.self_spell_id,
82 effect.effect_index,
83 &effect.var_type,
84 ) {
85 self.last_number = Some(value);
86
87 vec![SpellDescFragment::Value {
88 value: format_effect_value(value, &effect.var_type),
89 raw: value,
90 }]
91 } else {
92 let token = format!("${}{}", effect.var_type, effect.effect_index);
93
94 self.warn(format!("Unresolved effect variable: {token}"));
95
96 vec![SpellDescFragment::Unresolved { token }]
97 }
98 }
99
100 VariableNode::SpellLevel(spell_level) => {
101 let key = build_spell_level_key(&spell_level.var_type, spell_level.index);
102
103 if let Some(value) = self.resolver.get_spell_value(self.self_spell_id, &key) {
104 if let Ok(num) = value.split_whitespace().next().unwrap_or("").parse::<f64>() {
105 self.last_number = Some(num);
106 }
107
108 if spell_level.var_type == "d" {
109 if let Some(raw_ms) = parse_duration_ms(&value) {
110 return vec![SpellDescFragment::Duration { value, raw_ms }];
111 }
112 }
113
114 vec![SpellDescFragment::Text { value }]
115 } else {
116 let token = format!("${key}");
117
118 self.warn(format!("Unresolved spell-level variable: {token}"));
119
120 vec![SpellDescFragment::Unresolved { token }]
121 }
122 }
123
124 VariableNode::Player(player) => {
125 if let Some(value) = self.resolver.get_player_stat(player.var_name()) {
126 self.last_number = Some(value);
127
128 vec![SpellDescFragment::Value {
129 value: format_rendered_number(value, None),
130 raw: value,
131 }]
132 } else {
133 let token = format!("${}", player.var_name());
134
135 self.warn(format!("Unresolved player stat: {token}"));
136
137 vec![SpellDescFragment::Unresolved { token }]
138 }
139 }
140
141 VariableNode::CrossSpell(cross) => {
142 if let Some(idx) = cross.effect_index {
143 if let Some(value) =
144 self.resolver
145 .get_effect_value(cross.spell_id, idx, &cross.var_type)
146 {
147 self.last_number = Some(value);
148
149 vec![SpellDescFragment::Value {
150 value: format_effect_value(value, &cross.var_type),
151 raw: value,
152 }]
153 } else {
154 let token = format!("${}{}{}", cross.spell_id, cross.var_type, idx);
155
156 self.warn(format!("Unresolved cross-spell effect: {token}"));
157
158 vec![SpellDescFragment::Unresolved { token }]
159 }
160 } else if let Some(value) = self
161 .resolver
162 .get_spell_value(cross.spell_id, &cross.var_type)
163 {
164 vec![SpellDescFragment::Text { value }]
165 } else {
166 let token = format!("${}{}", cross.spell_id, cross.var_type);
167
168 self.warn(format!("Unresolved cross-spell value: {token}"));
169
170 vec![SpellDescFragment::Unresolved { token }]
171 }
172 }
173
174 VariableNode::Custom(custom) => {
175 if let Some(value) = self.resolver.get_custom_var(custom.var_name()) {
176 self.last_number = Some(value);
177
178 vec![SpellDescFragment::Value {
179 value: format_rendered_number(value, None),
180 raw: value,
181 }]
182 } else {
183 let token = format!("$<{}>", custom.var_name());
184
185 self.warn(format!("Unresolved custom variable: {token}"));
186
187 vec![SpellDescFragment::Unresolved { token }]
188 }
189 }
190
191 VariableNode::At(at) => self.render_at_variable(at),
192
193 VariableNode::Enchant(enchant) => {
194 if let Some(value) = self
195 .resolver
196 .get_spell_value(self.self_spell_id, enchant.var_type())
197 {
198 vec![SpellDescFragment::Text { value }]
199 } else if let Some(value) = self.resolver.get_player_stat(enchant.var_type()) {
200 self.last_number = Some(value);
201
202 vec![SpellDescFragment::Value {
203 value: format_rendered_number(value, None),
204 raw: value,
205 }]
206 } else {
207 let token = format!("${}", enchant.var_type());
208
209 self.warn(format!("Unresolved enchant variable: {token}"));
210
211 vec![SpellDescFragment::Unresolved { token }]
212 }
213 }
214
215 VariableNode::Misc(misc) => {
216 let key = build_misc_key(&misc.var_name, misc.id);
217
218 if let Some(value) = self.resolver.get_spell_value(self.self_spell_id, &key) {
219 vec![SpellDescFragment::Text { value }]
220 } else if let Some(value) = self.resolver.get_player_stat(&key) {
221 self.last_number = Some(value);
222
223 vec![SpellDescFragment::Value {
224 value: format_rendered_number(value, None),
225 raw: value,
226 }]
227 } else {
228 let token = format!("${key}");
229
230 self.warn(format!("Unresolved misc variable: {token}"));
231
232 vec![SpellDescFragment::Unresolved { token }]
233 }
234 }
235 }
236 }
237
238 fn render_at_variable(&mut self, at: &AtVariableNode) -> Vec<SpellDescFragment> {
240 match at.var_type.as_str() {
241 "spelldesc" => {
242 if self.embed_depth >= MAX_EMBED_DEPTH {
243 self.warn(format!(
244 "Max embed depth ({MAX_EMBED_DEPTH}) exceeded for @spelldesc"
245 ));
246
247 return vec![SpellDescFragment::Text {
248 value: "[...]".to_string(),
249 }];
250 }
251
252 if let Some(spell_id) = at.spell_id {
253 if let Some(desc) = self.resolver.get_spell_description(spell_id) {
254 let saved_spell_id = self.self_spell_id;
255
256 self.self_spell_id = spell_id;
257 self.embed_depth += 1;
258 let parsed = parse(&desc);
259 let fragments = self.render_nodes(&parsed.ast.nodes);
260
261 self.embed_depth -= 1;
262 self.self_spell_id = saved_spell_id;
263
264 vec![SpellDescFragment::Embedded {
265 spell_id,
266 fragments,
267 }]
268 } else {
269 let token = format!("@spelldesc{spell_id}");
270
271 self.warn(format!("Could not fetch embedded spell: {spell_id}"));
272
273 vec![SpellDescFragment::Unresolved { token }]
274 }
275 } else {
276 vec![SpellDescFragment::Unresolved {
277 token: "@spelldesc".to_string(),
278 }]
279 }
280 }
281
282 "spellname" => {
283 if let Some(spell_id) = at.spell_id {
284 if let Some(name) = self.resolver.get_spell_name(spell_id) {
285 vec![SpellDescFragment::SpellName { spell_id, name }]
286 } else {
287 let token = format!("@spellname{spell_id}");
288
289 self.warn(format!("Could not fetch spell name: {spell_id}"));
290
291 vec![SpellDescFragment::Unresolved { token }]
292 }
293 } else {
294 vec![SpellDescFragment::Unresolved {
295 token: "@spellname".to_string(),
296 }]
297 }
298 }
299
300 "spellicon" => {
301 if let Some(spell_id) = at.spell_id {
302 if let Some(path) = self.resolver.get_spell_icon(spell_id) {
303 vec![SpellDescFragment::Icon { spell_id, path }]
304 } else {
305 let token = format!("@spellicon{spell_id}");
306
307 self.warn(format!("Could not fetch spell icon: {spell_id}"));
308
309 vec![SpellDescFragment::Unresolved { token }]
310 }
311 } else {
312 vec![SpellDescFragment::Unresolved {
313 token: "@spellicon".to_string(),
314 }]
315 }
316 }
317
318 "versadmg" | "versaheal" | "versatility" => {
319 if let Some(value) = self.resolver.get_player_stat(&at.var_type) {
320 self.last_number = Some(value);
321
322 vec![SpellDescFragment::Value {
323 value: format!("{}%", format_rendered_number(value, Some(1))),
324 raw: value,
325 }]
326 } else {
327 let token = format!("@{}", at.var_type);
328
329 self.warn(format!("Unresolved @ variable: {token}"));
330
331 vec![SpellDescFragment::Unresolved { token }]
332 }
333 }
334
335 _ => {
336 if let Some(value) = self.resolver.get_player_stat(&at.var_type) {
337 self.last_number = Some(value);
338
339 vec![SpellDescFragment::Value {
340 value: format_rendered_number(value, None),
341 raw: value,
342 }]
343 } else {
344 let token = format!("@{}", at.var_type);
345
346 self.warn(format!("Unresolved @ variable: {token}"));
347
348 vec![SpellDescFragment::Unresolved { token }]
349 }
350 }
351 }
352 }
353
354 fn render_conditional(&mut self, cond: &ConditionalNode) -> Vec<SpellDescFragment> {
355 for branch in &cond.conditions {
356 if self.evaluate_predicate(&branch.predicate) {
357 return self.render_nodes(&branch.content);
358 }
359 }
360
361 if let Some(else_branch) = &cond.else_branch {
362 self.render_nodes(else_branch)
363 } else {
364 Vec::new()
365 }
366 }
367
368 fn render_pluralization(&self, plural: &PluralizationNode) -> Vec<SpellDescFragment> {
369 let is_singular = self
370 .last_number
371 .is_some_and(|n| (n - 1.0).abs() < f64::EPSILON);
372
373 let result = if is_singular {
374 &plural.singular
375 } else {
376 &plural.plural
377 };
378
379 let value = capitalize_if_needed(result, plural.capitalized);
380
381 vec![SpellDescFragment::Text { value }]
382 }
383
384 fn render_gender(&self, gender: &GenderNode) -> Vec<SpellDescFragment> {
385 let result = if self.resolver.is_male() {
386 &gender.male
387 } else {
388 &gender.female
389 };
390
391 let value = capitalize_if_needed(result, gender.capitalized);
392
393 vec![SpellDescFragment::Text { value }]
394 }
395}