1
0
Fork 0
mirror of https://codeberg.org/demostf/parser.git synced 2026-06-03 18:24:05 +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"
path = "src/bin/reencode.rs"
[[bin]]
name = "gamestate"
path = "src/bin/gamestate.rs"
[[bin]]
name = "schema"
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 {
pub fn new<U>(number: U) -> Self
where
u8: TryFrom<U>,
where
u8: TryFrom<U>,
{
Team::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default()
}
@ -70,7 +70,7 @@ impl Default for Team {
}
#[derive(
Debug, Clone, Serialize, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Display, FromStr,
Debug, Clone, Serialize, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Display, FromStr,
)]
#[display(style = "lowercase")]
#[serde(rename_all = "lowercase")]
@ -90,8 +90,8 @@ pub enum Class {
impl<'de> Deserialize<'de> for Class {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
where
D: Deserializer<'de>,
{
#[derive(Deserialize, Debug)]
#[serde(untagged)]
@ -121,8 +121,8 @@ fn test_class_deserialize() {
impl Class {
pub fn new<U>(number: U) -> Self
where
u8: TryFrom<U>,
where
u8: TryFrom<U>,
{
Class::try_from(u8::try_from(number).unwrap_or_default()).unwrap_or_default()
}
@ -140,7 +140,7 @@ pub struct ClassList([u8; 10]);
impl ClassList {
/// 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
.iter()
.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
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)>>();
classes.sort_by(|a, b| a.1.cmp(&b.1).reverse());
classes.into_iter()
@ -184,8 +184,8 @@ impl IndexMut<Class> for ClassList {
impl Serialize for ClassList {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let count = self.0.iter().filter(|c| **c > 0).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))]
#[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);
@ -465,8 +465,15 @@ impl Analyser {
}
}
fn parse_user_info(&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)? {
fn parse_user_info(
&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
.users
.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::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::parser::analyser::UserInfo;
pub use crate::demo::parser::analyser::{Class, Team, UserId};
use crate::demo::parser::handler::BorrowMessageHandler;
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::{MessageType, ParserState, ReadResult, Stream};
use fnv::FnvHashMap;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::str::FromStr;
@ -47,6 +50,7 @@ pub struct Player {
pub pitch_angle: f32,
pub state: PlayerState,
pub info: Option<UserInfo>,
pub charge: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -105,11 +109,33 @@ pub struct World {
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)]
pub struct GameState {
pub players: Vec<Player>,
pub buildings: Vec<Building>,
pub world: Option<World>,
pub kills: Vec<Kill>,
}
impl GameState {
@ -134,6 +160,7 @@ impl GameState {
pitch_angle: 0.0,
state: PlayerState::Alive,
info: None,
charge: 0,
};
let index = self.players.len();
@ -149,7 +176,7 @@ impl GameState {
#[derive(Default, Debug)]
pub struct GameStateAnalyser {
pub state: GameState,
prop_names: FnvHashMap<SendPropIdentifier, (SendTableName, SendPropName)>,
tick: u32,
class_names: Vec<ServerClassName>, // indexed by ClassId
}
@ -157,39 +184,27 @@ impl MessageHandler for GameStateAnalyser {
type Output = GameState;
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) {
if let Message::PacketEntities(message) = message {
for entity in &message.entities {
self.handle_entity(entity, parser_state);
match message {
Message::PacketEntities(message) => {
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(
&mut self,
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 {
self.state
}
@ -238,7 +275,7 @@ impl GameStateAnalyser {
pub fn handle_player_resource(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
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()) {
let entity_id = EntityId::from(player_id);
if let Some(player) = self
@ -260,6 +297,9 @@ impl GameStateAnalyser {
player.class =
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<()> {
if let Some(user_info) = crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)? {
fn parse_user_info(
&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;
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,
"steamId": "[U:1:64229260]",
"team": "other"
}
},
"charge": 0
}
],
"buildings": [],
@ -35,5 +36,6 @@
"y": 3936.0,
"z": 1641.0
}
}
},
"kills": []
}

View file

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