Skip to main content

tf_demo_parser/demo/parser/gamestateanalyser/
mod.rs

1mod 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>, // indexed by ClassId
43    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}