Skip to main content

wowlab_cli/commands/snapshot/assisted_rotas/
conditions.rs

1use serde_json::{Value, json};
2use wowlab_parsers::DbcData;
3use wowlab_types::{
4    constants::{HUNDRED, MS_PER_SECOND},
5    data::{AssistedActionFlat, AssistedConditionFlat, ConditionType},
6    sim::FastMap,
7};
8
9use super::dsl::{
10    compare, condition_spell_ref, float, int, read, read_aura, read_key, resolve_spell_id,
11    spell_token_for_id,
12};
13
14pub(super) fn build_action_condition(
15    dbc: &DbcData,
16    overrides: &FastMap<i32, i32>,
17    action: &AssistedActionFlat,
18    cast_spell: &str,
19) -> Option<Value> {
20    let mut expressions = Vec::with_capacity(action.conditions.len());
21
22    for condition in &action.conditions {
23        let Some(condition_type) = condition.condition_type else {
24            tracing::warn!(
25                condition_type = condition.r#type,
26                spell = cast_spell,
27                "Unknown assisted condition type"
28            );
29            continue;
30        };
31
32        expressions.push(map_condition(
33            dbc,
34            overrides,
35            condition_type,
36            condition,
37            cast_spell,
38        ));
39    }
40
41    match expressions.len() {
42        0 => None,
43        1 => expressions.pop(),
44        _ => Some(json!({ "type": "and", "operands": expressions })),
45    }
46}
47
48fn aura(dbc: &DbcData, overrides: &FastMap<i32, i32>, spell_id: i32) -> String {
49    spell_token_for_id(dbc, resolve_spell_id(overrides, spell_id))
50}
51
52fn is_talent_spell(dbc: &DbcData, spell_id: i32) -> bool {
53    let is_action = dbc
54        .assisted_combat_step
55        .values()
56        .flatten()
57        .any(|step| step.SpellID == spell_id);
58
59    is_talent_spell_id(
60        dbc.trait_definition
61            .values()
62            .map(|definition| (definition.SpellID, definition.VisibleSpellID)),
63        spell_id,
64        is_action,
65    )
66}
67
68pub(super) fn is_talent_spell_id(
69    mut definitions: impl Iterator<Item = (i32, i32)>,
70    spell_id: i32,
71    is_action_spell: bool,
72) -> bool {
73    !is_action_spell && definitions.any(|(spell, visible)| spell == spell_id || visible == spell_id)
74}
75
76pub(super) fn player_requirement(token: &str, is_talent: bool, active: bool) -> Value {
77    if !is_talent {
78        return read_aura(
79            token,
80            if active { "is_active" } else { "is_inactive" },
81            None,
82        );
83    }
84
85    let selected = read_key("talent", "is_enabled", token);
86
87    if active {
88        selected
89    } else {
90        json!({ "type": "not", "operand": selected })
91    }
92}
93
94fn player_aura_or_talent(
95    dbc: &DbcData,
96    overrides: &FastMap<i32, i32>,
97    spell_id: i32,
98    active: bool,
99) -> Value {
100    let resolved = resolve_spell_id(overrides, spell_id);
101
102    player_requirement(
103        &spell_token_for_id(dbc, resolved),
104        is_talent_spell(dbc, resolved),
105        active,
106    )
107}
108
109fn resource(resource: &str, property: &str, op: &str, value: Value) -> Value {
110    compare(op, read_key("resource", property, resource), value)
111}
112
113#[rustfmt::skip]
114fn map_condition(
115    dbc: &DbcData,
116    overrides: &FastMap<i32, i32>,
117    condition_type: ConditionType,
118    condition: &AssistedConditionFlat,
119    cast_spell: &str,
120) -> Value {
121    use ConditionType::{SpellLearned, HostileTarget, CooldownAllowCastingSuccess, FriendlyTarget, SpellOffCooldown, SpellOnCooldown, CooldownRemainingGreater, CooldownRemainingLess, SpellChargesGreater, SpellChargesLess, AffordCost, SpellInRange, AuraOnPlayer, AuraOnTarget, AuraMissingPlayer, AuraMissingTarget, AuraDurationPlayer, AuraDurationTarget, PlayerAuraApplicationGreater, PlayerAuraApplicationLess, TargetAuraApplicationGreater, TargetAuraApplicationLess, ManaGreater, ManaLess, RageGreater, RageLess, FocusGreater, FocusLess, EnergyGreater, EnergyLess, ComboPointsGreater, ComboPointsLess, RunesGreater, RunesLess, RunicPowerGreater, RunicPowerLess, SoulShardsGreater, SoulShardsLess, LunarPowerGreater, LunarPowerLess, HolyPowerGreater, HolyPowerLess, MaelstromGreater, MaelstromLess, ChiGreater, ChiLess, InsanityGreater, InsanityLess, EssenceGreater, EssenceLess, ArcaneChargesGreater, ArcaneChargesLess, FuryGreater, PainGreater, FuryLess, PainLess, HealthPctGreater, HealthPctLess, PlayerHealthPctGreater, PlayerHealthPctLess, TargetDistanceLess, TargetDistanceGreater, TargetCountNearTargetGreater, TargetCountNearPlayerGreater, AuraCountNearPlayerGreater, TargetCountNearTargetLess, TargetCountNearPlayerLess, AuraCountNearPlayerLess, HasPet, HasNoPet, AutomationOnly};
122    let v1 = condition.v1;
123    let v2 = condition.v2;
124    let cd = |spell_id| condition_spell_ref(dbc, overrides, spell_id, cast_spell);
125    let aura = |spell_id| aura(dbc, overrides, spell_id);
126    let tenths = |value| float(f64::from(value) / 10.0);
127    let hundredths = |value| float(f64::from(value) / HUNDRED);
128    let seconds = |value| float(f64::from(value) / MS_PER_SECOND);
129    let current = |name, op, value| resource(name, "current", op, value);
130    let percent = |name, op, value| resource(name, "pct", op, value);
131
132    match condition_type {
133        SpellLearned | HostileTarget | CooldownAllowCastingSuccess | AutomationOnly => json!({"type":"bool","value":true}),
134        FriendlyTarget => json!({"type":"bool","value":false}),
135        SpellOffCooldown => read_key("cooldown", "is_ready", &cd(v1)),
136        SpellOnCooldown => json!({"type":"not","operand":read_key("cooldown", "is_ready", &cd(v1))}),
137        CooldownRemainingGreater => compare("gte", read_key("cooldown", "remaining", &cd(v1)), seconds(v2)),
138        CooldownRemainingLess => compare("lte", read_key("cooldown", "remaining", &cd(v1)), seconds(v2)),
139        SpellChargesGreater => compare("gte", read_key("cooldown", "charges", cast_spell), int(v1)),
140        SpellChargesLess => compare("lte", read_key("cooldown", "charges", cast_spell), int(v1)),
141        AffordCost => read_key("spell", "is_usable", &cd(v1)),
142        SpellInRange => read_key("spell", "is_in_range", &cd(v1)),
143        AuraOnPlayer => player_aura_or_talent(dbc, overrides, v1, true),
144        AuraOnTarget => read_aura(&aura(v1), "is_active", Some("target")),
145        AuraMissingPlayer => player_aura_or_talent(dbc, overrides, v1, false),
146        AuraMissingTarget => read_aura(&aura(v1), "is_inactive", Some("target")),
147        AuraDurationPlayer => json!({"type":"and","operands":[
148            read_aura(&aura(v1), "is_active", None),
149            compare("lte", read_aura(&aura(v1), "remaining", None), seconds(v2)),
150        ]}),
151        AuraDurationTarget => compare("lte", read_aura(&aura(v1), "remaining", Some("target")), seconds(v2)),
152        PlayerAuraApplicationGreater => compare("gte", read_aura(&aura(v1), "stacks", None), int(v2)),
153        PlayerAuraApplicationLess => compare("lte", read_aura(&aura(v1), "stacks", None), int(v2)),
154        TargetAuraApplicationGreater => compare("gte", read_aura(&aura(v1), "stacks", Some("target")), int(v2)),
155        TargetAuraApplicationLess => compare("lte", read_aura(&aura(v1), "stacks", Some("target")), int(v2)),
156        ManaGreater => percent("Mana", "gte", tenths(v1)),
157        ManaLess => percent("Mana", "lte", tenths(v1)),
158        RageGreater => current("Rage", "gte", tenths(v1)),
159        RageLess => current("Rage", "lte", tenths(v1)),
160        FocusGreater => current("Focus", "gte", int(v1)),
161        FocusLess => current("Focus", "lte", int(v1)),
162        EnergyGreater => current("Energy", "gte", int(v1)),
163        EnergyLess => current("Energy", "lte", int(v1)),
164        ComboPointsGreater => current("ComboPoints", "gte", int(v1)),
165        ComboPointsLess => current("ComboPoints", "lte", int(v1)),
166        RunesGreater => current("Runes", "gte", int(v1)),
167        RunesLess => current("Runes", "lte", int(v1)),
168        RunicPowerGreater => current("RunicPower", "gte", tenths(v1)),
169        RunicPowerLess => current("RunicPower", "lte", tenths(v1)),
170        SoulShardsGreater => current("SoulShards", "gte", tenths(v1)),
171        SoulShardsLess => current("SoulShards", "lte", tenths(v1)),
172        LunarPowerGreater => current("AstralPower", "gte", tenths(v1)),
173        LunarPowerLess => current("AstralPower", "lte", tenths(v1)),
174        HolyPowerGreater => current("HolyPower", "gte", int(v1)),
175        HolyPowerLess => current("HolyPower", "lte", int(v1)),
176        MaelstromGreater => current("Maelstrom", "gte", int(v1)),
177        MaelstromLess => current("Maelstrom", "lte", int(v1)),
178        ChiGreater => current("Chi", "gte", int(v1)),
179        ChiLess => current("Chi", "lte", int(v1)),
180        InsanityGreater => current("Insanity", "gte", hundredths(v1)),
181        InsanityLess => current("Insanity", "lte", hundredths(v1)),
182        EssenceGreater => current("Essence", "gte", int(v1)),
183        EssenceLess => current("Essence", "lte", int(v1)),
184        ArcaneChargesGreater => current("ArcaneCharges", "gte", int(v1)),
185        ArcaneChargesLess => current("ArcaneCharges", "lte", int(v1)),
186        FuryGreater | PainGreater => current("Fury", "gte", int(v1)),
187        FuryLess | PainLess => current("Fury", "lte", int(v1)),
188        HealthPctGreater => compare("gte", read_key("unit", "health_pct", "target"), int(v1)),
189        HealthPctLess => compare("lte", read_key("unit", "health_pct", "target"), int(v1)),
190        PlayerHealthPctGreater => compare("gte", read_key("unit", "health_pct", "player"), int(v1)),
191        PlayerHealthPctLess => compare("lte", read_key("unit", "health_pct", "player"), int(v1)),
192        TargetDistanceLess => compare("lte", read_key("unit", "distance", "target"), int(v1)),
193        TargetDistanceGreater => compare("gte", read_key("unit", "distance", "target"), int(v1)),
194        TargetCountNearTargetGreater | TargetCountNearPlayerGreater | AuraCountNearPlayerGreater => compare("gte", read("combat", "enemy_count"), int(v1)),
195        TargetCountNearTargetLess | TargetCountNearPlayerLess | AuraCountNearPlayerLess => compare("lte", read("combat", "enemy_count"), int(v1)),
196        HasPet => read("pet", "is_active"),
197        HasNoPet => json!({"type":"not","operand":read("pet", "is_active")}),
198    }
199}