mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 18:24:05 +02:00
gamestate analyser wip
This commit is contained in:
parent
6222258ac2
commit
5f6cfe077e
13 changed files with 379 additions and 9 deletions
|
|
@ -4,10 +4,12 @@ use std::fs;
|
|||
use main_error::MainError;
|
||||
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();
|
||||
|
||||
let args: Vec<_> = env::args().collect();
|
||||
|
|
|
|||
|
|
@ -9034,4 +9034,3 @@ pub fn get_sizes() -> std::collections::hash_map::HashMap<&'static str, usize> {
|
|||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ impl From<ClassId> for usize {
|
|||
#[derive(BitRead, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Clone, Display)]
|
||||
pub struct ServerClassName(Rc<String>);
|
||||
|
||||
impl ServerClassName {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ServerClassName {
|
||||
fn from(value: String) -> Self {
|
||||
Self(Rc::new(value))
|
||||
|
|
@ -50,6 +56,12 @@ pub struct ServerClass {
|
|||
)]
|
||||
pub struct SendTableName(Rc<String>);
|
||||
|
||||
impl SendTableName {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SendTableName {
|
||||
fn from(value: String) -> Self {
|
||||
Self(Rc::new(value))
|
||||
|
|
|
|||
260
src/demo/parser/gamestateanalyser.rs
Normal file
260
src/demo/parser/gamestateanalyser.rs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
use crate::demo::gameevent_gen::GameEventType::PlayerSappedObject;
|
||||
use crate::demo::message::packetentities::{EntityId, PacketEntity};
|
||||
use crate::demo::message::Message;
|
||||
use crate::demo::packet::datatable::{ParseSendTable, SendTableName, ServerClass, ServerClassName};
|
||||
use crate::demo::parser::analyser::{Class, Team, UserId};
|
||||
use crate::demo::parser::MessageHandler;
|
||||
use crate::demo::vector::{Vector, VectorXY};
|
||||
use crate::{MessageType, ParserState};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub struct CachedEntities {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
pub enum PlayerState {
|
||||
Alive = 0,
|
||||
Dying = 1,
|
||||
Death = 2,
|
||||
Respawnable = 3,
|
||||
}
|
||||
|
||||
impl PlayerState {
|
||||
pub fn new(number: i64) -> Self {
|
||||
match number {
|
||||
1 => PlayerState::Dying,
|
||||
2 => PlayerState::Death,
|
||||
3 => PlayerState::Respawnable,
|
||||
_ => PlayerState::Alive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Player {
|
||||
entity: EntityId,
|
||||
position: Vector,
|
||||
health: u16,
|
||||
max_health: u16,
|
||||
class: Class,
|
||||
team: Team,
|
||||
view_angle: f32,
|
||||
state: PlayerState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Building {
|
||||
Sentry {
|
||||
builder: UserId,
|
||||
position: Vector,
|
||||
level: u8,
|
||||
max_health: u16,
|
||||
health: u16,
|
||||
building: bool,
|
||||
sapped: bool,
|
||||
team: Team,
|
||||
angle: f32,
|
||||
player_controller: bool,
|
||||
auto_aim_target: UserId,
|
||||
shells: u16,
|
||||
rockets: u16,
|
||||
is_mini: bool,
|
||||
},
|
||||
Dispenser {
|
||||
builder: UserId,
|
||||
position: Vector,
|
||||
level: u8,
|
||||
max_health: u16,
|
||||
health: u16,
|
||||
building: bool,
|
||||
sapped: bool,
|
||||
team: Team,
|
||||
angle: f32,
|
||||
healing: Vec<UserId>,
|
||||
metal: u16,
|
||||
},
|
||||
Teleporter {
|
||||
builder: UserId,
|
||||
position: Vector,
|
||||
level: u8,
|
||||
max_health: u16,
|
||||
health: u16,
|
||||
building: bool,
|
||||
sapped: bool,
|
||||
team: Team,
|
||||
angle: f32,
|
||||
is_entrance: bool,
|
||||
other_end: EntityId,
|
||||
recharge_time: f32,
|
||||
recharge_duration: f32,
|
||||
times_used: u16,
|
||||
yaw_to_exit: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GameState {
|
||||
players: Vec<Player>,
|
||||
buildings: Vec<Building>,
|
||||
}
|
||||
|
||||
impl GameState {
|
||||
pub fn get_or_create_player(&mut self, entity_id: EntityId) -> &mut Player {
|
||||
let index = match self
|
||||
.players
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find(|(index, player)| player.entity == entity_id)
|
||||
.map(|(index, _)| index)
|
||||
{
|
||||
Some(index) => index,
|
||||
None => {
|
||||
let player = Player {
|
||||
entity: entity_id,
|
||||
position: Vector::default(),
|
||||
health: 0,
|
||||
max_health: 0,
|
||||
class: Class::Other,
|
||||
team: Team::Other,
|
||||
view_angle: 0.0,
|
||||
state: PlayerState::Alive,
|
||||
};
|
||||
|
||||
let index = self.players.len();
|
||||
self.players.push(player);
|
||||
index
|
||||
}
|
||||
};
|
||||
|
||||
&mut self.players[index]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct GameStateAnalyser {
|
||||
pub state: GameState,
|
||||
class_names: Vec<ServerClassName>, // indexed by ClassId
|
||||
}
|
||||
|
||||
impl MessageHandler for GameStateAnalyser {
|
||||
type Output = GameState;
|
||||
|
||||
fn does_handle(message_type: MessageType) -> bool {
|
||||
match message_type {
|
||||
MessageType::PacketEntities => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_message(&mut self, message: &Message, tick: u32) {
|
||||
match message {
|
||||
Message::PacketEntities(message) => {
|
||||
for entity in &message.entities {
|
||||
self.handle_entity(entity);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_data_tables(&mut self, tables: &[ParseSendTable], server_classes: &[ServerClass]) {
|
||||
self.class_names = server_classes
|
||||
.iter()
|
||||
.map(|class| &class.name)
|
||||
.cloned()
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn get_output(self, state: ParserState) -> Self::Output {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
impl GameStateAnalyser {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn handle_entity(&mut self, entity: &PacketEntity) {
|
||||
let class_name: &str = self
|
||||
.class_names
|
||||
.get(usize::from(entity.server_class))
|
||||
.map(|class_name| class_name.as_str())
|
||||
.unwrap_or("");
|
||||
match class_name {
|
||||
"CTFPlayer" => self.handle_player_entity(entity),
|
||||
"CTFPlayerResource" => self.handle_player_resource(entity),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_player_resource(&mut self, entity: &PacketEntity) {
|
||||
for prop in &entity.props {
|
||||
if let Ok(player_id) = u32::from_str(prop.definition.name.as_str()) {
|
||||
let entity_id = EntityId::from(player_id);
|
||||
if let Some(player) = self
|
||||
.state
|
||||
.players
|
||||
.iter_mut()
|
||||
.find(|player| player.entity == entity_id)
|
||||
{
|
||||
match prop.definition.owner_table.as_str() {
|
||||
"m_iTeam" => {
|
||||
player.team =
|
||||
Team::new(i64::try_from(&prop.value).unwrap_or_default() as u16)
|
||||
}
|
||||
"m_iMaxHealth" => {
|
||||
player.max_health =
|
||||
i64::try_from(&prop.value).unwrap_or_default() as u16
|
||||
}
|
||||
"m_iPlayerClass" => {
|
||||
player.class =
|
||||
Class::new(i64::try_from(&prop.value).unwrap_or_default() as u16)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_player_entity(&mut self, entity: &PacketEntity) {
|
||||
let player = self.state.get_or_create_player(entity.entity_index);
|
||||
|
||||
for prop in &entity.props {
|
||||
match prop.definition.owner_table.as_str() {
|
||||
"DT_BasePlayer" => match prop.definition.name.as_str() {
|
||||
"m_iHealth" => {
|
||||
player.health = i64::try_from(&prop.value).unwrap_or_default() as u16
|
||||
}
|
||||
"m_iMaxHealth" => {
|
||||
player.max_health = i64::try_from(&prop.value).unwrap_or_default() as u16
|
||||
}
|
||||
"m_lifeState" => {
|
||||
player.state =
|
||||
PlayerState::new(i64::try_from(&prop.value).unwrap_or_default())
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
"DT_TFLocalPlayerExclusive" | "DT_TFNonLocalPlayerExclusive" => {
|
||||
match prop.definition.name.as_str() {
|
||||
"m_vecOrigin" => {
|
||||
let pos_xy = VectorXY::try_from(&prop.value).unwrap_or_default();
|
||||
player.position.x = pos_xy.x;
|
||||
player.position.y = pos_xy.y;
|
||||
}
|
||||
"m_vecOrigin[2]" => {
|
||||
player.position.z = f32::try_from(&prop.value).unwrap_or_default()
|
||||
}
|
||||
"m_angEyeAngles[1]" => {
|
||||
player.view_angle = f32::try_from(&prop.value).unwrap_or_default()
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ pub trait MessageHandler {
|
|||
|
||||
fn handle_string_entry(&mut self, table: &String, index: usize, entries: &StringTableEntry) {}
|
||||
|
||||
fn handle_data_tables(&mut self, tables: &[ParseSendTable]) {}
|
||||
fn handle_data_tables(&mut self, tables: &[ParseSendTable], server_classes: &[ServerClass]) {}
|
||||
|
||||
fn get_output(self, state: ParserState) -> Self::Output;
|
||||
}
|
||||
|
|
@ -114,7 +114,8 @@ impl<T: MessageHandler> DemoHandler<T> {
|
|||
send_tables: Vec<ParseSendTable>,
|
||||
server_classes: Vec<ServerClass>,
|
||||
) {
|
||||
self.analyser.handle_data_tables(&send_tables);
|
||||
self.analyser
|
||||
.handle_data_tables(&send_tables, &server_classes);
|
||||
self.state_handler
|
||||
.handle_data_table(send_tables, server_classes);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use err_derive::Error;
|
|||
|
||||
mod analyser;
|
||||
mod error;
|
||||
pub mod gamestateanalyser;
|
||||
mod handler;
|
||||
mod messagetypeanalyser;
|
||||
mod state;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::demo::message::stringtable::log_base2;
|
|||
use crate::demo::packet::datatable::SendTableName;
|
||||
use crate::demo::parser::MalformedSendPropDefinitionError;
|
||||
use parse_display::Display;
|
||||
use serde::export::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -20,6 +21,12 @@ use std::rc::Rc;
|
|||
)]
|
||||
pub struct SendPropName(Rc<String>);
|
||||
|
||||
impl SendPropName {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for SendPropName {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
self.0.as_str() == *other
|
||||
|
|
@ -528,6 +535,66 @@ impl From<Vec<SendPropValue>> for SendPropValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&SendPropValue> for i64 {
|
||||
type Error = ();
|
||||
fn try_from(value: &SendPropValue) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
SendPropValue::Integer(val) => Ok(*val),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&SendPropValue> for Vector {
|
||||
type Error = ();
|
||||
fn try_from(value: &SendPropValue) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
SendPropValue::Vector(val) => Ok(*val),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&SendPropValue> for VectorXY {
|
||||
type Error = ();
|
||||
fn try_from(value: &SendPropValue) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
SendPropValue::VectorXY(val) => Ok(*val),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&SendPropValue> for f32 {
|
||||
type Error = ();
|
||||
fn try_from(value: &SendPropValue) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
SendPropValue::Float(val) => Ok(*val),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a SendPropValue> for &'a str {
|
||||
type Error = ();
|
||||
fn try_from(value: &'a SendPropValue) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
SendPropValue::String(val) => Ok(val.as_str()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a SendPropValue> for &'a [SendPropValue] {
|
||||
type Error = ();
|
||||
fn try_from(value: &'a SendPropValue) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
SendPropValue::Array(val) => Ok(val.as_slice()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct SendPropDefinitionIndex(u32);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue