cs2_gsi/model/mod.rs
1//! Top-level GameState model.
2//!
3//! The root JSON document POSTed by Counter-Strike 2 to its Game State
4//! Integration endpoint. Every node is optional — the cfg file decides which
5//! sections CS2 includes — so missing or null sub-objects deserialize to
6//! their `Default` value rather than failing.
7
8mod auth;
9mod bomb;
10mod grenade;
11mod helpers;
12mod map;
13mod player;
14mod provider;
15mod round;
16mod tournament;
17
18pub use auth::Auth;
19pub use bomb::{Bomb, BombState};
20pub use grenade::{Grenade, GrenadeKind};
21pub use map::{Map, MapPhase, TeamStatistics};
22pub use player::{
23 MatchStats, Player, PlayerActivity, PlayerState, PlayerTeam, Weapon, WeaponKind, WeaponState,
24};
25pub use provider::Provider;
26pub use round::{BombRoundState, Round, RoundPhase, WinningTeam};
27pub use tournament::{PhaseCountdowns, TournamentDraft};
28
29use serde::{Deserialize, Serialize};
30use std::collections::BTreeMap;
31
32/// Root GSI document received from CS2.
33#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
34pub struct GameState {
35 /// Authentication block declared in the cfg file. CS2 itself never adds
36 /// a token, but tokens you wrote into the cfg appear here.
37 #[serde(default)]
38 pub auth: Auth,
39
40 /// Producer of the payload (the CS2 client itself).
41 #[serde(default)]
42 pub provider: Provider,
43
44 /// Series / map state. Absent when the player is not in a match.
45 #[serde(default)]
46 pub map: Option<Map>,
47
48 /// Per-round state. Absent outside of an active round.
49 #[serde(default)]
50 pub round: Option<Round>,
51
52 /// Player snapshot from the local client's point-of-view (the player or
53 /// the spectated entity). Absent on the main menu.
54 #[serde(default)]
55 pub player: Option<Player>,
56
57 /// All known players keyed by SteamID. Only populated when the cfg file
58 /// requests `allplayers_*` sections AND the local client is permitted to
59 /// see them (e.g. spectator / replay / GOTV).
60 #[serde(default)]
61 pub allplayers: BTreeMap<String, Player>,
62
63 /// Countdown for the current map / round phase.
64 #[serde(default, rename = "phase_countdowns")]
65 pub phase_countdowns: Option<PhaseCountdowns>,
66
67 /// Detailed bomb info.
68 #[serde(default)]
69 pub bomb: Option<Bomb>,
70
71 /// All in-flight / active grenades, keyed by entity index.
72 #[serde(default, alias = "allgrenades")]
73 pub grenades: BTreeMap<String, Grenade>,
74
75 /// Tournament-mode draft state.
76 #[serde(default, alias = "tournamentdraft")]
77 pub tournament_draft: Option<TournamentDraft>,
78
79 /// CS2 may include a `previously` block listing the values of fields
80 /// that just changed. cs2-gsi performs its own diff and does not rely on
81 /// this — it is exposed here as raw JSON for advanced consumers.
82 #[serde(default)]
83 pub previously: Option<serde_json::Value>,
84
85 /// CS2 may include an `added` block listing fields that appeared for the
86 /// first time. Exposed as raw JSON for advanced consumers.
87 #[serde(default)]
88 pub added: Option<serde_json::Value>,
89}
90
91impl GameState {
92 /// Parse a GSI payload from raw bytes.
93 pub fn from_slice(bytes: &[u8]) -> Result<Self, serde_json::Error> {
94 serde_json::from_slice(bytes)
95 }
96
97 /// Parse a GSI payload from a string. Provided as an inherent method for
98 /// discoverability — the conflicting [`std::str::FromStr`] impl has the
99 /// same semantics.
100 #[allow(clippy::should_implement_trait)]
101 pub fn from_str(s: &str) -> Result<Self, serde_json::Error> {
102 serde_json::from_str(s)
103 }
104
105 /// Convenience: returns `true` when the local player is dead (HP == 0
106 /// AND in the `playing` activity). Returns `false` when no player block
107 /// is present.
108 pub fn local_is_dead(&self) -> bool {
109 match &self.player {
110 Some(p) => matches!(p.activity, PlayerActivity::Playing) && p.state.health == 0,
111 None => false,
112 }
113 }
114}