### Run Haste Examples Source: https://github.com/blukai/haste/blob/main/readme.md Navigate to the haste directory and run examples using `cargo run --example -- `. Replace `` with the desired example (e.g., deadlock-position) and `` with the path to your replay file. ```console $ cargo run --example -- ``` -------------------------------- ### Run Bundled Examples: Entity Life-State Tracking Source: https://context7.com/blukai/haste/llms.txt Example for entity life-state tracking. Requires the `preserve-metadata` feature. ```console # entity life-state tracking (requires --features preserve-metadata) cargo run --example lifestate --features preserve-metadata -- path/to/match.dem ``` -------------------------------- ### Run Bundled Examples: Download Live Broadcast Source: https://context7.com/blukai/haste/llms.txt Example for downloading a live broadcast to a file. Requires the `broadcast` feature. ```console # download a live broadcast to a file (requires --features broadcast) cargo run --bin broadcast --features broadcast -- download --url http://dist1-ord1.steamcontent.com/tv/18895867 --output out.bin ``` -------------------------------- ### Run Bundled Examples: Player Positions Source: https://context7.com/blukai/haste/llms.txt Example for player positions in Deadlock. Requires the `deadlock` feature. ```console # player positions (Deadlock, requires --features deadlock) cargo run --example deadlock-position --features deadlock -- path/to/match.dem ``` -------------------------------- ### Run Bundled Examples: Random Seek Benchmark Source: https://context7.com/blukai/haste/llms.txt Example for a random seek benchmark. No extra features are needed. ```console # random seek benchmark (no extra features needed) cargo run --example seek -- path/to/match.dem ``` -------------------------------- ### Run Bundled Examples: All-Chat Messages Source: https://context7.com/blukai/haste/llms.txt Example for all-chat messages in Dota 2. Requires the `dota2` feature. ```console # all-chat messages (Dota 2, requires --features dota2) cargo run --example dota2-allchat --features dota2 -- path/to/match.dem ``` -------------------------------- ### Run Bundled Examples: Game Time Computation Source: https://context7.com/blukai/haste/llms.txt Example for game time computation in Deadlock. Requires the `deadlock` feature. ```console # game time computation (Deadlock, requires --features deadlock) cargo run --example deadlock-gametime --features deadlock -- path/to/match.dem ``` -------------------------------- ### Adding Haste as a Git Dependency Source: https://context7.com/blukai/haste/llms.txt Instructions on how to add the Haste library to your project's Cargo.toml file, with examples for different feature sets. ```APIDOC ## Cargo.toml dependency Add haste as a git dependency (it is not published to crates.io yet). ```toml [dependencies] # minimal – file parsing only haste = { git = "https://github.com/blukai/haste.git" } # with HTTP TV-broadcast support haste = { git = "https://github.com/blukai/haste.git", features = ["broadcast"] } # Deadlock-specific helpers (coord_from_cell, etc.) haste = { git = "https://github.com/blukai/haste.git", features = ["deadlock"] } # Dota 2-specific helpers haste = { git = "https://github.com/blukai/haste.git", features = ["dota2"] } # Compile protoc from source (no system protoc required) haste = { git = "https://github.com/blukai/haste.git", features = ["protobuf-src"] } ``` ``` -------------------------------- ### Iterate and Look Up Live Entities Source: https://context7.com/blukai/haste/llms.txt Access live entities through `ctx.entities()`. Use `get(index)` for direct lookup, `iter()` to traverse all entities, and `is_ehandle_valid`/`ehandle_to_index` to convert entity handles to indices. ```rust use haste::entities::{EntityContainer, ehandle_to_index, fkey_from_path, is_ehandle_valid}; use haste::parser::Context; use haste::fxhash; const PLAYER_PAWN: u64 = fxhash::hash_bytes(b"CCitadelPlayerPawn"); fn print_all_players(ctx: &Context) { let Some(entities) = ctx.entities() else { return }; for (_index, entity) in entities.iter() { if !entity.serializer_name_heq(PLAYER_PAWN) { continue; } // resolve an ehandle field to another entity const OWNER_HANDLE: u64 = fkey_from_path(&["m_hOwnerEntity"]); if let Some(handle): Option = entity.get_value(&OWNER_HANDLE) { if is_ehandle_valid(handle) { let owner_index = ehandle_to_index(handle); if let Some(owner) = entities.get(&owner_index) { println!("entity #{} owned by #{}", entity.index(), owner.index()); } } } } } ``` -------------------------------- ### `EntityContainer` methods Source: https://context7.com/blukai/haste/llms.txt The `EntityContainer` provides methods to iterate over and look up live entities. It offers `get(index)`, `iter()`, and baseline equivalents. `is_ehandle_valid` and `ehandle_to_index` are utility functions for converting networked entity handles to slot indices. ```APIDOC ## `EntityContainer` — iterate and look up live entities Accessible via `ctx.entities()` inside any visitor callback. Provides `get(index)`, `iter()` over all live entities, and baseline equivalents. `is_ehandle_valid` and `ehandle_to_index` convert networked entity handles to slot indices. ``` -------------------------------- ### Open and Read Demo File Info Source: https://context7.com/blukai/haste/llms.txt Use `DemoFile::start_reading` to open a `.dem` file from any `Read + Seek` reader. Buffering with `BufReader` is recommended for performance. The `file_info()` method retrieves header information like total ticks and playback time. ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::demostream::DemoStream; fn main() -> anyhow::Result<()> { let file = File::open("match.dem")?; let buf_reader = BufReader::new(file); // buffering is important for perf let mut demo_file = DemoFile::start_reading(buf_reader)?; // read the file-info header (seeks to fileinfo_offset, then seeks back) let file_info = demo_file.file_info()?; println!("total ticks : {}", file_info.playback_ticks()); println!("total time : {}s", file_info.playback_time()); // expected output: // total ticks : 111840 // total time : 1864.0 Ok(()) } ``` -------------------------------- ### DemoFile::start_reading Source: https://context7.com/blukai/haste/llms.txt Opens a `.dem` file for parsing by wrapping a reader and validating the magic header. It provides access to file information like total ticks and playback time. ```APIDOC ## `DemoFile::start_reading` — open a `.dem` file for parsing `DemoFile` wraps any `Read + Seek` reader (use `BufReader` for best performance) and validates the `PBDEMS2` magic header. It implements the `DemoStream` trait so it can be passed directly to `Parser`. The `file_info()` method lazily reads `CDemoFileInfo` (including total tick count) from the file's offset table. ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::demostream::DemoStream; fn main() -> anyhow::Result<()> { let file = File::open("match.dem")?; let buf_reader = BufReader::new(file); // buffering is important for perf let mut demo_file = DemoFile::start_reading(buf_reader)?; // read the file-info header (seeks to fileinfo_offset, then seeks back) let file_info = demo_file.file_info()?; println!("total ticks : {}", file_info.playback_ticks()); println!("total time : {}s", file_info.playback_time()); // expected output: // total ticks : 111840 // total time : 1864.0 Ok(()) } ``` ``` -------------------------------- ### Create Parser with Custom Visitor Source: https://context7.com/blukai/haste/llms.txt Instantiate a `Parser` with a custom `Visitor` implementation to handle events like entity updates and tick boundaries. The `Parser` drives the demo stream and dispatches events to the visitor. ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::parser::{Context, Parser, Visitor}; use haste::demostream::CmdHeader; use haste::entities::{DeltaHeader, Entity}; use anyhow::Result; struct MyVisitor; impl Visitor for MyVisitor { fn on_tick_end(&mut self, ctx: &Context) -> Result<()> { println!("tick {}", ctx.tick()); Ok(()) } } fn main() -> Result<()> { let file = File::open("match.dem")?; let demo_file = DemoFile::start_reading(BufReader::new(file))?; let mut parser = Parser::from_stream_with_visitor(demo_file, MyVisitor)?; parser.run_to_end() } ``` -------------------------------- ### BroadcastHttp::start_streaming Source: https://context7.com/blukai/haste/llms.txt Consumes a live TV broadcast over HTTP. It drives the state machine against a Valve TV broadcast CDN endpoint and requires manual driving via `next_packet().await` and `parser.run_to_end()`. ```APIDOC ## `BroadcastHttp::start_streaming` — consume a live TV broadcast over HTTP `BroadcastHttp` drives the sync → start → full-frame → delta-frames state machine against a Valve TV broadcast CDN endpoint. Requires an `HttpClient` implementation (the `reqwest` feature provides one). The broadcast must be "driven" manually: call `next_packet().await` in a loop, then call `parser.run_to_end()` after each successful packet. ```rust // Cargo.toml: haste = { ..., features = ["broadcast"] } // haste_broadcast/Cargo.toml: features = ["reqwest", "tokio"] use haste::broadcast::BroadcastHttp; use haste::parser::{Parser, Visitor, Context}; use haste::demostream::CmdHeader; use anyhow::Result; use std::time::Duration; struct MyVisitor; impl Visitor for MyVisitor { fn on_cmd(&mut self, _ctx: &Context, h: &CmdHeader, _data: &[u8]) -> Result<()> { eprintln!("cmd {:?} tick={}", h.cmd, h.tick); Ok(()) } } #[tokio::main] async fn main() -> Result<()> { let base_url = "http://dist1-ord1.steamcontent.com/tv/18895867"; let http_client = reqwest::Client::builder() .timeout(Duration::from_secs(3)) .build()?; let demo_stream = BroadcastHttp::start_streaming(http_client, base_url).await?; let sync = demo_stream.sync_response(); println!("map={} fragment={} tps={}", sync.map, sync.fragment, sync.tps); let mut parser = Parser::from_stream_with_visitor(demo_stream, MyVisitor)?; loop { let demo_stream = parser.demo_stream_mut(); match demo_stream.next_packet().await { Some(Ok(_)) => parser.run_to_end()?, Some(Err(e)) => return Err(e.into()), None => break, // broadcast ended / 404 } } Ok(()) } ``` ``` -------------------------------- ### Consume Live TV Broadcast with BroadcastHttp Source: https://context7.com/blukai/haste/llms.txt Drives the sync state machine against a Valve TV broadcast CDN endpoint. Requires an `HttpClient` implementation and manual driving via `next_packet().await` and `parser.run_to_end()`. ```rust // Cargo.toml: haste = { ..., features = ["broadcast"] } // haste_broadcast/Cargo.toml: features = ["reqwest", "tokio"] use haste::broadcast::BroadcastHttp; use haste::parser::{Parser, Visitor, Context}; use haste::demostream::CmdHeader; use anyhow::Result; use std::time::Duration; struct MyVisitor; impl Visitor for MyVisitor { fn on_cmd(&mut self, _ctx: &Context, h: &CmdHeader, _data: &[u8]) -> Result<()> { eprintln!("cmd {:?} tick={}", h.cmd, h.tick); Ok(()) } } #[tokio::main] async fn main() -> Result<()> { let base_url = "http://dist1-ord1.steamcontent.com/tv/18895867"; let http_client = reqwest::Client::builder() .timeout(Duration::from_secs(3)) .build()?; let demo_stream = BroadcastHttp::start_streaming(http_client, base_url).await?; let sync = demo_stream.sync_response(); println!("map={} fragment={} tps={}", sync.map, sync.fragment, sync.tps); let mut parser = Parser::from_stream_with_visitor(demo_stream, MyVisitor)?; loop { let demo_stream = parser.demo_stream_mut(); match demo_stream.next_packet().await { Some(Ok(_)) => parser.run_to_end()?, Some(Err(e)) => return Err(e.into()), None => break, // broadcast ended / 404 } } Ok(()) } ``` -------------------------------- ### Build Valve-Replica HTTP Headers Source: https://context7.com/blukai/haste/llms.txt Replicates headers sent by Steam client for TV broadcasts, potentially required by CDNs. Returns an `http::HeaderMap`. ```rust use haste::broadcast::default_headers; fn build_reqwest_client(app_id: u32) -> anyhow::Result { let hmap = default_headers(app_id)?; // app_id 1422450 = Deadlock, 570 = Dota 2 let mut rbuilder = reqwest::Client::builder(); for (name, value) in &hmap { rbuilder = rbuilder.default_headers({ let mut m = reqwest::header::HeaderMap::new(); m.insert(name.clone(), value.clone()); m }); } Ok(rbuilder.build()?) } // resulting User-Agent: "Valve/Steam HTTP Client 1.0 (1422450)" ``` -------------------------------- ### Parser::from_stream_with_visitor Source: https://context7.com/blukai/haste/llms.txt Creates a parser that drives the demo stream and dispatches events to a custom `Visitor` implementation. This allows for handling specific events like tick boundaries. ```APIDOC ## `Parser::from_stream_with_visitor` — create a parser with a custom visitor `Parser` drives the demo stream and dispatches events to `V: Visitor`. Pass a `DemoFile` (or any `DemoStream`) plus a `Visitor` implementation. For quick dumps without any callbacks use `Parser::from_stream` which installs a no-op visitor. ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::parser::{Context, Parser, Visitor}; use haste::demostream::CmdHeader; use haste::entities::{DeltaHeader, Entity}; use anyhow::Result; struct MyVisitor; impl Visitor for MyVisitor { fn on_tick_end(&mut self, ctx: &Context) -> Result<()> { println!("tick {}", ctx.tick()); Ok(()) } } fn main() -> Result<()> { let file = File::open("match.dem")?; let demo_file = DemoFile::start_reading(BufReader::new(file))?; let mut parser = Parser::from_stream_with_visitor(demo_file, MyVisitor)?; parser.run_to_end() } ``` ``` -------------------------------- ### default_headers Source: https://context7.com/blukai/haste/llms.txt Builds Valve-replica HTTP headers for broadcast requests. These headers may be required by some CDN configurations as they replicate those sent by Valve's Steam client. ```APIDOC ## `default_headers` — build Valve-replica HTTP headers for broadcast requests Returns an `http::HeaderMap` that replicates the headers Valve's own Steam client sends when connecting to a TV broadcast, which may be required by some CDN configurations. ```rust use haste::broadcast::default_headers; fn build_reqwest_client(app_id: u32) -> anyhow::Result { let hmap = default_headers(app_id)?; // app_id 1422450 = Deadlock, 570 = Dota 2 let mut rbuilder = reqwest::Client::builder(); for (name, value) in &hmap { rbuilder = rbuilder.default_headers({ let mut m = reqwest::header::HeaderMap::new(); m.insert(name.clone(), value.clone()); m }); } Ok(rbuilder.build()?) } // resulting User-Agent: "Valve/Steam HTTP Client 1.0 (1422450)" ``` ``` -------------------------------- ### Parse Saved TV Broadcast Files Source: https://context7.com/blukai/haste/llms.txt Use BroadcastFile::start_reading to parse Valve's GOTV/TV broadcast files, which use a concatenated fragment format. This allows treating broadcast files like regular demo files by implementing the DemoStream trait. ```rust // Cargo.toml: haste = { ..., features = ["broadcast"] } use std::fs::File; use std::io::BufReader; use haste::broadcast::BroadcastFile; use haste::parser::{Parser, NopVisitor}; fn main() -> anyhow::Result<()> { let file = File::open("recorded_broadcast.bin")?; let buf_reader = BufReader::new(file); // no header magic check — starts at byte 0 let broadcast = BroadcastFile::start_reading(buf_reader); let mut parser = Parser::from_stream(broadcast)?; parser.run_to_end()?; println!("broadcast parsed"); Ok(()) } ``` -------------------------------- ### Parse Entire Replay with NopVisitor Source: https://context7.com/blukai/haste/llms.txt Use `Parser::from_stream` with `NopVisitor` to parse a replay without any callbacks, which is useful for benchmarking. The `run_to_end()` method processes the entire stream until EOF. ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::parser::{Parser, NopVisitor}; fn main() -> anyhow::Result<()> { let demo_file = DemoFile::start_reading(BufReader::new(File::open("match.dem")?))?; // NopVisitor: parse without any callbacks, useful for benchmarking let mut parser = Parser::from_stream(demo_file)?; parser.run_to_end()?; // returns Ok(()) on clean EOF println!("done"); Ok(()) } ``` -------------------------------- ### Generate Compile-Time Entity Field Keys Source: https://context7.com/blukai/haste/llms.txt Generates a `u64` hash key from a dot-style field path at compile time using `fkey_from_path`. This allows for zero-cost runtime access to typed field values via `Entity::get_value` and `Entity::try_get_value`. ```rust use haste::entities::{fkey_from_path, Entity}; // keys computed at compile time — zero cost at runtime const LIFE_STATE: u64 = fkey_from_path(&["m_lifeState"]); const HERO_CELL_X: u64 = fkey_from_path(&["CBodyComponent", "m_skeletonInstance", "m_vecOrigin", "m_cellX"]); const HERO_VEC_X: u64 = fkey_from_path(&["CBodyComponent", "m_skeletonInstance", "m_vecOrigin", "m_vecX"]); const GAME_START: u64 = fkey_from_path(&["m_pGameRules", "m_flGameStartTime"]); const GAME_PAUSED: u64 = fkey_from_path(&["m_pGameRules", "m_bGamePaused"]); fn read_fields(entity: &Entity) { // get_value returns None if the field is absent or the type doesn't match let life: Option = entity.get_value(&LIFE_STATE); let cell_x: Option = entity.get_value(&HERO_CELL_X); let vec_x: Option = entity.get_value(&HERO_VEC_X); // try_get_value returns a Result distinguishing "field missing" from "wrong type" let start_time: Result = entity.try_get_value(&GAME_START); let paused: Result = entity.try_get_value(&GAME_PAUSED); println!("life={:?} cell_x={:?} vec_x={:?}", life, cell_x, vec_x); println!("game_start={:?} paused={:?}", start_time, paused); } ``` -------------------------------- ### Implement Visitor Trait for Parser Callbacks Source: https://context7.com/blukai/haste/llms.txt Implement the `Visitor` trait to receive callbacks for demo events like commands, packets, entity updates, and tick endings. Default implementations are no-ops. Requires importing necessary types from `haste`. ```rust use haste::demostream::CmdHeader; use haste::entities::{DeltaHeader, Entity}; use haste::parser::{Context, Visitor}; use haste::valveprotos::common::{EDemoCommands, NetMessages, CnetMsgTick}; use haste::valveprotos::prost::Message; use anyhow::Result; struct DebugVisitor; impl Visitor for DebugVisitor { // called for every raw demo command (before it is handled internally) fn on_cmd(&mut self, ctx: &Context, cmd_header: &CmdHeader, _data: &[u8]) -> Result<()> { if cmd_header.cmd == EDemoCommands::DemSyncTick { println!("sync tick – tick_interval = {}", ctx.tick_interval()); } Ok(()) } // called for every inner network packet inside DemPacket / DemSignonPacket fn on_packet(&mut self, _ctx: &Context, packet_type: u32, data: &[u8]) -> Result<()> { if packet_type == NetMessages::NetTick as u32 { let msg = CnetMsgTick::decode(data)?; eprintln!("NetTick: {:?}", msg.tick); } Ok(()) } // called after every entity create / update / delete fn on_entity(&mut self, ctx: &Context, delta: DeltaHeader, entity: &Entity) -> Result<()> { match delta { DeltaHeader::CREATE => println!("tick {}: entity #{} created ({})", ctx.tick(), entity.index(), entity.serializer().serializer_name.str), DeltaHeader::DELETE => println!("tick {}: entity #{} deleted", ctx.tick(), entity.index()), _ => {} } Ok(()) } // called once per tick after all entity updates for that tick are processed fn on_tick_end(&mut self, ctx: &Context) -> Result<()> { if ctx.tick() % 1800 == 0 { println!("--- tick {} ---", ctx.tick()); } Ok(()) } } ``` -------------------------------- ### `Visitor` Trait Source: https://context7.com/blukai/haste/llms.txt Implement the `Visitor` trait to receive callbacks from the parser for various demo events. The trait has four optional methods: `on_cmd`, `on_packet`, `on_entity`, and `on_tick_end`, all of which receive a `&Context`. ```APIDOC ## `Visitor` Trait ### Description Receive callbacks from the parser for various demo events. The `Visitor` trait has four optional methods; default implementations are no-ops. All methods receive `&Context` which exposes `tick()`, `tick_interval()`, `entities()`, `string_tables()`, `serializers()`, and `entity_classes()`. ### Methods * **`on_cmd(&mut self, ctx: &Context, cmd_header: &CmdHeader, data: &[u8]) -> Result<()>`**: Called for every raw demo command before it is handled internally. * **`on_packet(&mut self, ctx: &Context, packet_type: u32, data: &[u8]) -> Result<()>`**: Called for every inner network packet inside `DemPacket` / `DemSignonPacket`. * **`on_entity(&mut self, ctx: &Context, delta: DeltaHeader, entity: &Entity) -> Result<()>`**: Called after every entity create, update, or delete. * **`on_tick_end(&mut self, ctx: &Context) -> Result<()>`**: Called once per tick after all entity updates for that tick are processed. ### Example Implementation ```rust use haste::demostream::CmdHeader; use haste::entities::{DeltaHeader, Entity}; use haste::parser::{Context, Visitor}; use haste::valveprotos::common::{EDemoCommands, NetMessages, CnetMsgTick}; use haste::valveprotos::prost::Message; use anyhow::Result; struct DebugVisitor; impl Visitor for DebugVisitor { fn on_cmd(&mut self, ctx: &Context, cmd_header: &CmdHeader, _data: &[u8]) -> Result<()> { if cmd_header.cmd == EDemoCommands::DemSyncTick { println!("sync tick – tick_interval = {}", ctx.tick_interval()); } Ok(()) } fn on_packet(&mut self, _ctx: &Context, packet_type: u32, data: &[u8]) -> Result<()> { if packet_type == NetMessages::NetTick as u32 { let msg = CnetMsgTick::decode(data)?; eprintln!("NetTick: {:?}", msg.tick); } Ok(()) } fn on_entity(&mut self, ctx: &Context, delta: DeltaHeader, entity: &Entity) -> Result<()> { match delta { DeltaHeader::CREATE => println!("tick {}: entity #{} created ({})", ctx.tick(), entity.index(), entity.serializer().serializer_name.str), DeltaHeader::DELETE => println!("tick {}: entity #{} deleted", ctx.tick(), entity.index()), _ => {} } Ok(()) } fn on_tick_end(&mut self, ctx: &Context) -> Result<()> { if ctx.tick() % 1800 == 0 { println!("--- tick {} ---", ctx.tick()); } Ok(()) } } ``` ``` -------------------------------- ### Add Haste as a Git Dependency Source: https://github.com/blukai/haste/blob/main/readme.md To use Haste in your project, add it as a git dependency in your Cargo.toml file. Ensure you have `protoc` in your PATH or `$PROTOC` environment variable, or enable the `protobuf-src` feature flag. ```toml [dependencies] haste = { git = "https://github.com/blukai/haste.git" } ``` -------------------------------- ### Seek to Specific Tick in Demo File Source: https://context7.com/blukai/haste/llms.txt Resets parser state and fast-forwards to a target tick. Useful for replay scrubbing or state extraction at a specific moment. Requires opening a demo file and initializing the parser. ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::demostream::DemoStream; use haste::parser::Parser; use rand::Rng; fn main() -> anyhow::Result<()> { let file = File::open("match.dem")?; let demo_file = DemoFile::start_reading(BufReader::new(file))?; let mut parser = Parser::from_stream(demo_file)?; let total = parser.demo_stream_mut().total_ticks()?; let mut rng = rand::thread_rng(); // random seek — illustrates the seek API let target = rng.gen_range(0..total); println!("seeking to tick {}/{}", target, total); parser.run_to_tick(target)?; println!("current tick after seek: {}", parser.context().tick()); // expected output: // seeking to tick 54321/111840 // current tick after seek: 54321 Ok(()) } ``` -------------------------------- ### Add Haste as a Git Dependency Source: https://context7.com/blukai/haste/llms.txt Include haste in your Cargo.toml file by specifying the git repository. Features like 'broadcast', 'deadlock', 'dota2', and 'protobuf-src' can be enabled as needed. ```toml [dependencies] # minimal – file parsing only haste = { git = "https://github.com/blukai/haste.git" } # with HTTP TV-broadcast support haste = { git = "https://github.com/blukai/haste.git", features = ["broadcast"] } # Deadlock-specific helpers (coord_from_cell, etc.) haste = { git = "https://github.com/blukai/haste.git", features = ["deadlock"] } # Dota 2-specific helpers haste = { git = "https://github.com/blukai/haste.git", features = ["dota2"] } # Compile protoc from source (no system protoc required) haste = { git = "https://github.com/blukai/haste.git", features = ["protobuf-src"] } ``` -------------------------------- ### `Parser::run_to_tick` Source: https://context7.com/blukai/haste/llms.txt Resets the parser state and fast-forwards through demo snapshots until a target tick is reached. This is useful for building replay scrubbers or extracting state at a specific moment in the demo. ```APIDOC ## `Parser::run_to_tick` ### Description Resets the parser state, fast-forwards through `DemFullPacket` snapshots, and then replays commands tick-by-tick until `target_tick` is reached. Useful for building replay scrubbers or extracting state at a particular moment. ### Method Signature `fn run_to_tick(&mut self, target_tick: u32) -> Result<()>` ### Parameters * **target_tick** (u32) - The tick to seek to. ### Request Example ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::demostream::DemoStream; use haste::parser::Parser; let file = File::open("match.dem")?; let demo_file = DemoFile::start_reading(BufReader::new(file))?; let mut parser = Parser::from_stream(demo_file)?; let total = parser.demo_stream_mut().total_ticks()?; let target = 54321; // Example target tick parser.run_to_tick(target)?; println!("Current tick after seek: {}", parser.context().tick()); ``` ### Response * **Result<()>** - Returns Ok(()) on success, or an error if seeking fails. ``` -------------------------------- ### Intercept Raw Network Messages with on_packet Source: https://context7.com/blukai/haste/llms.txt Implement the on_packet visitor method to intercept raw network messages. Decode specific user messages, like Dota 2 chat messages, using prost::Message::decode. Ensure the 'dota2' feature is enabled for Dota 2 specific messages. ```rust // Cargo.toml: haste = { ..., features = ["dota2"] } use haste::parser::{Context, Visitor}; use haste::valveprotos::dota2::{CdotaUserMsgChatMessage, EDotaUserMessages}; use haste::valveprotos::prost::Message; use anyhow::Result; struct ChatPrinter; impl Visitor for ChatPrinter { fn on_packet(&mut self, _ctx: &Context, packet_type: u32, data: &[u8]) -> Result<()> { if packet_type == EDotaUserMessages::DotaUmChatMessage as u32 { let msg = CdotaUserMsgChatMessage::decode(data)?; // CdotaUserMsgChatMessage fields: message_source, channel_type, // player_id, message_text (string), etc. eprintln!("[chat] {:?}" msg); // example output: // [chat] CdotaUserMsgChatMessage { channel_type: Some(0), player_id: Some(3), // message_source: Some(0), message_text: Some("gg wp") } } Ok(()) } } ``` -------------------------------- ### Parse Downloaded Broadcast File Source: https://context7.com/blukai/haste/llms.txt Command to parse a downloaded broadcast file. Requires the 'broadcast' feature to be enabled. ```bash cargo run --bin broadcast --features broadcast -- parse --filepath out.bin ``` -------------------------------- ### Access Parser State with Context in Visitor Source: https://context7.com/blukai/haste/llms.txt Implement the Visitor trait to access parser state like tick, tick interval, and string tables within visitor callbacks. Ensure subsystems like string_tables() and entities() are checked for initialization before use. ```rust use haste::parser::{Context, Visitor}; use anyhow::Result; struct InfoVisitor; impl Visitor for InfoVisitor { fn on_tick_end(&mut self, ctx: &Context) -> Result<()> { let tick = ctx.tick(); // i32, -1 before DemSyncTick let tick_interval = ctx.tick_interval(); // f32 let game_time = tick as f32 * tick_interval; // string_tables / entities / serializers return None until initialized if let Some(tables) = ctx.string_tables() { for table in tables.tables() { let _ = table.name(); // e.g. "EntityNames", "instancebaseline" } } if let Some(entities) = ctx.entities() { let count = entities.iter().count(); println!("tick {tick:>6} t={game_time:.2}s live_entities={count}"); } Ok(()) } } ``` -------------------------------- ### `Entity::get_value` and `Entity::try_get_value` Source: https://context7.com/blukai/haste/llms.txt These methods allow reading typed field values from an entity. `get_value` returns an Option, treating missing fields or type mismatches as None. `try_get_value` returns a Result, distinguishing between missing fields and type conversion errors. ```APIDOC ## `Entity::get_value` / `Entity::try_get_value` — read typed field values `get_value` returns `Option`, treating both missing fields and type mismatches as `None`. `try_get_value` returns `Result`, distinguishing `FieldNotExist` from `FieldValueConversionError`. Supported target types include various integer and float types, booleans, and fixed-size arrays of floats. ``` -------------------------------- ### Parser::run_to_end Source: https://context7.com/blukai/haste/llms.txt Parses the entire replay file from the stream until the end of the file (EOF). It automatically handles various internal commands and calls visitor callbacks for events. ```APIDOC ## `Parser::run_to_end` — parse the entire replay Reads commands from the stream until EOF, calling visitor callbacks for every entity update, packet, and tick-end event encountered. Internally handles `DemPacket`, `DemSignonPacket`, `DemSendTables`, `DemClassInfo`, and `DemFullPacket` commands automatically. ```rust use std::fs::File; use std::io::BufReader; use haste::demofile::DemoFile; use haste::parser::{Parser, NopVisitor}; fn main() -> anyhow::Result<()> { let demo_file = DemoFile::start_reading(BufReader::new(File::open("match.dem")?))?; // NopVisitor: parse without any callbacks, useful for benchmarking let mut parser = Parser::from_stream(demo_file)?; parser.run_to_end()?; // returns Ok(()) on clean EOF println!("done"); Ok(()) } ``` ``` -------------------------------- ### `deadlock_coord_from_cell` and `dota2_coord_from_cell` Source: https://context7.com/blukai/haste/llms.txt These functions reconstruct world-space coordinates from Source 2's cell index and sub-cell float offset. They are available when the `deadlock` or `dota2` feature flags are enabled, respectively. ```APIDOC ## `deadlock_coord_from_cell` / `dota2_coord_from_cell` — reconstruct world coordinates Source 2 encodes world-space positions as a cell index plus a sub-cell float offset. These helpers (gated behind the `deadlock` or `dota2` feature flags respectively) convert the pair back into an absolute world coordinate. ``` -------------------------------- ### Inspect Entity Schema with FlattenedSerializerContainer Source: https://context7.com/blukai/haste/llms.txt Enumerates entity fields without parsing a full replay, available via `ctx.serializers()`. Requires `preserve-metadata` feature for string names. ```rust use haste::parser::{Context, Visitor}; use anyhow::Result; struct SchemaDumper; impl Visitor for SchemaDumper { fn on_tick_end(&mut self, ctx: &Context) -> Result<()> { let Some(serializers) = ctx.serializers() else { return Ok(()) }; for serializer in serializers.values() { // serializer_name.str is available only with the `preserve-metadata` feature // serializer_name.hash is always available for (i, field) in serializer.fields.iter().enumerate() { // field.var_name.hash / field.var_type.hash always available #[cfg(feature = "preserve-metadata")] println!( " {}[{}] {}:வுகளை", serializer.serializer_name.str, i, field.var_name.str, field.var_type.str, ); } } Ok(()) } } ``` -------------------------------- ### Read Typed Field Values from Entities Source: https://context7.com/blukai/haste/llms.txt Use `get_value` for simple reads that treat all errors as `None`. Employ `try_get_value` to distinguish between missing fields and type conversion errors. Supported types include various integers, floats, booleans, and byte arrays. ```rust use haste::entities::{fkey_from_path, Entity, GetValueError}; fn print_entity_fields(entity: &Entity) { // simple read — None on any error const HP: u64 = fkey_from_path(&["m_iHealth"]); if let Some(hp): Option = entity.get_value(&HP) { println!("health = {hp}"); } // strict read — distinguish missing vs wrong type const POS: u64 = fkey_from_path(&["CBodyComponent", "m_vecAbsOrigin"]); match entity.try_get_value::<[f32; 3]>(&POS) { Ok(pos) => println!("pos = {:?}", pos), Err(GetValueError::FieldNotExist) => println!("no position field"), Err(GetValueError::FieldValueConversionError(e)) => println!("type error: {e}"), } // iterate ALL fields on an entity (key hash + FieldValue) for (key, value) in entity.iter() { println!(" {key:#018x} = {value}"); } } ``` -------------------------------- ### Reconstruct World Coordinates from Cell Data Source: https://context7.com/blukai/haste/llms.txt Convert Source 2's cell index and sub-cell float offset into absolute world coordinates. Requires enabling the `deadlock` or `dota2` feature flags. ```rust // Cargo.toml: haste = { ..., features = ["deadlock"] } use haste::entities::{deadlock_coord_from_cell, fkey_from_path, Entity}; fn get_player_position(entity: &Entity) -> Option<[f32; 3]> { const CX: u64 = fkey_from_path(&["CBodyComponent","m_skeletonInstance","m_vecOrigin","m_cellX"]); const CY: u64 = fkey_from_path(&["CBodyComponent","m_skeletonInstance","m_vecOrigin","m_cellY"]); const CZ: u64 = fkey_from_path(&["CBodyComponent","m_skeletonInstance","m_vecOrigin","m_cellZ"]); const VX: u64 = fkey_from_path(&["CBodyComponent","m_skeletonInstance","m_vecOrigin","m_vecX"]); const VY: u64 = fkey_from_path(&["CBodyComponent","m_skeletonInstance","m_vecOrigin","m_vecY"]); const VZ: u64 = fkey_from_path(&["CBodyComponent","m_skeletonInstance","m_vecOrigin","m_vecZ"]); let cell_x: u16 = entity.get_value(&CX)?; let cell_y: u16 = entity.get_value(&CY)?; let cell_z: u16 = entity.get_value(&CZ)?; let vec_x: f32 = entity.get_value(&VX)?; let vec_y: f32 = entity.get_value(&VY)?; let vec_z: f32 = entity.get_value(&VZ)?; Some([ deadlock_coord_from_cell(cell_x, vec_x), deadlock_coord_from_cell(cell_y, vec_y), deadlock_coord_from_cell(cell_z, vec_z), ]) // example output: [1024.5, -2048.25, 320.0] } ``` -------------------------------- ### Retrieve Entity Names from String Tables Source: https://context7.com/blukai/haste/llms.txt Access entity names by querying the 'EntityNames' string table using an entity's 'm_nameStringableIndex'. This function requires the Context and Entity objects, and handles potential errors during string conversion. ```rust use haste::parser::{Context, Visitor}; use haste::entities::{fkey_from_path, Entity}; use anyhow::Result; fn get_entity_name<'a>(entity: &'a Entity, ctx: &'a Context) -> Option<&'a str> { const NAME_IDX_KEY: u64 = fkey_from_path(&["m_pEntity", "m_nameStringableIndex"]); let idx: i32 = entity.get_value(&NAME_IDX_KEY)?; let tables = ctx.string_tables()?; let table = tables.find_table("EntityNames")?; let item = table.get_item(&idx)?; std::str::from_utf8(item.string.as_ref()?).ok() } struct NamePrinter; impl Visitor for NamePrinter { fn on_entity( &mut self, ctx: &Context, _delta: haste::entities::DeltaHeader, entity: &Entity, ) -> Result<()> { if let Some(name) = get_entity_name(entity, ctx) { println!("entity #{}" name, entity.index()); } Ok(()) } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.