Skip to main content

wowlab_cli/commands/snapshot/assisted_rotas/
dsl.rs

1use serde_json::{Value, json};
2use wowlab_parsers::DbcData;
3use wowlab_types::sim::FastMap;
4
5pub(super) fn resolve_spell_id(overrides: &FastMap<i32, i32>, spell_id: i32) -> i32 {
6    overrides.get(&spell_id).copied().unwrap_or(spell_id)
7}
8
9pub(super) fn spell_token_for_id(dbc: &DbcData, spell_id: i32) -> String {
10    let name = dbc
11        .spell_name
12        .get(&spell_id)
13        .and_then(|row| row.Name_lang.as_deref())
14        .unwrap_or("");
15    let token = slugify(name);
16
17    if token.is_empty() {
18        format!("spell_{spell_id}")
19    } else {
20        token
21    }
22}
23
24pub(super) fn condition_spell_ref(
25    dbc: &DbcData,
26    overrides: &FastMap<i32, i32>,
27    spell_id: i32,
28    fallback: &str,
29) -> String {
30    if spell_id > 0 {
31        spell_token_for_id(dbc, resolve_spell_id(overrides, spell_id))
32    } else {
33        fallback.to_string()
34    }
35}
36
37#[expect(
38    clippy::needless_pass_by_value,
39    reason = "the comparison node takes ownership of both JSON subtrees"
40)]
41pub(super) fn compare(op: &str, left: Value, right: Value) -> Value {
42    json!({ "type": "compare", "op": op, "left": left, "right": right })
43}
44
45pub(super) fn int(v: i32) -> Value {
46    json!({ "type": "int", "value": v })
47}
48
49pub(super) fn float(v: f64) -> Value {
50    json!({ "type": "float", "value": v })
51}
52
53pub(super) fn read(domain: &str, name: &str) -> Value {
54    json!({ "type": "read", "domain": domain, "name": name })
55}
56
57pub(super) fn read_key(domain: &str, name: &str, key: &str) -> Value {
58    json!({ "type": "read", "domain": domain, "name": name, "key": key })
59}
60
61pub(super) fn read_aura(aura: &str, name: &str, on: Option<&str>) -> Value {
62    match on {
63        Some(target) => json!({
64            "type": "read", "domain": "aura", "name": name, "key": aura, "on": target
65        }),
66        None => read_key("aura", name, aura),
67    }
68}
69
70pub(super) fn slugify_or(input: &str, fallback: impl FnOnce() -> String) -> String {
71    let slug = slugify(input);
72
73    if slug.is_empty() { fallback() } else { slug }
74}
75
76fn slugify(input: &str) -> String {
77    let mut output = String::with_capacity(input.len());
78    let mut last_was_separator = true;
79
80    for character in input.chars() {
81        if character.is_ascii_alphanumeric() {
82            output.push(character.to_ascii_lowercase());
83            last_was_separator = false;
84        } else if character == '\'' || character == '\u{2019}' {
85        } else if !last_was_separator {
86            output.push('_');
87            last_was_separator = true;
88        }
89    }
90
91    while output.ends_with('_') {
92        output.pop();
93    }
94
95    if output.starts_with(|character: char| character.is_ascii_digit()) {
96        format!("spell_{output}")
97    } else {
98        output
99    }
100}