mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 10:14:06 +02:00
gamestate improvements
This commit is contained in:
parent
74ab5072d2
commit
307a6cf953
7 changed files with 1710 additions and 65 deletions
|
|
@ -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
31
src/bin/gamestate.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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": []
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue