Skip to main content

boon/demo/
command.rs

1//! Demo command types and utilities.
2
3// Re-export proto enums for convenient access
4pub use boon_proto::proto::EDemoCommands;
5pub use boon_proto::proto::SvcMessages;
6
7/// Outer demo command type constants.
8///
9/// Rust doesn't allow `Enum as i32` in match patterns, so we re-export
10/// the protobuf enum values as plain `i32`/`u32` constants.
11pub mod dem {
12    use super::EDemoCommands;
13    pub const STOP: i32 = EDemoCommands::DemStop as i32;
14    pub const FILE_HEADER: i32 = EDemoCommands::DemFileHeader as i32;
15    pub const FILE_INFO: i32 = EDemoCommands::DemFileInfo as i32;
16    pub const SYNC_TICK: i32 = EDemoCommands::DemSyncTick as i32;
17    pub const SEND_TABLES: i32 = EDemoCommands::DemSendTables as i32;
18    pub const CLASS_INFO: i32 = EDemoCommands::DemClassInfo as i32;
19    pub const PACKET: i32 = EDemoCommands::DemPacket as i32;
20    pub const SIGNON_PACKET: i32 = EDemoCommands::DemSignonPacket as i32;
21    pub const FULL_PACKET: i32 = EDemoCommands::DemFullPacket as i32;
22    /// Bitmask ORed into the raw command to indicate Snappy compression.
23    pub const IS_COMPRESSED: u32 = EDemoCommands::DemIsCompressed as u32;
24}
25
26/// Inner packet service message type constants.
27pub mod svc {
28    use super::SvcMessages;
29    pub const SERVER_INFO: u32 = SvcMessages::SvcServerInfo as u32;
30    pub const CREATE_STRING_TABLE: u32 = SvcMessages::SvcCreateStringTable as u32;
31    pub const UPDATE_STRING_TABLE: u32 = SvcMessages::SvcUpdateStringTable as u32;
32    pub const PACKET_ENTITIES: u32 = SvcMessages::SvcPacketEntities as u32;
33    pub const USER_MESSAGE: u32 = SvcMessages::SvcUserMessage as u32;
34}
35
36/// Game event message type constants (from `EBaseGameEvents`).
37pub mod ge {
38    /// `GE_Source1LegacyGameEventList` — defines available event types.
39    pub const SOURCE1_LEGACY_GAME_EVENT_LIST: u32 = 205;
40    /// `GE_Source1LegacyGameEvent` — an individual game event instance.
41    pub const SOURCE1_LEGACY_GAME_EVENT: u32 = 207;
42}
43
44/// Return a human-readable name for a user message type.
45pub fn user_message_name(msg_type: i32) -> String {
46    boon_proto::proto::CitadelUserMessageIds::try_from(msg_type)
47        .map(|e| e.as_str_name().to_string())
48        .unwrap_or_else(|_| format!("UserMessage_{}", msg_type))
49}
50
51/// Header for a demo command in the stream.
52///
53/// Each command in the `.dem` file is prefixed with this header:
54/// `(varint cmd | IS_COMPRESSED, varint tick, varint body_size)`.
55#[derive(Debug, Clone)]
56pub struct CmdHeader {
57    /// Command type (one of the `dem::*` constants).
58    pub cmd: i32,
59    /// Game tick this command applies to.
60    pub tick: i32,
61    /// Whether the body is Snappy-compressed.
62    pub compressed: bool,
63    /// Size of the body in bytes (before decompression).
64    pub body_size: u32,
65}
66
67/// Return a human-readable name for a demo command.
68pub fn command_name(cmd: i32) -> &'static str {
69    EDemoCommands::try_from(cmd)
70        .map(|e| e.as_str_name())
71        .unwrap_or("DEM_Unknown")
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn command_name_stop() {
80        assert_eq!(command_name(dem::STOP), "DEM_Stop");
81    }
82
83    #[test]
84    fn command_name_file_header() {
85        assert_eq!(command_name(dem::FILE_HEADER), "DEM_FileHeader");
86    }
87
88    #[test]
89    fn command_name_unknown() {
90        assert_eq!(command_name(9999), "DEM_Unknown");
91    }
92
93    #[test]
94    fn user_message_name_known() {
95        // 300 is k_ECitadelUserMsg_Damage
96        let name = user_message_name(300);
97        assert!(!name.starts_with("UserMessage_"), "got: {name}");
98    }
99
100    #[test]
101    fn user_message_name_unknown() {
102        let name = user_message_name(99999);
103        assert_eq!(name, "UserMessage_99999");
104    }
105
106    #[test]
107    fn dem_constants_match_expected() {
108        assert_eq!(dem::STOP, 0);
109        assert_eq!(dem::FILE_HEADER, 1);
110        assert_eq!(dem::PACKET, 7);
111        assert_eq!(dem::SIGNON_PACKET, 8);
112    }
113
114    #[test]
115    fn ge_constants() {
116        assert_eq!(ge::SOURCE1_LEGACY_GAME_EVENT_LIST, 205);
117        assert_eq!(ge::SOURCE1_LEGACY_GAME_EVENT, 207);
118    }
119}