forge/
talent_target_flag_summary.rs1use std::collections::{BTreeMap, BTreeSet};
6
7#[cfg(test)]
8use googletest::{Result as GtestResult, prelude::*};
9use wowlab_common::output;
10use wowlab_engine_domain::dbc::{
11 SemanticSupport, SpellCastTargetFlags, spell_cast_target_flag_semantic, spell_target_masks,
12};
13use wowlab_types::{data::SpellDataFlat, game::SpecId};
14
15#[derive(Default)]
16struct TargetFlagSummary {
17 observations: usize,
18 specs: BTreeSet<String>,
19}
20
21#[derive(Default)]
22pub(super) struct TargetFlagSummaries(BTreeMap<u8, TargetFlagSummary>);
23
24#[derive(tabled::Tabled)]
25struct TargetFlagSummaryRow {
26 #[tabled(rename = "Bit")]
27 bit: u8,
28 #[tabled(rename = "Name")]
29 name: &'static str,
30 #[tabled(rename = "Coverage")]
31 coverage: &'static str,
32 #[tabled(rename = "Observations")]
33 observations: usize,
34 #[tabled(rename = "Specs")]
35 specs: usize,
36 #[tabled(rename = "Handler / gap")]
37 handler: &'static str,
38}
39
40impl TargetFlagSummaries {
41 pub(super) fn observe(&mut self, spec: SpecId, spell: &SpellDataFlat, filter: &[u8]) {
42 let explicit = spell_target_masks(spell).explicit;
43
44 for bit in filter {
45 let flag = SpellCastTargetFlags::from_bits_retain(1_u32 << bit);
46
47 if explicit.contains(flag) {
48 let summary = self.0.entry(*bit).or_default();
49
50 summary.observations += 1;
51 summary.specs.insert(spec.slug().to_string());
52 }
53 }
54 }
55
56 pub(super) fn print(&self, filter: &[u8]) {
57 output::blank();
58 output::header("Cross-spec cast-target flag summary");
59 output::table(filter.iter().map(|bit| self.row(*bit)));
60 }
61
62 fn row(&self, bit: u8) -> TargetFlagSummaryRow {
63 let flag = SpellCastTargetFlags::from_bits_retain(1_u32 << bit);
64 let semantic = spell_cast_target_flag_semantic(flag);
65 let (name, coverage, handler) = semantic.map_or(
66 (
67 "Unregistered",
68 "unsupported",
69 "cast-target bit is absent from the canonical registry",
70 ),
71 |semantic| {
72 let coverage = match semantic.support {
73 SemanticSupport::Generic => "generic",
74 SemanticSupport::Partial | SemanticSupport::ContentRequired => "partial",
75 SemanticSupport::Ignored => "ignored",
76 _ => "unsupported",
77 };
78
79 (semantic.name, coverage, semantic.handler)
80 },
81 );
82 let summary = self.0.get(&bit);
83
84 TargetFlagSummaryRow {
85 bit,
86 name,
87 coverage,
88 observations: summary.map_or(0, |summary| summary.observations),
89 specs: summary.map_or(0, |summary| summary.specs.len()),
90 handler,
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[gtest]
100 fn counts_only_filtered_explicit_payload_bits() -> GtestResult<()> {
101 let mut summaries = TargetFlagSummaries::default();
102 let spell = SpellDataFlat {
103 target_flags: (1 << 5) | (1 << 7),
104 ..SpellDataFlat::default()
105 };
106
107 summaries.observe(SpecId::Fire, &spell, &[5, 6]);
108
109 let summary = &summaries.0[&5];
110
111 verify_that!(summary.observations, eq(1))?;
112 verify_that!(summary.specs.len(), eq(1))?;
113 verify_true!(!summaries.0.contains_key(&6))?;
114
115 Ok(())
116 }
117}