1use std::collections::HashSet;
2
3use rustc_hash::FxHashMap;
4
5use crate::error::{Error, Result};
6use crate::io::BitReader;
7
8use super::class_info::ClassInfo;
9use super::field_decoder::FieldDecodeContext;
10use super::field_path::{self, FieldPath};
11use super::field_value::FieldValue;
12use super::serializers::{Serializer, SerializerContainer, SerializerField};
13use super::string_tables::StringTableContainer;
14
15use boon_proto::proto::CsvcMsgPacketEntities;
16
17const MAX_EDICT_BITS: u32 = 14;
23const NUM_ENT_ENTRY_BITS: u32 = MAX_EDICT_BITS + 1;
24const NUM_SERIAL_NUM_BITS: u32 = 32 - NUM_ENT_ENTRY_BITS;
25
26const MAX_ENTITY_INDEX: i64 = (1i64 << MAX_EDICT_BITS) - 1;
30
31pub const ENTITY_HANDLE_INDEX_MASK: u32 = 0x3FFF;
38
39pub const INVALID_ENTITY_HANDLE: u32 = 0x00FF_FFFF;
44
45pub fn protobuf_handle_index(handle: Option<u32>) -> Option<i32> {
57 handle
58 .filter(|&h| h != INVALID_ENTITY_HANDLE)
59 .map(|h| (h & ENTITY_HANDLE_INDEX_MASK) as i32)
60}
61
62const DELTA_UPDATE: u8 = 0b00;
64const DELTA_CREATE: u8 = 0b10;
65const DELTA_LEAVE: u8 = 0b01;
66const DELTA_DELETE: u8 = 0b11;
67
68fn resolve_field_path<'a>(
75 serializer: &'a Serializer,
76 fp: &FieldPath,
77) -> Result<&'a SerializerField> {
78 let oob = || Error::Parse {
79 context: format!(
80 "field path out of range for serializer '{}' ({} fields)",
81 serializer.name,
82 serializer.fields.len()
83 ),
84 };
85
86 let mut field = serializer.fields.get(fp.get(0)).ok_or_else(oob)?;
87 for i in 1..=fp.last {
88 let idx = fp.get(i);
89 if field.is_dynamic_array() {
90 match field.field_serializer.as_ref() {
91 Some(fs) => field = fs.fields.first().ok_or_else(oob)?,
92 None => break,
93 }
94 } else if let Some(fs) = field.field_serializer.as_ref() {
95 field = fs.fields.get(idx).ok_or_else(oob)?;
96 } else {
97 break;
98 }
99 }
100 Ok(field)
101}
102
103#[derive(Debug, Clone)]
105pub struct Entity {
106 pub index: i32,
108 pub serial: u32,
110 pub class_id: i32,
112 pub class_name: String,
114 pub fields: FxHashMap<u64, FieldValue>,
116}
117
118impl Entity {
119 fn new(index: i32, class_id: i32, class_name: String) -> Self {
120 Self {
121 index,
122 serial: 0,
123 class_id,
124 class_name,
125 fields: FxHashMap::default(),
126 }
127 }
128
129 #[allow(clippy::needless_range_loop)]
131 fn apply_update(
132 &mut self,
133 br: &mut BitReader,
134 serializer: &Serializer,
135 ctx: &mut FieldDecodeContext,
136 fp_buf: &mut Vec<FieldPath>,
137 ) -> Result<()> {
138 field_path::read_field_paths(br, fp_buf)?;
139
140 for fp_idx in 0..fp_buf.len() {
141 let field = resolve_field_path(serializer, &fp_buf[fp_idx])?;
142
143 let key = fp_buf[fp_idx].pack();
144 let value = field
145 .metadata
146 .decoder
147 .decode(ctx, br)
148 .map_err(|e| Error::Parse {
149 context: format!(
150 "field #{} key={:#x} (type: {}, decoder: {:?}, pos: {}, remaining: {}): {}",
151 fp_idx,
152 key,
153 field.var_type,
154 field.metadata.decoder,
155 br.position(),
156 br.bits_remaining(),
157 e
158 ),
159 })?;
160 self.fields.insert(key, value);
161 }
162
163 Ok(())
164 }
165
166 #[allow(clippy::needless_range_loop)]
169 fn skip_update(
170 br: &mut BitReader,
171 serializer: &Serializer,
172 ctx: &mut FieldDecodeContext,
173 fp_buf: &mut Vec<FieldPath>,
174 ) -> Result<()> {
175 field_path::read_field_paths(br, fp_buf)?;
176
177 for fp_idx in 0..fp_buf.len() {
178 let field = resolve_field_path(serializer, &fp_buf[fp_idx])?;
179
180 field.metadata.decoder.skip(ctx, br)?;
182 }
183
184 Ok(())
185 }
186
187 pub fn get_by_name(&self, path: &str, serializer: &Serializer) -> Option<&FieldValue> {
189 let key = serializer.resolve_field_key(path)?;
190 self.fields.get(&key)
191 }
192
193 pub fn get_i64(&self, key: Option<u64>) -> i64 {
204 key.and_then(|k| self.fields.get(&k))
205 .and_then(|v| match v {
206 FieldValue::U32(n) => Some(*n as i64),
207 FieldValue::U64(n) => Some(*n as i64),
208 FieldValue::I32(n) => Some(*n as i64),
209 FieldValue::I64(n) => Some(*n),
210 _ => None,
211 })
212 .unwrap_or(0)
213 }
214
215 pub fn get_u32(&self, key: Option<u64>) -> u32 {
217 key.and_then(|k| self.fields.get(&k))
218 .and_then(|v| match v {
219 FieldValue::U32(n) => Some(*n),
220 FieldValue::U64(n) => Some(*n as u32),
221 FieldValue::I32(n) => Some(*n as u32),
222 FieldValue::I64(n) => Some(*n as u32),
223 _ => None,
224 })
225 .unwrap_or(0)
226 }
227
228 pub fn get_f32(&self, key: Option<u64>) -> f32 {
230 key.and_then(|k| self.fields.get(&k))
231 .and_then(|v| match v {
232 FieldValue::F32(f) => Some(*f),
233 _ => None,
234 })
235 .unwrap_or(0.0)
236 }
237
238 pub fn get_bool(&self, key: Option<u64>) -> bool {
240 key.and_then(|k| self.fields.get(&k))
241 .and_then(|v| match v {
242 FieldValue::Bool(b) => Some(*b),
243 _ => None,
244 })
245 .unwrap_or(false)
246 }
247
248 pub fn get_qangle(&self, key: Option<u64>) -> [f32; 3] {
250 key.and_then(|k| self.fields.get(&k))
251 .and_then(|v| match v {
252 FieldValue::QAngle(a) => Some(*a),
253 _ => None,
254 })
255 .unwrap_or([0.0; 3])
256 }
257
258 pub fn world_position(
272 &self,
273 cell_keys: [Option<u64>; 3],
274 offset_keys: [Option<u64>; 3],
275 ) -> [f32; 3] {
276 let cell = [
277 self.get_i64(cell_keys[0]) as i32,
278 self.get_i64(cell_keys[1]) as i32,
279 self.get_i64(cell_keys[2]) as i32,
280 ];
281 let offset = [
282 self.get_f32(offset_keys[0]),
283 self.get_f32(offset_keys[1]),
284 self.get_f32(offset_keys[2]),
285 ];
286 [
287 crate::position::cell_to_world(cell[0], offset[0]),
288 crate::position::cell_to_world(cell[1], offset[1]),
289 crate::position::cell_to_world(cell[2], offset[2]),
290 ]
291 }
292
293 pub fn get_handle(&self, key: Option<u64>) -> Option<u32> {
299 key.and_then(|k| self.fields.get(&k)).and_then(|v| match v {
300 FieldValue::U32(n) => Some(*n),
301 FieldValue::U64(n) => Some(*n as u32),
302 FieldValue::I32(n) => Some(*n as u32),
303 FieldValue::I64(n) => Some(*n as u32),
304 _ => None,
305 })
306 }
307}
308
309#[derive(Default)]
311pub struct EntityContainer {
312 pub entities: Vec<Option<Entity>>,
318 skipped_entity_classes: Vec<i32>,
322 updated: Vec<i32>,
328}
329
330impl EntityContainer {
331 pub fn new() -> Self {
332 Self::default()
333 }
334
335 #[inline]
337 fn entity_mut(&mut self, index: i32) -> Option<&mut Entity> {
338 usize::try_from(index)
339 .ok()
340 .and_then(|i| self.entities.get_mut(i))
341 .and_then(Option::as_mut)
342 }
343
344 #[inline]
346 fn put_entity(&mut self, index: i32, entity: Entity) {
347 let Ok(i) = usize::try_from(index) else {
348 return;
349 };
350 if i >= self.entities.len() {
351 self.entities.resize_with(i + 1, || None);
352 }
353 self.entities[i] = Some(entity);
354 }
355
356 #[inline]
358 fn take_entity(&mut self, index: i32) {
359 if let Ok(i) = usize::try_from(index)
360 && let Some(slot) = self.entities.get_mut(i)
361 {
362 *slot = None;
363 }
364 }
365
366 #[inline]
368 fn skipped_class(&self, index: i32) -> Option<i32> {
369 usize::try_from(index)
370 .ok()
371 .and_then(|i| self.skipped_entity_classes.get(i))
372 .copied()
373 .filter(|&c| c >= 0)
374 }
375
376 #[inline]
378 fn set_skipped(&mut self, index: i32, class_id: Option<i32>) {
379 let Ok(i) = usize::try_from(index) else {
380 return;
381 };
382 let value = class_id.unwrap_or(-1);
383 if i >= self.skipped_entity_classes.len() {
384 if value < 0 {
385 return; }
387 self.skipped_entity_classes.resize(i + 1, -1);
388 }
389 self.skipped_entity_classes[i] = value;
390 }
391
392 pub fn handle_packet_entities(
394 &mut self,
395 msg: CsvcMsgPacketEntities,
396 class_info: &ClassInfo,
397 serializers: &SerializerContainer,
398 string_tables: &StringTableContainer,
399 field_decode_ctx: &mut FieldDecodeContext,
400 fp_buf: &mut Vec<FieldPath>,
401 ) -> Result<()> {
402 let has_pvs_vis_bits = msg.has_pvs_vis_bits_deprecated.unwrap_or(0);
403 let entity_data = msg.entity_data.unwrap_or_default();
404 let mut br = BitReader::new(&entity_data);
405
406 let mut entity_index: i32 = -1;
407
408 for _ in 0..msg.updated_entries.unwrap_or(0) {
409 let next = entity_index as i64 + br.read_ubitvar()? as i64 + 1;
410 if !(0..=MAX_ENTITY_INDEX).contains(&next) {
411 return Err(Error::Parse {
412 context: format!("entity index {next} out of range"),
413 });
414 }
415 entity_index = next as i32;
416
417 let dh = br.read_bits(2)? as u8;
419
420 match dh {
421 DELTA_CREATE => {
422 self.handle_create(
423 entity_index,
424 &mut br,
425 class_info,
426 serializers,
427 string_tables,
428 field_decode_ctx,
429 fp_buf,
430 )
431 .map_err(|e| Error::Parse {
432 context: format!("entity create #{}: {}", entity_index, e),
433 })?;
434 self.updated.push(entity_index);
435 }
436 DELTA_UPDATE => {
437 if has_pvs_vis_bits > 0 && (br.read_bits(2)? & 0x01) != 0 {
441 continue;
442 }
443 self.handle_update(
444 entity_index,
445 &mut br,
446 class_info,
447 serializers,
448 field_decode_ctx,
449 fp_buf,
450 )
451 .map_err(|e| Error::Parse {
452 context: format!(
453 "entity update #{} (class: {:?}): {}",
454 entity_index,
455 self.get(entity_index).map(|e| &e.class_name),
456 e
457 ),
458 })?;
459 self.updated.push(entity_index);
460 }
461 DELTA_DELETE | DELTA_LEAVE => {
462 self.take_entity(entity_index);
463 }
464 _ => {}
465 }
466 }
467
468 Ok(())
469 }
470
471 #[allow(clippy::too_many_arguments)]
474 pub fn handle_packet_entities_filtered(
475 &mut self,
476 msg: CsvcMsgPacketEntities,
477 class_info: &ClassInfo,
478 serializers: &SerializerContainer,
479 string_tables: &StringTableContainer,
480 field_decode_ctx: &mut FieldDecodeContext,
481 class_filter: &HashSet<&str>,
482 fp_buf: &mut Vec<FieldPath>,
483 ) -> Result<()> {
484 let has_pvs_vis_bits = msg.has_pvs_vis_bits_deprecated.unwrap_or(0);
485 let entity_data = msg.entity_data.unwrap_or_default();
486 let mut br = BitReader::new(&entity_data);
487
488 let mut entity_index: i32 = -1;
489
490 for _ in 0..msg.updated_entries.unwrap_or(0) {
491 let next = entity_index as i64 + br.read_ubitvar()? as i64 + 1;
492 if !(0..=MAX_ENTITY_INDEX).contains(&next) {
493 return Err(Error::Parse {
494 context: format!("entity index {next} out of range"),
495 });
496 }
497 entity_index = next as i32;
498
499 let dh = br.read_bits(2)? as u8;
501
502 match dh {
503 DELTA_CREATE => {
504 if self.handle_create_filtered(
505 entity_index,
506 &mut br,
507 class_info,
508 serializers,
509 string_tables,
510 field_decode_ctx,
511 class_filter,
512 fp_buf,
513 )? {
514 self.updated.push(entity_index);
515 }
516 }
517 DELTA_UPDATE => {
518 if has_pvs_vis_bits > 0 && (br.read_bits(2)? & 0x01) != 0 {
520 continue;
521 }
522 if self.handle_update_filtered(
523 entity_index,
524 &mut br,
525 class_info,
526 serializers,
527 field_decode_ctx,
528 class_filter,
529 fp_buf,
530 )? {
531 self.updated.push(entity_index);
532 }
533 }
534 DELTA_DELETE | DELTA_LEAVE => {
535 self.take_entity(entity_index);
536 self.set_skipped(entity_index, None);
537 }
538 _ => {}
539 }
540 }
541
542 Ok(())
543 }
544
545 #[allow(clippy::too_many_arguments)]
546 fn handle_create(
547 &mut self,
548 index: i32,
549 br: &mut BitReader,
550 class_info: &ClassInfo,
551 serializers: &SerializerContainer,
552 string_tables: &StringTableContainer,
553 field_decode_ctx: &mut FieldDecodeContext,
554 fp_buf: &mut Vec<FieldPath>,
555 ) -> Result<()> {
556 let class_id = br.read_bits(class_info.bits)? as i32;
557 let _serial = br.read_bits(NUM_SERIAL_NUM_BITS as usize)?;
558 let _unknown = br.read_uvarint32()?;
559
560 let class_entry = class_info.by_id(class_id).ok_or_else(|| Error::Parse {
561 context: format!("unknown class_id {}", class_id),
562 })?;
563
564 let serializer =
565 serializers
566 .get(&class_entry.network_name)
567 .ok_or_else(|| Error::Parse {
568 context: format!("no serializer for {}", class_entry.network_name),
569 })?;
570
571 let mut entity = Entity::new(index, class_id, class_entry.network_name.clone());
572 entity.fields.reserve(serializer.fields.len());
576
577 if let Some(baseline_data) = string_tables.instance_baselines.get(&class_id) {
579 let mut baseline_br = BitReader::new(baseline_data);
580 entity
581 .apply_update(&mut baseline_br, serializer, field_decode_ctx, fp_buf)
582 .map_err(|err| Error::Parse {
583 context: format!(
584 "baseline for {} (class_id {}): {}",
585 class_entry.network_name, class_id, err
586 ),
587 })?;
588 }
589
590 entity
592 .apply_update(br, serializer, field_decode_ctx, fp_buf)
593 .map_err(|err| Error::Parse {
594 context: format!(
595 "create delta for {} (class_id {}): {}",
596 class_entry.network_name, class_id, err
597 ),
598 })?;
599 self.put_entity(index, entity);
600
601 Ok(())
602 }
603
604 fn handle_update(
605 &mut self,
606 index: i32,
607 br: &mut BitReader,
608 _class_info: &ClassInfo,
609 serializers: &SerializerContainer,
610 field_decode_ctx: &mut FieldDecodeContext,
611 fp_buf: &mut Vec<FieldPath>,
612 ) -> Result<()> {
613 let entity = match self.entity_mut(index) {
614 Some(e) => e,
615 None => {
616 return Err(Error::Parse {
617 context: format!("tried to update non-existent entity #{}", index),
618 });
619 }
620 };
621
622 let serializer = serializers
623 .get(&entity.class_name)
624 .ok_or_else(|| Error::Parse {
625 context: format!("no serializer for {}", entity.class_name),
626 })?;
627
628 entity.apply_update(br, serializer, field_decode_ctx, fp_buf)?;
629 Ok(())
630 }
631
632 #[allow(clippy::too_many_arguments)]
634 fn handle_create_filtered(
635 &mut self,
636 index: i32,
637 br: &mut BitReader,
638 class_info: &ClassInfo,
639 serializers: &SerializerContainer,
640 string_tables: &StringTableContainer,
641 field_decode_ctx: &mut FieldDecodeContext,
642 class_filter: &HashSet<&str>,
643 fp_buf: &mut Vec<FieldPath>,
644 ) -> Result<bool> {
645 let class_id = br.read_bits(class_info.bits)? as i32;
646 let _serial = br.read_bits(NUM_SERIAL_NUM_BITS as usize)?;
647 let _unknown = br.read_uvarint32()?;
648
649 let class_entry = class_info.by_id(class_id).ok_or_else(|| Error::Parse {
650 context: format!("unknown class_id {}", class_id),
651 })?;
652
653 let serializer =
654 serializers
655 .get(&class_entry.network_name)
656 .ok_or_else(|| Error::Parse {
657 context: format!("no serializer for {}", class_entry.network_name),
658 })?;
659
660 if !class_filter.contains(class_entry.network_name.as_str()) {
662 self.set_skipped(index, Some(class_id));
665 Entity::skip_update(br, serializer, field_decode_ctx, fp_buf)?;
666 return Ok(false);
667 }
668
669 let mut entity = Entity::new(index, class_id, class_entry.network_name.clone());
671 entity.fields.reserve(serializer.fields.len());
675
676 if let Some(baseline_data) = string_tables.instance_baselines.get(&class_id) {
677 let mut baseline_br = BitReader::new(baseline_data);
678 entity.apply_update(&mut baseline_br, serializer, field_decode_ctx, fp_buf)?;
679 }
680
681 entity.apply_update(br, serializer, field_decode_ctx, fp_buf)?;
682 self.put_entity(index, entity);
683
684 Ok(true)
685 }
686
687 #[allow(clippy::too_many_arguments)]
689 fn handle_update_filtered(
690 &mut self,
691 index: i32,
692 br: &mut BitReader,
693 class_info: &ClassInfo,
694 serializers: &SerializerContainer,
695 field_decode_ctx: &mut FieldDecodeContext,
696 _class_filter: &HashSet<&str>,
697 fp_buf: &mut Vec<FieldPath>,
698 ) -> Result<bool> {
699 if let Some(entity) = self.entity_mut(index) {
701 let serializer = serializers
702 .get(&entity.class_name)
703 .ok_or_else(|| Error::Parse {
704 context: format!("no serializer for {}", entity.class_name),
705 })?;
706
707 entity.apply_update(br, serializer, field_decode_ctx, fp_buf)?;
708 return Ok(true);
709 }
710
711 if let Some(class_id) = self.skipped_class(index) {
713 let class_entry = class_info.by_id(class_id).ok_or_else(|| Error::Parse {
714 context: format!("unknown class_id {}", class_id),
715 })?;
716
717 let serializer =
718 serializers
719 .get(&class_entry.network_name)
720 .ok_or_else(|| Error::Parse {
721 context: format!("no serializer for {}", class_entry.network_name),
722 })?;
723
724 Entity::skip_update(br, serializer, field_decode_ctx, fp_buf)?;
726 }
727
728 Ok(false)
731 }
732
733 pub fn get(&self, index: i32) -> Option<&Entity> {
735 usize::try_from(index)
736 .ok()
737 .and_then(|i| self.entities.get(i))
738 .and_then(Option::as_ref)
739 }
740
741 pub fn get_by_handle(&self, handle: u32) -> Option<&Entity> {
747 self.get((handle & ENTITY_HANDLE_INDEX_MASK) as i32)
748 }
749
750 pub fn iter(&self) -> impl Iterator<Item = (i32, &Entity)> {
753 self.entities
754 .iter()
755 .enumerate()
756 .filter_map(|(i, slot)| slot.as_ref().map(|e| (i as i32, e)))
757 }
758
759 pub fn updated_indices(&self) -> &[i32] {
771 &self.updated
772 }
773
774 pub fn clear_updated(&mut self) {
776 self.updated.clear();
777 }
778
779 pub fn len(&self) -> usize {
781 self.entities.iter().filter(|slot| slot.is_some()).count()
782 }
783
784 pub fn is_empty(&self) -> bool {
785 self.entities.iter().all(Option::is_none)
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792
793 #[test]
794 fn container_new_is_empty() {
795 let c = EntityContainer::new();
796 assert!(c.is_empty());
797 assert_eq!(c.len(), 0);
798 assert!(c.get(0).is_none());
799 }
800
801 #[test]
802 fn entity_fields_insert_and_get() {
803 let mut e = Entity::new(1, 10, "TestClass".to_string());
804 e.fields.insert(42, FieldValue::I32(100));
805 assert!(matches!(e.fields.get(&42), Some(FieldValue::I32(100))));
806 }
807
808 #[test]
809 fn container_insert_and_iter() {
810 let mut c = EntityContainer::new();
811 let e = Entity::new(5, 10, "Hero".to_string());
812 c.put_entity(5, e);
813 assert_eq!(c.len(), 1);
814 assert!(!c.is_empty());
815 assert!(c.get(5).is_some());
816 assert_eq!(c.get(5).unwrap().class_name, "Hero");
817 }
818
819 #[test]
820 fn container_iter_yields_entries() {
821 let mut c = EntityContainer::new();
822 c.put_entity(1, Entity::new(1, 1, "A".to_string()));
823 c.put_entity(2, Entity::new(2, 2, "B".to_string()));
824 let keys: Vec<i32> = c.iter().map(|(k, _)| k).collect();
825 assert_eq!(keys.len(), 2);
826 }
827
828 #[test]
829 fn entity_basic_fields() {
830 let e = Entity::new(7, 42, "NPC".to_string());
831 assert_eq!(e.index, 7);
832 assert_eq!(e.class_id, 42);
833 assert_eq!(e.class_name, "NPC");
834 assert_eq!(e.serial, 0);
835 assert!(e.fields.is_empty());
836 }
837}