Skip to main content

forge/manifest_ledger/
shapes.rs

1//! Live summary of recurring behavior shapes in composed spec manifests.
2
3use std::collections::BTreeMap;
4
5use wowlab_manifest_schema::{Manifest, ManifestAuraDef, ManifestSpellDef};
6
7pub(super) fn spec_counts(manifest: &Manifest) -> BTreeMap<&'static str, usize> {
8    let mut counts = BTreeMap::new();
9
10    counts.insert("Effect bindings read by hooks", manifest.effects.len());
11    counts.insert(
12        "Spell hooks",
13        manifest
14            .spells
15            .values()
16            .filter(|spell| spell.hook.is_some())
17            .count(),
18    );
19    counts.insert(
20        "Aura lifecycle fields",
21        manifest.auras.values().map(aura_lifecycle_fields).sum(),
22    );
23    counts.insert(
24        "Authored spell damage programs",
25        manifest
26            .spells
27            .values()
28            .map(|spell| usize::from(spell.damage.is_some()) + spell.damage_effects.len())
29            .sum(),
30    );
31    counts.insert(
32        "Authored spell-to-aura programs",
33        manifest.spells.values().map(spell_aura_operations).sum(),
34    );
35    counts.insert(
36        "Periodic aura payloads",
37        manifest.auras.values().map(periodic_payloads).sum(),
38    );
39    counts.insert(
40        "Aura modifiers",
41        manifest.auras.values().map(aura_modifiers).sum(),
42    );
43    counts.insert(
44        "Content-effect declarations",
45        manifest.content_effects.len(),
46    );
47    counts.insert(
48        "Spell gates/overrides",
49        manifest.spells.values().map(spell_gates).sum(),
50    );
51    counts.insert(
52        "Core spell-property overrides",
53        manifest.spells.values().map(core_spell_overrides).sum(),
54    );
55    counts.insert(
56        "Aura hooks",
57        manifest
58            .auras
59            .values()
60            .map(|aura| {
61                usize::from(aura.expire_hook.is_some()) + usize::from(aura.tick_hook.is_some())
62            })
63            .sum(),
64    );
65    counts.insert(
66        "Mastery hooks",
67        1 + usize::from(manifest.mastery.crit_damage_hook.is_some()),
68    );
69    counts.insert(
70        "Resource literals/references",
71        manifest
72            .spells
73            .values()
74            .filter(|spell| spell.gain.is_some())
75            .count(),
76    );
77    counts.insert("Auto-attack definitions", manifest.auto_attacks.len());
78    counts.insert(
79        "Spec custom handlers",
80        usize::from(manifest.spec.custom_handler.is_some()),
81    );
82    counts.insert(
83        "Auto-attack hooks",
84        manifest
85            .auto_attacks
86            .values()
87            .filter(|attack| attack.hook.is_some())
88            .count(),
89    );
90    counts.insert(
91        "On-consume event operations",
92        manifest
93            .auras
94            .values()
95            .map(|aura| aura.on_consume.len())
96            .sum(),
97    );
98    counts.insert("Declarative impact procs", manifest.impact_procs.len());
99    counts.insert(
100        "Cooldown mutation declarations",
101        manifest.spells.values().map(cooldown_mutations).sum(),
102    );
103
104    counts
105}
106
107fn aura_lifecycle_fields(aura: &ManifestAuraDef) -> usize {
108    [
109        aura.duration_ms.is_some(),
110        aura.max_stacks.is_some(),
111        aura.no_pandemic,
112        aura.split_across_stacks,
113        aura.apply_at_max_stacks,
114        aura.async_stacks,
115        aura.periodic_damage_scales_with_stacks,
116        aura.reapply_on_expire,
117        aura.periodic_partial_tick,
118        aura.periodic_hasted.is_some(),
119        aura.precombat,
120        aura.snapshot,
121    ]
122    .into_iter()
123    .filter(|present| *present)
124    .count()
125}
126
127fn spell_aura_operations(spell: &ManifestSpellDef) -> usize {
128    usize::from(spell.applies_aura.is_some())
129        + spell.applies_auras.len()
130        + spell.extends_auras.len()
131}
132
133fn periodic_payloads(aura: &ManifestAuraDef) -> usize {
134    [
135        aura.periodic_remove_stack.is_some(),
136        aura.periodic_damage.is_some(),
137        aura.periodic_residual_damage.is_some(),
138        aura.periodic_resource.is_some(),
139        aura.periodic_resource_drain.is_some(),
140        aura.periodic_apply_aura.is_some(),
141        aura.periodic_hook.is_some(),
142    ]
143    .into_iter()
144    .filter(|present| *present)
145    .count()
146}
147
148fn aura_modifiers(aura: &ManifestAuraDef) -> usize {
149    [
150        aura.damage_mult.is_some(),
151        aura.damage_mult_stacking.is_some(),
152        aura.pet_damage_mult.is_some(),
153        aura.haste_per_stack.is_some(),
154        aura.attack_speed_per_stack.is_some(),
155        aura.auto_attack_damage_per_stack.is_some(),
156        aura.resource_gain_mult_per_stack.is_some(),
157        aura.regen_percent_per_stack.is_some(),
158        aura.max_resource_per_stack.is_some(),
159        aura.crit_per_stack.is_some(),
160        aura.mastery_per_stack.is_some(),
161        aura.primary_stat_percent_per_stack.is_some(),
162        aura.attack_power_percent_per_stack.is_some(),
163        aura.crit_rating_per_stack.is_some(),
164        aura.haste_rating_per_stack.is_some(),
165        aura.mastery_rating_per_stack.is_some(),
166        aura.versatility_rating_per_stack.is_some(),
167        aura.primary_stat_per_stack.is_some(),
168        aura.crit_chance_spells.is_some(),
169        aura.crit_damage_spells.is_some(),
170        aura.damage_percent_spells.is_some(),
171        aura.damage_percent_spells_linear.is_some(),
172        aura.cast_time_percent.is_some(),
173        aura.cast_time_percent_per_stack.is_some(),
174        aura.gcd_percent.is_some(),
175        aura.spell_cost_flat.is_some(),
176        aura.spell_cost_percent.is_some(),
177        aura.secondary_spell_cost_percent.is_some(),
178        aura.secondary_spell_cost_flat.is_some(),
179    ]
180    .into_iter()
181    .filter(|present| *present)
182    .count()
183        + aura.recharge_rate_percent.len()
184}
185
186fn spell_gates(spell: &ManifestSpellDef) -> usize {
187    [
188        spell.requires_aura_id.is_some(),
189        spell.requires_aura_min_stacks.is_some(),
190        spell.cost_free_when_aura.is_some(),
191        spell.consume_aura_on_cast.is_some(),
192        spell.cooldown_bypass_when_aura.is_some(),
193        spell.execute_below_pct.is_some(),
194        spell.instant_when_aura.is_some(),
195        spell.override_when_aura.is_some(),
196        spell.override_with_spell.is_some(),
197        spell.override_aura_min_stacks.is_some(),
198        spell.override_shares_base_cooldown,
199        spell.override_preserves_aura,
200    ]
201    .into_iter()
202    .filter(|present| *present)
203    .count()
204}
205
206fn core_spell_overrides(spell: &ManifestSpellDef) -> usize {
207    [
208        spell.cooldown.is_some(),
209        spell.cost.is_some(),
210        spell.travel_time_ms.is_some(),
211        spell.cast_time_ms.is_some(),
212        spell.gcd_ms.is_some(),
213        spell.charges.is_some(),
214        spell.charge_cd.is_some(),
215        spell.cooldown_hasted.is_some(),
216        spell.usable_while_casting,
217        !spell.breaks_stealth,
218        spell.guaranteed_crit,
219        !spell.infer_damage,
220        !spell.infer_applies_aura,
221    ]
222    .into_iter()
223    .filter(|present| *present)
224    .count()
225}
226
227fn cooldown_mutations(spell: &ManifestSpellDef) -> usize {
228    spell.reduces_cd.as_ref().map_or(0, Vec::len)
229        + usize::from(spell.reduces_cd_chance.is_some())
230        + usize::from(spell.resets_cd_while.is_some())
231}