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}