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
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 {
|
||||
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())
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue