gamedig/services/valve_master_server/
types.rs

1use std::collections::HashMap;
2use std::mem::Discriminant;
3
4/// A query filter.
5#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
6pub enum Filter {
7    IsSecured(bool),
8    RunsMap(String),
9    CanHavePassword(bool),
10    CanBeEmpty(bool),
11    IsEmpty(bool),
12    CanBeFull(bool),
13    RunsAppID(u32),
14    NotAppID(u32),
15    HasTags(Vec<String>),
16    MatchName(String),
17    MatchVersion(String),
18    /// Restrict to only a server if an IP hosts (on different ports) multiple
19    /// servers.
20    RestrictUniqueIP(bool),
21    /// Query for servers on a specific address.
22    OnAddress(String),
23    Whitelisted(bool),
24    SpectatorProxy(bool),
25    IsDedicated(bool),
26    RunsLinux(bool),
27    HasGameDir(String),
28}
29
30const fn bool_as_char_u8(b: &bool) -> u8 {
31    match b {
32        true => b'1',
33        false => b'0',
34    }
35}
36
37impl Filter {
38    pub(crate) fn to_bytes(&self) -> Vec<u8> {
39        let mut bytes: Vec<u8> = Vec::new();
40
41        match self {
42            Self::IsSecured(secured) => {
43                bytes = b"\\secure\\".to_vec();
44                bytes.extend([bool_as_char_u8(secured)]);
45            }
46            Self::RunsMap(map) => {
47                bytes = b"\\map\\".to_vec();
48                bytes.extend(map.as_bytes());
49            }
50            Self::CanHavePassword(password) => {
51                bytes = b"\\password\\".to_vec();
52                bytes.extend([bool_as_char_u8(password)]);
53            }
54            Self::CanBeEmpty(empty) => {
55                bytes = b"\\empty\\".to_vec();
56                bytes.extend([bool_as_char_u8(empty)]);
57            }
58            Self::CanBeFull(full) => {
59                bytes = b"\\full\\".to_vec();
60                bytes.extend([bool_as_char_u8(full)]);
61            }
62            Self::RunsAppID(id) => {
63                bytes = b"\\appid\\".to_vec();
64                bytes.extend(id.to_string().as_bytes());
65            }
66            Self::HasTags(tags) => {
67                if !tags.is_empty() {
68                    bytes = b"\\gametype\\".to_vec();
69                    for tag in tags.iter() {
70                        bytes.extend(tag.as_bytes());
71                        bytes.extend([b',']);
72                    }
73
74                    bytes.pop();
75                }
76            }
77            Self::NotAppID(id) => {
78                bytes = b"\\napp\\".to_vec();
79                bytes.extend(id.to_string().as_bytes());
80            }
81            Self::IsEmpty(empty) => {
82                bytes = b"\\noplayers\\".to_vec();
83                bytes.extend([bool_as_char_u8(empty)]);
84            }
85            Self::MatchName(name) => {
86                bytes = b"\\name_match\\".to_vec();
87                bytes.extend(name.as_bytes());
88            }
89            Self::MatchVersion(version) => {
90                bytes = b"\\version_match\\".to_vec();
91                bytes.extend(version.as_bytes());
92            }
93            Self::RestrictUniqueIP(unique) => {
94                bytes = b"\\collapse_addr_hash\\".to_vec();
95                bytes.extend([bool_as_char_u8(unique)]);
96            }
97            Self::OnAddress(address) => {
98                bytes = b"\\gameaddr\\".to_vec();
99                bytes.extend(address.as_bytes());
100            }
101            Self::Whitelisted(whitelisted) => {
102                bytes = b"\\white\\".to_vec();
103                bytes.extend([bool_as_char_u8(whitelisted)]);
104            }
105            Self::SpectatorProxy(condition) => {
106                bytes = b"\\proxy\\".to_vec();
107                bytes.extend([bool_as_char_u8(condition)]);
108            }
109            Self::IsDedicated(dedicated) => {
110                bytes = b"\\dedicated\\".to_vec();
111                bytes.extend([bool_as_char_u8(dedicated)]);
112            }
113            Self::RunsLinux(linux) => {
114                bytes = b"\\linux\\".to_vec();
115                bytes.extend([bool_as_char_u8(linux)]);
116            }
117            Self::HasGameDir(game_dir) => {
118                bytes = b"\\gamedir\\".to_vec();
119                bytes.extend(game_dir.as_bytes());
120            }
121        }
122
123        bytes
124    }
125}
126
127/// Query search filters.
128/// An example of constructing one:
129/// ```rust
130/// use gamedig::valve_master_server::{Filter, SearchFilters};
131///
132/// let search_filters = SearchFilters::new()
133///             .insert(Filter::RunsAppID(440))
134///             .insert(Filter::IsEmpty(false))
135///             .insert(Filter::CanHavePassword(false));
136/// ```
137/// This will construct filters that search for servers that can't have a
138/// password, are not empty and run App ID 440.
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct SearchFilters {
141    filters: HashMap<Discriminant<Filter>, Filter>,
142    nor_filters: HashMap<Discriminant<Filter>, Filter>,
143    nand_filters: HashMap<Discriminant<Filter>, Filter>,
144}
145
146impl Default for SearchFilters {
147    fn default() -> Self { Self::new() }
148}
149
150impl SearchFilters {
151    pub fn new() -> Self {
152        Self {
153            filters: HashMap::new(),
154            nor_filters: HashMap::new(),
155            nand_filters: HashMap::new(),
156        }
157    }
158
159    pub fn insert(self, filter: Filter) -> Self {
160        let mut updated_fitler = self.filters;
161        updated_fitler.insert(std::mem::discriminant(&filter), filter);
162
163        Self {
164            filters: updated_fitler,
165            nand_filters: self.nand_filters,
166            nor_filters: self.nor_filters,
167        }
168    }
169
170    pub fn insert_nand(self, filter: Filter) -> Self {
171        let mut updated_fitler = self.nor_filters;
172        updated_fitler.insert(std::mem::discriminant(&filter), filter);
173
174        Self {
175            filters: self.filters,
176            nand_filters: self.nand_filters,
177            nor_filters: updated_fitler,
178        }
179    }
180
181    pub fn insert_nor(self, filter: Filter) -> Self {
182        let mut updated_fitler = self.nand_filters;
183        updated_fitler.insert(std::mem::discriminant(&filter), filter);
184
185        Self {
186            filters: self.filters,
187            nand_filters: updated_fitler,
188            nor_filters: self.nor_filters,
189        }
190    }
191
192    fn special_filter_to_bytes(name: &str, filters: &HashMap<Discriminant<Filter>, Filter>) -> Vec<u8> {
193        let mut bytes = Vec::new();
194
195        if !filters.is_empty() {
196            bytes.extend(name.as_bytes());
197            bytes.extend(filters.len().to_string().as_bytes());
198            for filter in filters.values() {
199                bytes.extend(filter.to_bytes());
200            }
201        }
202
203        bytes
204    }
205
206    pub(crate) fn to_bytes(&self) -> Vec<u8> {
207        let mut bytes: Vec<u8> = Vec::new();
208
209        for filter in self.filters.values() {
210            bytes.extend(filter.to_bytes())
211        }
212
213        bytes.extend(Self::special_filter_to_bytes("nand", &self.nand_filters));
214        bytes.extend(Self::special_filter_to_bytes("nor", &self.nor_filters));
215
216        bytes.extend([0x00]);
217        bytes
218    }
219}
220
221/// The region that you want to query server for.
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
223#[repr(u8)]
224pub enum Region {
225    UsEast = 0x00,
226    UsWest = 0x01,
227    AmericaSouth = 0x02,
228    Europe = 0x03,
229    Asia = 0x04,
230    Australia = 0x05,
231    MiddleEast = 0x06,
232    Africa = 0x07,
233    Others = 0xFF,
234}