boon/entity/
class_info.rs1use boon_proto::proto::CDemoClassInfo;
2
3#[derive(Debug, Clone, serde::Serialize)]
5pub struct ClassEntry {
6 pub class_id: i32,
7 pub network_name: String,
8 pub table_name: String,
9}
10
11#[derive(Debug, Clone)]
13pub struct ClassInfo {
14 pub classes: Vec<ClassEntry>,
15 pub bits: usize,
17 lookup: Vec<Option<usize>>,
19}
20
21impl ClassInfo {
22 pub fn empty() -> Self {
24 ClassInfo {
25 classes: Vec::new(),
26 bits: 1,
27 lookup: Vec::new(),
28 }
29 }
30
31 pub fn parse(cmd: CDemoClassInfo) -> Self {
33 let classes: Vec<ClassEntry> = cmd
34 .classes
35 .into_iter()
36 .map(|c| ClassEntry {
37 class_id: c.class_id.unwrap_or(0),
38 network_name: c.network_name.unwrap_or_default(),
39 table_name: c.table_name.unwrap_or_default(),
40 })
41 .collect();
42
43 let max_id = classes.iter().map(|c| c.class_id).max().unwrap_or(0) as u32;
47 let bits = if max_id == 0 {
48 1
49 } else {
50 32 - max_id.leading_zeros() as usize
51 };
52
53 let mut lookup = vec![None; max_id as usize + 1];
55 for (i, c) in classes.iter().enumerate() {
56 if c.class_id >= 0 {
57 lookup[c.class_id as usize] = Some(i);
58 }
59 }
60
61 ClassInfo {
62 classes,
63 bits,
64 lookup,
65 }
66 }
67
68 pub fn by_id(&self, class_id: i32) -> Option<&ClassEntry> {
70 let idx = *self.lookup.get(class_id as usize)?.as_ref()?;
71 Some(&self.classes[idx])
72 }
73
74 pub fn name_by_id(&self, class_id: i32) -> Option<&str> {
76 self.by_id(class_id).map(|c| c.network_name.as_str())
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use boon_proto::proto::c_demo_class_info;
84
85 fn make_class_info(ids: &[(i32, &str)]) -> ClassInfo {
86 let cmd = CDemoClassInfo {
87 classes: ids
88 .iter()
89 .map(|(id, name)| c_demo_class_info::ClassT {
90 class_id: Some(*id),
91 network_name: Some(name.to_string()),
92 table_name: Some(String::new()),
93 })
94 .collect(),
95 };
96 ClassInfo::parse(cmd)
97 }
98
99 #[test]
100 fn empty_classes_bits_is_1() {
101 let ci = make_class_info(&[]);
102 assert_eq!(ci.bits, 1);
103 }
104
105 #[test]
106 fn single_class_id_0_bits_is_1() {
107 let ci = make_class_info(&[(0, "A")]);
108 assert_eq!(ci.bits, 1);
109 }
110
111 #[test]
112 fn max_id_10_bits_is_4() {
113 let ci = make_class_info(&[(0, "A"), (10, "B")]);
114 assert_eq!(ci.bits, 4);
115 }
116
117 #[test]
118 fn max_id_8_bits_is_4() {
119 let ci = make_class_info(&[(8, "A")]);
120 assert_eq!(ci.bits, 4);
121 }
122
123 #[test]
124 fn max_id_255_bits_is_8() {
125 let ci = make_class_info(&[(255, "A")]);
126 assert_eq!(ci.bits, 8);
127 }
128
129 #[test]
130 fn by_id_found() {
131 let ci = make_class_info(&[(5, "Hero"), (10, "Creep")]);
132 let entry = ci.by_id(10).unwrap();
133 assert_eq!(entry.network_name, "Creep");
134 }
135
136 #[test]
137 fn by_id_not_found() {
138 let ci = make_class_info(&[(5, "Hero")]);
139 assert!(ci.by_id(99).is_none());
140 }
141
142 #[test]
143 fn name_by_id_returns_correct_name() {
144 let ci = make_class_info(&[(1, "Player"), (2, "NPC")]);
145 assert_eq!(ci.name_by_id(1), Some("Player"));
146 assert_eq!(ci.name_by_id(99), None);
147 }
148}