tf_demo_parser/demo/parser/gamestateanalyser/
mod.rs1mod building;
2mod player;
3mod projectile;
4mod weapon;
5
6pub use crate::demo::data::game_state::{
7 Building, BuildingClass, Dispenser, GameState, Kill, PlayerState, Sentry, Teleporter, World,
8};
9use crate::demo::data::game_state::{Cart, Handle, Objective};
10use crate::demo::data::DemoTick;
11use crate::demo::gameevent_gen::ObjectDestroyedEvent;
12use crate::demo::gamevent::GameEvent;
13use crate::demo::message::gameevent::GameEventMessage;
14use crate::demo::message::packetentities::{EntityId, PacketEntity};
15use crate::demo::message::{Message, UpdateType};
16use crate::demo::packet::datatable::{ParseSendTable, ServerClass, ServerClassName};
17use crate::demo::packet::message::MessagePacketMeta;
18use crate::demo::packet::stringtable::StringTableEntry;
19pub use crate::demo::parser::analyser::{Class, Team, UserId};
20use crate::demo::parser::gamestateanalyser::building::{
21 handle_dispenser_entity, handle_sentry_entity, handle_teleporter_entity,
22};
23use crate::demo::parser::gamestateanalyser::player::{
24 handle_player_entity, handle_player_resource,
25};
26use crate::demo::parser::gamestateanalyser::projectile::handle_projectile_entity;
27use crate::demo::parser::gamestateanalyser::weapon::handle_medigun_entity;
28use crate::demo::parser::handler::BorrowMessageHandler;
29use crate::demo::parser::MessageHandler;
30use crate::demo::sendprop::{SendProp, SendPropIdentifier, SendPropValue};
31use crate::demo::vector::Vector;
32use crate::{MessageType, ParserState, ReadResult, Stream};
33use std::collections::HashMap;
34use std::convert::TryFrom;
35
36pub struct CachedEntities {}
37
38#[derive(Default, Debug)]
39pub struct GameStateAnalyser {
40 pub state: GameState,
41 tick: DemoTick,
42 class_names: Vec<ServerClassName>, outer_map: HashMap<Handle, EntityId>,
44 outer_map_rev: HashMap<EntityId, Handle>,
45}
46
47impl MessageHandler for GameStateAnalyser {
48 type Output = GameState;
49
50 fn does_handle(message_type: MessageType) -> bool {
51 matches!(
52 message_type,
53 MessageType::PacketEntities | MessageType::GameEvent | MessageType::ServerInfo
54 )
55 }
56
57 fn handle_message(&mut self, message: &Message, _tick: DemoTick, parser_state: &ParserState) {
58 match message {
59 Message::PacketEntities(message) => {
60 for entity in &message.entities {
61 self.handle_entity(entity, parser_state);
62 }
63 for id in &message.removed_entities {
64 self.state.projectile_destroy(*id);
65 self.state.remove_building(*id);
66 }
67 }
68 Message::ServerInfo(message) => {
69 self.state.interval_per_tick = message.interval_per_tick
70 }
71 Message::GameEvent(GameEventMessage { event, .. }) => {
72 self.state.events.push((self.tick, event.clone()));
73 match event {
74 GameEvent::PlayerDeath(death) => {
75 self.state.kills.push(Kill::new(self.tick, death.as_ref()))
76 }
77 GameEvent::RoundStart(_) => {
78 self.state.buildings.clear();
79 self.state.projectiles.clear();
80 }
81 GameEvent::TeamPlayRoundStart(_) => {
82 self.state.buildings.clear();
83 self.state.projectiles.clear();
84 }
85 GameEvent::ObjectDestroyed(ObjectDestroyedEvent { index, .. }) => {
86 self.state.remove_building((*index as u32).into());
87 }
88 _ => {}
89 }
90 }
91 _ => {}
92 }
93 }
94
95 fn handle_string_entry(
96 &mut self,
97 table: &str,
98 index: usize,
99 entry: &StringTableEntry,
100 _parser_state: &ParserState,
101 ) {
102 if table == "userinfo" {
103 let _ = self.parse_user_info(
104 index,
105 entry.text.as_ref().map(|s| s.as_ref()),
106 entry.extra_data.as_ref().map(|data| data.data.clone()),
107 );
108 }
109 }
110
111 fn handle_data_tables(
112 &mut self,
113 _parse_tables: &[ParseSendTable],
114 server_classes: &[ServerClass],
115 _parser_state: &ParserState,
116 ) {
117 self.class_names = server_classes
118 .iter()
119 .map(|class| &class.name)
120 .cloned()
121 .collect();
122 }
123
124 fn handle_packet_meta(
125 &mut self,
126 tick: DemoTick,
127 _meta: &MessagePacketMeta,
128 _parser_state: &ParserState,
129 ) {
130 self.state.tick = tick;
131 self.tick = tick;
132 }
133
134 fn into_output(mut self, state: &ParserState) -> Self::Output {
135 self.state.server_classes = state.server_classes.clone();
136 self.state
137 }
138}
139
140impl BorrowMessageHandler for GameStateAnalyser {
141 fn borrow_output(&self, _state: &ParserState) -> &Self::Output {
142 &self.state
143 }
144}
145
146impl GameStateAnalyser {
147 pub fn new() -> Self {
148 Self::default()
149 }
150
151 pub fn handle_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
152 const OUTER: SendPropIdentifier =
153 SendPropIdentifier::new("DT_AttributeContainer", "m_hOuter");
154 const OUTER2: SendPropIdentifier =
155 SendPropIdentifier::new("DT_AttributeManager", "m_hOuter");
156
157 let Some(class_name) = self.class_names.get(usize::from(entity.server_class)) else {
158 return;
159 };
160
161 if entity.update_type == UpdateType::Enter
162 && let Some(prop) = entity
163 .get_prop_by_identifier(&OUTER, parser_state)
164 .or_else(|| entity.get_prop_by_identifier(&OUTER2, parser_state))
165 && let Ok(outer) = Handle::try_from(&prop.value) {
166 self.outer_map.insert(outer, entity.entity_index);
167 self.outer_map_rev.insert(entity.entity_index, outer);
168 }
169
170 match class_name.as_str() {
171 "CTFPlayer" => handle_player_entity(&mut self.state, entity, parser_state),
172 "CTFPlayerResource" => handle_player_resource(&mut self.state, entity, parser_state),
173 "CWorld" => self.handle_world_entity(entity, parser_state),
174 "CObjectSentrygun" => handle_sentry_entity(&mut self.state, entity, parser_state),
175 "CObjectDispenser" => handle_dispenser_entity(&mut self.state, entity, parser_state),
176 "CObjectTeleporter" => handle_teleporter_entity(&mut self.state, entity, parser_state),
177 "CFuncTrackTrain" => self.handle_train_entity(entity, parser_state),
178 "CWeaponMedigun" => handle_medigun_entity(&mut self.state, entity, &self.outer_map_rev),
179 _ if class_name.starts_with("CTFProjectile_")
180 || class_name.as_str() == "CTFGrenadePipebombProjectile" =>
181 {
182 handle_projectile_entity(&mut self.state, entity, parser_state, &self.class_names)
183 }
184 _ => {}
185 }
186 }
187
188 pub fn handle_world_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
189 const MINS: SendPropIdentifier = SendPropIdentifier::new("DT_WORLD", "m_WorldMins");
190 const MAXS: SendPropIdentifier = SendPropIdentifier::new("DT_WORLD", "m_WorldMaxs");
191
192 if let (
193 Some(SendProp {
194 value: SendPropValue::Vector(boundary_min),
195 ..
196 }),
197 Some(SendProp {
198 value: SendPropValue::Vector(boundary_max),
199 ..
200 }),
201 ) = (
202 entity.get_prop_by_identifier(&MINS, parser_state),
203 entity.get_prop_by_identifier(&MAXS, parser_state),
204 ) {
205 self.state.world = Some(World {
206 boundary_min,
207 boundary_max,
208 })
209 }
210 }
211
212 pub fn handle_train_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
213 const POSITION: SendPropIdentifier =
214 SendPropIdentifier::new("DT_BaseEntity", "m_vecOrigin");
215
216 let objective = self
217 .state
218 .objectives
219 .entry(entity.entity_index)
220 .or_insert_with(|| Objective::Cart(Cart::default()));
221
222 #[allow(irrefutable_let_patterns)]
223 if let Objective::Cart(cart) = objective {
224 for prop in entity.props(parser_state) {
225 if prop.identifier == POSITION {
226 let pos = Vector::try_from(&prop.value).unwrap_or_default();
227 cart.position = pos
228 }
229 }
230 }
231 }
232
233 #[allow(dead_code, unused_variables)]
234 pub fn handle_cp_entity(&mut self, entity: &PacketEntity, parser_state: &ParserState) {
235 const OWNERS: [SendPropIdentifier; 5] = [
236 SendPropIdentifier::new("m_iOwner", "000"),
237 SendPropIdentifier::new("m_iOwner", "001"),
238 SendPropIdentifier::new("m_iOwner", "002"),
239 SendPropIdentifier::new("m_iOwner", "003"),
240 SendPropIdentifier::new("m_iOwner", "004"),
241 ];
242 const CAP_PERCENTAGE: [SendPropIdentifier; 5] = [
243 SendPropIdentifier::new("m_flLazyCapPerc", "000"),
244 SendPropIdentifier::new("m_flLazyCapPerc", "001"),
245 SendPropIdentifier::new("m_flLazyCapPerc", "002"),
246 SendPropIdentifier::new("m_flLazyCapPerc", "003"),
247 SendPropIdentifier::new("m_flLazyCapPerc", "004"),
248 ];
249
250 let objective = self
251 .state
252 .objectives
253 .entry(entity.entity_index)
254 .or_insert_with(|| Objective::Cart(Cart::default()));
255
256 todo!()
257 }
258
259 fn parse_user_info(
260 &mut self,
261 index: usize,
262 text: Option<&str>,
263 data: Option<Stream>,
264 ) -> ReadResult<()> {
265 if let Some(user_info) =
266 crate::demo::data::UserInfo::parse_from_string_table(index as u16, text, data)?
267 {
268 let id = user_info.entity_id;
269 self.state.get_or_create_player(id).info = Some(user_info.into());
270 }
271
272 Ok(())
273 }
274}