diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a266d7b8..516df0e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,6 @@ name: Python application on: push: branches: ["main"] - pull_request: - branches: ["main"] permissions: contents: read diff --git a/src/parser/src/entities.rs b/src/parser/src/entities.rs index 6f8fc5db..6e00d1ee 100644 --- a/src/parser/src/entities.rs +++ b/src/parser/src/entities.rs @@ -105,8 +105,8 @@ impl ParserThread { for (field_info, debug) in self.field_infos[..n_updates].iter().zip(&self.debug_fields) { let result = bitreader.decode(&field_info.decoder, &self.qf_mapper)?; // self.game_events_counter.insert(debug.field.full_name.clone()); - if debug.field.full_name.contains("Time") { - println!("{:?} {:?} {:?}", debug.path, debug.field.full_name, result); + if debug.field.full_name.contains("Controller") && self.tick < 200 { + println!("{:?} {:?} {:?} {:?}", debug.path, debug.field.full_name, result, self.tick); } } } else { diff --git a/src/parser/src/game_events.rs b/src/parser/src/game_events.rs index 48a2d200..f1950cbf 100644 --- a/src/parser/src/game_events.rs +++ b/src/parser/src/game_events.rs @@ -7,6 +7,7 @@ use crate::stringtables::UserInfo; use crate::variants::*; use ahash::AHashMap; use ahash::RandomState; +use csgoproto::cstrike15_usermessages::CCSUsrMsg_ServerRankUpdate; use csgoproto::netmessages::csvcmsg_game_event_list::Descriptor_t; use csgoproto::netmessages::CSVCMsg_GameEventList; use csgoproto::networkbasetypes::csvcmsg_game_event::Key_t; @@ -24,6 +25,8 @@ static INTERNALEVENTFIELDS: &'static [&str] = &[ "assister_pawn", ]; const ENTITYIDNONE: i32 = 2047; +// https://developer.valvesoftware.com/wiki/SteamID +const STEAMID64INDIVIDUALIDENTIFIER: u64 = 0x0110000100000000; impl Parser { // Message that should come before first game event @@ -81,7 +84,7 @@ impl ParserThread { }); Ok(()) } - fn find_user_by_userid(&self, userid: i32) -> Option<&UserInfo> { + pub fn find_user_by_userid(&self, userid: i32) -> Option<&UserInfo> { for player in self.stringtable_players.values() { if player.userid == userid { return Some(player); @@ -89,7 +92,7 @@ impl ParserThread { } return None; } - fn entity_id_from_userid(&self, userid: i32) -> Option { + pub fn entity_id_from_userid(&self, userid: i32) -> Option { if let Some(userinfo) = self.find_user_by_userid(userid) { for player in self.players.values() { if player.steamid == Some(userinfo.steamid) { @@ -99,7 +102,7 @@ impl ParserThread { } return None; } - fn find_extra(&self, fields: &Vec) -> Result, DemoParserError> { + pub fn find_extra(&self, fields: &Vec) -> Result, DemoParserError> { let mut extra_fields = vec![]; // Always add tick to event extra_fields.push(EventField { @@ -132,7 +135,7 @@ impl ParserThread { extra_fields.extend(self.find_non_player_props()); Ok(extra_fields) } - fn generate_empty_fields(&self, prefix: &str) -> Vec { + pub fn generate_empty_fields(&self, prefix: &str) -> Vec { let mut extra_fields = vec![]; // when pointer fails for some reason we need to add None to output for prop_info in &self.prop_controller.prop_infos { @@ -159,7 +162,7 @@ impl ParserThread { extra_fields } - fn find_non_player_props(&self) -> Vec { + pub fn find_non_player_props(&self) -> Vec { let mut extra_fields = vec![]; for prop_info in &self.prop_controller.prop_infos { let fields = match prop_info.prop_type { @@ -172,7 +175,7 @@ impl ParserThread { extra_fields } - fn find_other_rules_props(&self, prop_info: &PropInfo) -> Vec { + pub fn find_other_rules_props(&self, prop_info: &PropInfo) -> Vec { let mut extra_fields = vec![]; let prop = match self.rules_entity_id { Some(entid) => match self.get_prop_from_ent(&prop_info.id, &entid) { @@ -187,7 +190,7 @@ impl ParserThread { }); extra_fields } - fn find_other_team_props(&self, prop_info: &PropInfo) -> Vec { + pub fn find_other_team_props(&self, prop_info: &PropInfo) -> Vec { let mut extra_fields = vec![]; let t = self.teams.team2_entid; let ct = self.teams.team3_entid; @@ -259,7 +262,7 @@ impl ParserThread { } extra_pairs } - fn create_player_name_field(&self, entity_id: i32, prefix: &str) -> EventField { + pub fn create_player_name_field(&self, entity_id: i32, prefix: &str) -> EventField { if entity_id == ENTITYIDNONE { return EventField { name: prefix.to_owned() + "_name", @@ -278,7 +281,7 @@ impl ParserThread { data: data, } } - fn create_player_steamid_field(&self, entity_id: i32, prefix: &str) -> EventField { + pub fn create_player_steamid_field(&self, entity_id: i32, prefix: &str) -> EventField { if entity_id == ENTITYIDNONE { return EventField { name: prefix.to_owned() + "_steamid", @@ -297,6 +300,68 @@ impl ParserThread { data: data, } } + pub fn player_from_steamid32(&self, steamid32: i32) -> Option { + for (_entid, player) in &self.players { + if let Some(steamid) = player.steamid { + if steamid - STEAMID64INDIVIDUALIDENTIFIER == steamid32 as u64 { + return Some(player.player_entity_id.unwrap()); + } + } + } + None + } + pub fn create_custom_event_rank_update(&mut self, msg_bytes: &[u8]) -> Result<(), DemoParserError> { + if !self.wanted_events.contains(&"rank_update".to_string()) { + return Ok(()); + } + let update_msg: CCSUsrMsg_ServerRankUpdate = match Message::parse_from_bytes(&msg_bytes) { + Ok(m) => m, + Err(_e) => return Err(DemoParserError::MalformedMessage), + }; + + for update in update_msg.rank_update { + let mut fields = vec![]; + + let entity_id = match self.player_from_steamid32(update.account_id.unwrap()) { + Some(eid) => eid, + None => continue, + }; + + fields.push(self.create_player_name_field(entity_id, "user")); + fields.push(self.create_player_steamid_field(entity_id, "user")); + fields.extend(self.find_extra_props_events(entity_id, "user")); + + fields.push(EventField { + data: Some(Variant::I32(update.num_wins())), + name: "num_wins".to_string(), + }); + fields.push(EventField { + data: Some(Variant::I32(update.rank_old())), + name: "rank_old".to_string(), + }); + fields.push(EventField { + data: Some(Variant::I32(update.rank_new())), + name: "rank_new".to_string(), + }); + fields.push(EventField { + data: Some(Variant::F32(update.rank_change())), + name: "rank_change".to_string(), + }); + fields.push(EventField { + data: Some(Variant::I32(update.rank_type_id())), + name: "rank_type_id".to_string(), + }); + let ge = GameEvent { + name: "rank_update".to_string(), + fields: fields, + tick: self.tick, + }; + self.game_events.push(ge); + self.game_events_counter.insert("rank_update".to_string()); + } + + Ok(()) + } } // what is this shit fn parse_key(key: &Key_t) -> Option { diff --git a/src/parser/src/main.rs b/src/parser/src/main.rs index d372d97e..bbfaa72d 100644 --- a/src/parser/src/main.rs +++ b/src/parser/src/main.rs @@ -58,7 +58,7 @@ fn main() { let mut ds = Parser::new(settings); let d = ds.parse_demo().unwrap(); println!("TOTAL {:?}", before.elapsed()); - println!("{:?}", d.game_events_counter); + println!("{:?}", d.game_events); } println!("TOTAL {:?}", before.elapsed()); } diff --git a/src/parser/src/parser_threads.rs b/src/parser/src/parser_threads.rs index 6a7e1f0c..46962ef1 100644 --- a/src/parser/src/parser_threads.rs +++ b/src/parser/src/parser_threads.rs @@ -93,6 +93,7 @@ impl ParserThread { UM_SayText2 => self.parse_chat_messages(&msg_bytes), net_SetConVar => self.parse_convars(&msg_bytes), CS_UM_PlayerStatsUpdate => self.parse_player_stats_update(&msg_bytes), + CS_UM_ServerRankUpdate => self.create_custom_event_rank_update(&msg_bytes), _ => Ok(()), }; ok?; diff --git a/src/python/Cargo.toml b/src/python/Cargo.toml index 8672a59d..974a88fd 100644 --- a/src/python/Cargo.toml +++ b/src/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "demoparser2" -version = "0.5.1" +version = "0.6.0" edition = "2021" diff --git a/src/python/tests/e2e_test.py b/src/python/tests/e2e_test.py index d92bc149..8db62e0c 100755 --- a/src/python/tests/e2e_test.py +++ b/src/python/tests/e2e_test.py @@ -47,5 +47,11 @@ def test_parse_grenades(self): df = convert_same_dtypes(parser.parse_grenades(), df_correct) assert_frame_equal(df, df_correct) + def test_custom_even_rank_update(self): + parser = DemoParser("tests/data/test.dem") + df = parser.parse_event("rank_update") + df_correct = convert_same_dtypes(df, pd.read_csv("tests/data/python/rank_update.csv")) + assert_frame_equal(df, df_correct) + if __name__ == '__main__': unittest.main() \ No newline at end of file