diff --git a/Cargo.toml b/Cargo.toml index e7d35fb..decc9c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "democutter" version = "0.1.0" authors = ["Robin Appelman "] edition = "2021" +default-run = "cut" [lib] crate-type = ["cdylib", "rlib"] @@ -11,13 +12,17 @@ crate-type = ["cdylib", "rlib"] name = "cut" path = "src/cut.rs" +[[bin]] +name = "bookmarks" +path = "src/bookmarks.rs" + [features] default = ["console_error_panic_hook"] [dependencies] bitbuffer = "0.10" -tf-demo-parser = { version = "0.4", git = "https://github.com/demostf/parser" } -#tf-demo-parser = { version = "0.4", path = "../tf-demo-parser" } +#tf-demo-parser = { version = "0.4", git = "https://github.com/demostf/parser" } +tf-demo-parser = { version = "0.4", path = "../tf-demo-parser" } wasm-bindgen = "0.2.63" web-sys = { version = "0.3", features = ["console"] } diff --git a/src/bookmarks.rs b/src/bookmarks.rs new file mode 100644 index 0000000..25dd268 --- /dev/null +++ b/src/bookmarks.rs @@ -0,0 +1,17 @@ +use clap::Parser; +use democutter::bookmarks; +use std::fs; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Path to the demo + path: String, +} + +fn main() { + let args = Args::parse(); + let file = fs::read(&args.path).unwrap(); + let output = bookmarks(&file); + println!("{:?}", output); +} diff --git a/src/highlight.rs b/src/highlight.rs new file mode 100644 index 0000000..9e671e9 --- /dev/null +++ b/src/highlight.rs @@ -0,0 +1,137 @@ +use std::collections::{BTreeMap, HashMap}; +use tf_demo_parser::demo::gameevent_gen::{ + GameEvent, RocketJumpEvent, RocketJumpLandedEvent, StickyJumpEvent, StickyJumpLandedEvent, +}; +use tf_demo_parser::demo::message::gameevent::GameEventMessage; +use tf_demo_parser::demo::message::packetentities::PacketEntitiesMessage; +use tf_demo_parser::demo::message::usermessage::UserMessage; +use tf_demo_parser::demo::message::Message; +use tf_demo_parser::demo::packet::stringtable::StringTableEntry; +use tf_demo_parser::demo::parser::analyser::{UserId, UserInfo}; +use tf_demo_parser::demo::parser::MessageHandler; +use tf_demo_parser::demo::sendprop::{SendPropIdentifier, SendPropValue}; +use tf_demo_parser::{MessageType, ParserState, ReadResult, Stream}; + +#[derive(Debug)] +pub struct Highlight { + pub tick: u32, + pub user: UserId, + pub source: HighlightSource, +} + +#[derive(Debug)] +pub enum HighlightSource { + Prec, + AirShot, +} + +#[derive(Default)] +pub struct HighlightAnalyser { + highlights: Vec, + explosive_jumping: HashMap, + users: BTreeMap, +} + +impl HighlightAnalyser { + fn is_explosive_jumping(&self, user: UserId) -> bool { + self.explosive_jumping + .get(&user) + .copied() + .unwrap_or_default() + } + + fn parse_user_info(&mut self, text: Option<&str>, data: Option) -> ReadResult<()> { + if let Some(user_info) = + tf_demo_parser::demo::data::UserInfo::parse_from_string_table(text, data)? + { + self.users + .entry(user_info.player_info.user_id.into()) + .and_modify(|info| { + info.entity_id = user_info.entity_id; + }) + .or_insert_with(|| user_info.into()); + } + + Ok(()) + } +} + +impl MessageHandler for HighlightAnalyser { + type Output = Vec; + + fn does_handle(_message_type: MessageType) -> bool { + true + } + + fn handle_message(&mut self, message: &Message, tick: u32) { + match message { + Message::GameEvent(GameEventMessage { + event: GameEvent::PlayerHurt(hit), + .. + }) if hit.attacker != hit.user_id => { + let user = hit.user_id.into(); + if self.is_explosive_jumping(user) && hit.damage_amount > 50 { + dbg!(hit); + self.highlights.push(Highlight { + tick, + user, + source: HighlightSource::AirShot, + }) + } + } + Message::GameEvent(GameEventMessage { + event: GameEvent::RocketJump(RocketJumpEvent { user_id, .. }), + .. + }) + | Message::GameEvent(GameEventMessage { + event: GameEvent::StickyJump(StickyJumpEvent { user_id, .. }), + .. + }) => { + let user_id = (*user_id).into(); + self.explosive_jumping.insert(user_id, true); + } + Message::GameEvent(GameEventMessage { + event: GameEvent::RocketJumpLanded(RocketJumpLandedEvent { user_id, .. }), + .. + }) + | Message::GameEvent(GameEventMessage { + event: GameEvent::StickyJumpLanded(StickyJumpLandedEvent { user_id, .. }), + .. + }) => { + let user_id = (*user_id).into(); + self.explosive_jumping.insert(user_id, false); + } + Message::GameEvent(GameEventMessage { + event: GameEvent::PlayerSpawn(spawn), + .. + }) => { + let user_id = spawn.user_id.into(); + self.explosive_jumping.insert(user_id, false); + } + Message::UserMessage(UserMessage::SayText2(text)) => { + if text.text == "[P-REC] Bookmark." { + self.highlights.push(Highlight { + tick, + user: text.client, + source: HighlightSource::Prec, + }) + } + } + _ => {} + } + } + + fn handle_string_entry(&mut self, table: &str, _index: usize, entry: &StringTableEntry) { + if table == "userinfo" { + let _ = self.parse_user_info( + entry.text.as_ref().map(|s| s.as_ref()), + entry.extra_data.as_ref().map(|data| data.data.clone()), + ); + } + } + + fn into_output(self, _state: &ParserState) -> Self::Output { + dbg!(self.users); + self.highlights + } +} diff --git a/src/lib.rs b/src/lib.rs index c0d302a..1c5adbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ #![allow(unused_imports)] mod entity; +mod highlight; mod mutate; mod string_tables; mod utils; use crate::entity::ActiveEntities; +use crate::highlight::{Highlight, HighlightAnalyser}; use crate::mutate::{MessageMutator, MutatorList, PacketMutator}; use crate::string_tables::StringTablesUpdates; use crate::utils::set_panic_hook; @@ -16,16 +18,14 @@ use std::convert::TryInto; use std::iter::once; use std::mem::take; use tf_demo_parser::demo::header::Header; -use tf_demo_parser::demo::message::packetentities::UpdateType::Delete; use tf_demo_parser::demo::message::packetentities::{EntityId, PacketEntitiesMessage, UpdateType}; -use tf_demo_parser::demo::message::usermessage::UserMessageType; +use tf_demo_parser::demo::message::usermessage::{UserMessage, UserMessageType}; use tf_demo_parser::demo::message::{Message, NetTickMessage}; use tf_demo_parser::demo::packet::message::{MessagePacket, MessagePacketMeta}; use tf_demo_parser::demo::packet::stop::StopPacket; -use tf_demo_parser::demo::packet::PacketType::StringTables; use tf_demo_parser::demo::packet::{Packet, PacketType}; use tf_demo_parser::demo::parser::{DemoHandler, Encode, NullHandler, RawPacketStream}; -use tf_demo_parser::{Demo, MessageType}; +use tf_demo_parser::{Demo, DemoParser, MessageType}; use wasm_bindgen::prelude::*; use web_sys::console; @@ -282,3 +282,10 @@ fn net_tick(tick: u32) -> Message<'static> { std_dev: 263, }) } + +pub fn bookmarks(input: &[u8]) -> Vec { + let demo = Demo::new(&input); + let parser = DemoParser::new_with_analyser(demo.get_stream(), HighlightAnalyser::default()); + let (_, highlights) = parser.parse().unwrap(); + highlights +}