1
0
Fork 0
mirror of https://codeberg.org/demostf/parser.git synced 2026-06-03 10:14:06 +02:00

gamestate improvements

This commit is contained in:
Robin Appelman 2022-08-25 23:54:46 +02:00
commit 307a6cf953
7 changed files with 1710 additions and 65 deletions

View file

@ -21,6 +21,10 @@ path = "src/bin/main.rs"
name = "reencode_demo" name = "reencode_demo"
path = "src/bin/reencode.rs" path = "src/bin/reencode.rs"
[[bin]]
name = "gamestate"
path = "src/bin/gamestate.rs"
[[bin]] [[bin]]
name = "schema" name = "schema"
path = "src/bin/schema.rs" path = "src/bin/schema.rs"

31
src/bin/gamestate.rs Normal file
View file

@ -0,0 +1,31 @@
use std::env;
use std::fs;
use main_error::MainError;
use tf_demo_parser::demo::parser::gamestateanalyser::GameStateAnalyser;
pub use tf_demo_parser::{Demo, DemoParser, Parse, ParseError, ParserState, Stream};
#[cfg(feature = "jemallocator")]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() -> Result<(), MainError> {
#[cfg(feature = "better_panic")]
better_panic::install();
#[cfg(feature = "trace")]
tracing_subscriber::fmt::init();
let args: Vec<_> = env::args().collect();
if args.len() < 2 {
println!("1 argument required");
return Ok(());
}
let path = args[1].clone();
let file = fs::read(path)?;
let demo = Demo::new(&file);
let parser = DemoParser::new_with_analyser(demo.get_stream(), GameStateAnalyser::new());
let (_, state) = parser.parse()?;
println!("{}", serde_json::to_string_pretty(&state.players)?);
Ok(())
}

View file

@ -52,8 +52,8 @@ pub enum Team {
impl Team { impl Team {
pub fn new<U>(number: U) -> Self pub fn new<U>(number: U) -> Self
where where
u8: TryFrom<U>, u8: TryFrom<U>,
{ {
Team::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default() Team::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default()
} }
@ -70,7 +70,7 @@ impl Default for Team {
} }
#[derive( #[derive(
Debug, Clone, Serialize, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Display, FromStr, Debug, Clone, Serialize, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Display, FromStr,
)] )]
#[display(style = "lowercase")] #[display(style = "lowercase")]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
@ -90,8 +90,8 @@ pub enum Class {
impl<'de> Deserialize<'de> for Class { impl<'de> Deserialize<'de> for Class {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(untagged)] #[serde(untagged)]
@ -121,8 +121,8 @@ fn test_class_deserialize() {
impl Class { impl Class {
pub fn new<U>(number: U) -> Self pub fn new<U>(number: U) -> Self
where where
u8: TryFrom<U>, u8: TryFrom<U>,
{ {
Class::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default() Class::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default()
} }
@ -140,7 +140,7 @@ pub struct ClassList([u8; 10]);
impl ClassList { impl ClassList {
/// Get an iterator for all classes played and the number of spawn on the class /// Get an iterator for all classes played and the number of spawn on the class
pub fn iter(&self) -> impl Iterator<Item=(Class, u8)> + '_ { pub fn iter(&self) -> impl Iterator<Item = (Class, u8)> + '_ {
self.0 self.0
.iter() .iter()
.copied() .copied()
@ -150,7 +150,7 @@ impl ClassList {
} }
/// Get an iterator for all classes played and the number of spawn on the class, sorted by the number of spawns /// Get an iterator for all classes played and the number of spawn on the class, sorted by the number of spawns
pub fn sorted(&self) -> impl Iterator<Item=(Class, u8)> { pub fn sorted(&self) -> impl Iterator<Item = (Class, u8)> {
let mut classes = self.iter().collect::<Vec<(Class, u8)>>(); let mut classes = self.iter().collect::<Vec<(Class, u8)>>();
classes.sort_by(|a, b| a.1.cmp(&b.1).reverse()); classes.sort_by(|a, b| a.1.cmp(&b.1).reverse());
classes.into_iter() classes.into_iter()
@ -184,8 +184,8 @@ impl IndexMut<Class> for ClassList {
impl Serialize for ClassList { impl Serialize for ClassList {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
let count = self.0.iter().filter(|c| **c > 0).count(); let count = self.0.iter().filter(|c| **c > 0).count();
let mut classes = serializer.serialize_map(Some(count))?; let mut classes = serializer.serialize_map(Some(count))?;
@ -213,7 +213,7 @@ impl From<HashMap<Class, u8>> for ClassList {
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive( #[derive(
Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Default, Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Default,
)] )]
pub struct UserId(pub u8); pub struct UserId(pub u8);
@ -465,8 +465,15 @@ impl Analyser {
} }
} }
fn parse_user_info(&mut self, index: usize, text: Option<&str>, data: Option<Stream>) -> ReadResult<()> { fn parse_user_info(
if let Some(user_info) = crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)? { &mut self,
index: usize,
text: Option<&str>,
data: Option<Stream>,
) -> ReadResult<()> {
if let Some(user_info) =
crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)?
{
self.state self.state
.users .users
.entry(user_info.player_info.user_id.into()) .entry(user_info.player_info.user_id.into())

View file

@ -1,15 +1,18 @@
use crate::demo::gameevent_gen::PlayerDeathEvent;
use crate::demo::gamevent::GameEvent;
use crate::demo::message::gameevent::GameEventMessage;
use crate::demo::message::packetentities::{EntityId, PacketEntity}; use crate::demo::message::packetentities::{EntityId, PacketEntity};
use crate::demo::message::Message; use crate::demo::message::Message;
use crate::demo::packet::datatable::{ParseSendTable, SendTableName, ServerClass, ServerClassName}; use crate::demo::packet::datatable::{ParseSendTable, ServerClass, ServerClassName};
use crate::demo::packet::message::MessagePacketMeta;
use crate::demo::packet::stringtable::StringTableEntry; use crate::demo::packet::stringtable::StringTableEntry;
use crate::demo::parser::analyser::UserInfo; use crate::demo::parser::analyser::UserInfo;
pub use crate::demo::parser::analyser::{Class, Team, UserId}; pub use crate::demo::parser::analyser::{Class, Team, UserId};
use crate::demo::parser::handler::BorrowMessageHandler; use crate::demo::parser::handler::BorrowMessageHandler;
use crate::demo::parser::MessageHandler; use crate::demo::parser::MessageHandler;
use crate::demo::sendprop::{SendProp, SendPropIdentifier, SendPropName, SendPropValue}; use crate::demo::sendprop::{SendProp, SendPropIdentifier, SendPropValue};
use crate::demo::vector::{Vector, VectorXY}; use crate::demo::vector::{Vector, VectorXY};
use crate::{MessageType, ParserState, ReadResult, Stream}; use crate::{MessageType, ParserState, ReadResult, Stream};
use fnv::FnvHashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::str::FromStr; use std::str::FromStr;
@ -47,6 +50,7 @@ pub struct Player {
pub pitch_angle: f32, pub pitch_angle: f32,
pub state: PlayerState, pub state: PlayerState,
pub info: Option<UserInfo>, pub info: Option<UserInfo>,
pub charge: u8,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -105,11 +109,33 @@ pub struct World {
pub boundary_max: Vector, pub boundary_max: Vector,
} }
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Kill {
pub attacker_id: u16,
pub assister_id: u16,
pub victim_id: u16,
pub weapon: String,
pub tick: u32,
}
impl Kill {
fn new(tick: u32, death: &PlayerDeathEvent) -> Self {
Kill {
attacker_id: death.attacker,
assister_id: death.assister,
victim_id: death.user_id,
weapon: death.weapon.to_string(),
tick,
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] #[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct GameState { pub struct GameState {
pub players: Vec<Player>, pub players: Vec<Player>,
pub buildings: Vec<Building>, pub buildings: Vec<Building>,
pub world: Option<World>, pub world: Option<World>,
pub kills: Vec<Kill>,
} }
impl GameState { impl GameState {
@ -134,6 +160,7 @@ impl GameState {
pitch_angle: 0.0, pitch_angle: 0.0,
state: PlayerState::Alive, state: PlayerState::Alive,
info: None, info: None,
charge: 0,
}; };
let index = self.players.len(); let index = self.players.len();
@ -149,7 +176,7 @@ impl GameState {
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct GameStateAnalyser { pub struct GameStateAnalyser {
pub state: GameState, pub state: GameState,
prop_names: FnvHashMap<SendPropIdentifier, (SendTableName, SendPropName)>, tick: u32,
class_names: Vec<ServerClassName>, // indexed by ClassId class_names: Vec<ServerClassName>, // indexed by ClassId
} }
@ -157,39 +184,27 @@ impl MessageHandler for GameStateAnalyser {
type Output = GameState; type Output = GameState;
fn does_handle(message_type: MessageType) -> bool { fn does_handle(message_type: MessageType) -> bool {
matches!(message_type, MessageType::PacketEntities) matches!(
message_type,
MessageType::PacketEntities | MessageType::GameEvent
)
} }
fn handle_message(&mut self, message: &Message, _tick: u32, parser_state: &ParserState) { fn handle_message(&mut self, message: &Message, _tick: u32, parser_state: &ParserState) {
if let Message::PacketEntities(message) = message { match message {
for entity in &message.entities { Message::PacketEntities(message) => {
self.handle_entity(entity, parser_state); for entity in &message.entities {
self.handle_entity(entity, parser_state);
}
} }
Message::GameEvent(GameEventMessage {
event: GameEvent::PlayerDeath(death),
..
}) => self.state.kills.push(Kill::new(self.tick, death.as_ref())),
_ => {}
} }
} }
fn handle_data_tables(
&mut self,
parse_tables: &[ParseSendTable],
server_classes: &[ServerClass],
_parser_state: &ParserState,
) {
for table in parse_tables {
for prop_def in &table.props {
self.prop_names.insert(
prop_def.identifier(),
(table.name.clone(), prop_def.name.clone()),
);
}
}
self.class_names = server_classes
.iter()
.map(|class| &class.name)
.cloned()
.collect();
}
fn handle_string_entry( fn handle_string_entry(
&mut self, &mut self,
table: &str, table: &str,
@ -206,6 +221,28 @@ impl MessageHandler for GameStateAnalyser {
} }
} }
fn handle_data_tables(
&mut self,
_parse_tables: &[ParseSendTable],
server_classes: &[ServerClass],
_parser_state: &ParserState,
) {
self.class_names = server_classes
.iter()
.map(|class| &class.name)
.cloned()
.collect();
}
fn handle_packet_meta(
&mut self,
tick: u32,
_meta: &MessagePacketMeta,
_parser_state: &ParserState,
) {
self.tick = tick;
}
fn into_output(self, _state: &ParserState) -> Self::Output { fn into_output(self, _state: &ParserState) -> Self::Output {
self.state self.state
} }
@ -238,7 +275,7 @@ impl GameStateAnalyser {
pub fn handle_player_resource(&mut self, entity: &PacketEntity, parser_state: &ParserState) { pub fn handle_player_resource(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
for prop in entity.props(parser_state) { for prop in entity.props(parser_state) {
if let Some((table_name, prop_name)) = self.prop_names.get(&prop.identifier) { if let Some((table_name, prop_name)) = prop.identifier.names() {
if let Ok(player_id) = u32::from_str(prop_name.as_str()) { if let Ok(player_id) = u32::from_str(prop_name.as_str()) {
let entity_id = EntityId::from(player_id); let entity_id = EntityId::from(player_id);
if let Some(player) = self if let Some(player) = self
@ -260,6 +297,9 @@ impl GameStateAnalyser {
player.class = player.class =
Class::new(i64::try_from(&prop.value).unwrap_or_default()) Class::new(i64::try_from(&prop.value).unwrap_or_default())
} }
"m_iChargeLevel" => {
player.charge = i64::try_from(&prop.value).unwrap_or_default() as u8
}
_ => {} _ => {}
} }
} }
@ -346,8 +386,15 @@ impl GameStateAnalyser {
} }
} }
fn parse_user_info(&mut self, index: usize, text: Option<&str>, data: Option<Stream>) -> ReadResult<()> { fn parse_user_info(
if let Some(user_info) = crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)? { &mut self,
index: usize,
text: Option<&str>,
data: Option<Stream>,
) -> ReadResult<()> {
if let Some(user_info) =
crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)?
{
let id = user_info.entity_id; let id = user_info.entity_id;
self.state.get_or_create_player(id).info = Some(user_info.into()); self.state.get_or_create_player(id).info = Some(user_info.into());
} }

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,8 @@
"userId": 2, "userId": 2,
"steamId": "[U:1:64229260]", "steamId": "[U:1:64229260]",
"team": "other" "team": "other"
} },
"charge": 0
} }
], ],
"buildings": [], "buildings": [],
@ -35,5 +36,6 @@
"y": 3936.0, "y": 3936.0,
"z": 1641.0 "z": 1641.0
} }
} },
"kills": []
} }

View file

@ -40,11 +40,18 @@ fn game_state_test(input_file: &str, snapshot_file: &str) {
.parse() .parse()
.unwrap(); .unwrap();
// fs::write(
// format!("test_data/{}", snapshot_file),
// serde_json::to_string_pretty(&state).unwrap(),
// )
// .unwrap();
let expected: GameState = serde_json::from_slice( let expected: GameState = serde_json::from_slice(
fs::read(format!("test_data/{}", snapshot_file)) fs::read(format!("test_data/{}", snapshot_file))
.expect("Unable to read file") .expect("Unable to read file")
.as_slice(), .as_slice(),
) )
.unwrap(); .unwrap();
pretty_assertions::assert_eq!(expected.players, state.players);
pretty_assertions::assert_eq!(expected, state); pretty_assertions::assert_eq!(expected, state);
} }