use boon_proto::proto::CDemoClassInfo;
#[derive(Debug, Clone, serde::Serialize)]
pub struct ClassEntry {
pub class_id: i32,
pub network_name: String,
pub table_name: String,
}
#[derive(Debug, Clone)]
pub struct ClassInfo {
pub classes: Vec<ClassEntry>,
pub bits: usize,
lookup: Vec<Option<usize>>,
}
impl ClassInfo {
pub fn empty() -> Self {
ClassInfo {
classes: Vec::new(),
bits: 1,
lookup: Vec::new(),
}
}
pub fn parse(cmd: CDemoClassInfo) -> Self {
let classes: Vec<ClassEntry> = cmd
.classes
.into_iter()
.map(|c| ClassEntry {
class_id: c.class_id.unwrap_or(0),
network_name: c.network_name.unwrap_or_default(),
table_name: c.table_name.unwrap_or_default(),
})
.collect();
let max_id = classes.iter().map(|c| c.class_id).max().unwrap_or(0) as u32;
let bits = if max_id == 0 {
1
} else {
32 - max_id.leading_zeros() as usize
};
let mut lookup = vec![None; max_id as usize + 1];
for (i, c) in classes.iter().enumerate() {
if c.class_id >= 0 {
lookup[c.class_id as usize] = Some(i);
}
}
ClassInfo {
classes,
bits,
lookup,
}
}
pub fn by_id(&self, class_id: i32) -> Option<&ClassEntry> {
let idx = *self.lookup.get(class_id as usize)?.as_ref()?;
Some(&self.classes[idx])
}
pub fn name_by_id(&self, class_id: i32) -> Option<&str> {
self.by_id(class_id).map(|c| c.network_name.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use boon_proto::proto::c_demo_class_info;
fn make_class_info(ids: &[(i32, &str)]) -> ClassInfo {
let cmd = CDemoClassInfo {
classes: ids
.iter()
.map(|(id, name)| c_demo_class_info::ClassT {
class_id: Some(*id),
network_name: Some(name.to_string()),
table_name: Some(String::new()),
})
.collect(),
};
ClassInfo::parse(cmd)
}
#[test]
fn empty_classes_bits_is_1() {
let ci = make_class_info(&[]);
assert_eq!(ci.bits, 1);
}
#[test]
fn single_class_id_0_bits_is_1() {
let ci = make_class_info(&[(0, "A")]);
assert_eq!(ci.bits, 1);
}
#[test]
fn max_id_10_bits_is_4() {
let ci = make_class_info(&[(0, "A"), (10, "B")]);
assert_eq!(ci.bits, 4);
}
#[test]
fn max_id_8_bits_is_4() {
let ci = make_class_info(&[(8, "A")]);
assert_eq!(ci.bits, 4);
}
#[test]
fn max_id_255_bits_is_8() {
let ci = make_class_info(&[(255, "A")]);
assert_eq!(ci.bits, 8);
}
#[test]
fn by_id_found() {
let ci = make_class_info(&[(5, "Hero"), (10, "Creep")]);
let entry = ci.by_id(10).unwrap();
assert_eq!(entry.network_name, "Creep");
}
#[test]
fn by_id_not_found() {
let ci = make_class_info(&[(5, "Hero")]);
assert!(ci.by_id(99).is_none());
}
#[test]
fn name_by_id_returns_correct_name() {
let ci = make_class_info(&[(1, "Player"), (2, "NPC")]);
assert_eq!(ci.name_by_id(1), Some("Player"));
assert_eq!(ci.name_by_id(99), None);
}
}