1use std::{
2 fmt::Display,
3 net::{Ipv4Addr, SocketAddrV4},
4};
5
6use super::*;
7#[cfg(test)]
8use serial_test::serial;
9
10pub struct Matchmaking {
12 pub(crate) mm: *mut sys::ISteamMatchmaking,
13 pub(crate) inner: Arc<Inner>,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19pub enum LobbyType {
20 Private,
21 FriendsOnly,
22 Public,
23 Invisible,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28pub struct LobbyId(pub(crate) u64);
29
30impl LobbyId {
31 pub fn from_raw(id: u64) -> LobbyId {
36 LobbyId(id)
37 }
38
39 pub fn raw(&self) -> u64 {
44 self.0
45 }
46}
47
48impl Matchmaking {
49 pub fn request_lobby_list<F>(&self, cb: F)
50 where
51 F: FnOnce(SResult<Vec<LobbyId>>) + 'static + Send,
52 {
53 unsafe {
54 let api_call = sys::SteamAPI_ISteamMatchmaking_RequestLobbyList(self.mm);
55 register_call_result::<sys::LobbyMatchList_t, _>(
56 &self.inner,
57 api_call,
58 move |v, io_error| {
59 cb(if io_error {
60 Err(SteamError::IOFailure)
61 } else {
62 let mut out = Vec::with_capacity(v.m_nLobbiesMatching as usize);
63 for idx in 0..v.m_nLobbiesMatching {
64 out.push(LobbyId(sys::SteamAPI_ISteamMatchmaking_GetLobbyByIndex(
65 sys::SteamAPI_SteamMatchmaking_v009(),
66 idx as _,
67 )));
68 }
69 Ok(out)
70 })
71 },
72 );
73 }
74 }
75
76 pub fn create_lobby<F>(&self, ty: LobbyType, max_members: u32, cb: F)
87 where
88 F: FnOnce(SResult<LobbyId>) + 'static + Send,
89 {
90 assert!(max_members <= 250); unsafe {
92 let ty = match ty {
93 LobbyType::Private => sys::ELobbyType::k_ELobbyTypePrivate,
94 LobbyType::FriendsOnly => sys::ELobbyType::k_ELobbyTypeFriendsOnly,
95 LobbyType::Public => sys::ELobbyType::k_ELobbyTypePublic,
96 LobbyType::Invisible => sys::ELobbyType::k_ELobbyTypeInvisible,
97 };
98 let api_call =
99 sys::SteamAPI_ISteamMatchmaking_CreateLobby(self.mm, ty, max_members as _);
100 register_call_result::<sys::LobbyCreated_t, _>(
101 &self.inner,
102 api_call,
103 move |v, io_error| {
104 cb(if io_error {
105 Err(SteamError::IOFailure)
106 } else {
107 crate::to_steam_result(v.m_eResult).map(|_| LobbyId(v.m_ulSteamIDLobby))
108 })
109 },
110 );
111 }
112 }
113
114 pub fn join_lobby<F>(&self, lobby: LobbyId, cb: F)
116 where
117 F: FnOnce(Result<LobbyId, ()>) + 'static + Send,
118 {
119 unsafe {
120 let api_call = sys::SteamAPI_ISteamMatchmaking_JoinLobby(self.mm, lobby.0);
121 register_call_result::<sys::LobbyEnter_t, _>(
122 &self.inner,
123 api_call,
124 move |v, io_error| {
125 cb(if io_error || v.m_EChatRoomEnterResponse != 1 {
126 Err(())
127 } else {
128 Ok(LobbyId(v.m_ulSteamIDLobby))
129 })
130 },
131 );
132 }
133 }
134
135 pub fn lobby_data_count(&self, lobby: LobbyId) -> u32 {
137 unsafe { sys::SteamAPI_ISteamMatchmaking_GetLobbyDataCount(self.mm, lobby.0) as _ }
138 }
139
140 pub fn lobby_data(&self, lobby: LobbyId, key: &str) -> Option<String> {
143 let key = CString::new(key).unwrap();
144 unsafe {
145 let data = sys::SteamAPI_ISteamMatchmaking_GetLobbyData(self.mm, lobby.0, key.as_ptr());
146 CStr::from_ptr(data)
147 }
148 .to_str()
149 .ok()
150 .filter(|s| !s.is_empty())
151 .map(str::to_owned)
152 }
153
154 pub fn lobby_data_by_index(&self, lobby: LobbyId, idx: u32) -> Option<(String, String)> {
156 let mut key = [0i8; sys::k_nMaxLobbyKeyLength as usize];
157 let mut value = [0i8; sys::k_cubChatMetadataMax as usize];
158 unsafe {
159 let success = sys::SteamAPI_ISteamMatchmaking_GetLobbyDataByIndex(
160 self.mm,
161 lobby.0,
162 idx as _,
163 key.as_mut_ptr() as _,
164 key.len() as _,
165 value.as_mut_ptr() as _,
166 value.len() as _,
167 );
168 match success {
169 true => Some((
170 CStr::from_ptr(key.as_ptr()).to_string_lossy().into_owned(),
171 CStr::from_ptr(value.as_ptr())
172 .to_string_lossy()
173 .into_owned(),
174 )),
175 false => None,
176 }
177 }
178 }
179
180 pub fn set_lobby_data(&self, lobby: LobbyId, key: &str, value: &str) -> bool {
182 let key = CString::new(key).unwrap();
183 let value = CString::new(value).unwrap();
184 unsafe {
185 sys::SteamAPI_ISteamMatchmaking_SetLobbyData(
186 self.mm,
187 lobby.0,
188 key.as_ptr(),
189 value.as_ptr(),
190 )
191 }
192 }
193
194 pub fn delete_lobby_data(&self, lobby: LobbyId, key: &str) -> bool {
196 let key = CString::new(key).unwrap();
197 unsafe { sys::SteamAPI_ISteamMatchmaking_DeleteLobbyData(self.mm, lobby.0, key.as_ptr()) }
198 }
199
200 pub fn set_lobby_member_data(&self, lobby: LobbyId, key: &str, value: &str) {
204 let key = CString::new(key).unwrap();
205 let value = CString::new(value).unwrap();
206 unsafe {
207 sys::SteamAPI_ISteamMatchmaking_SetLobbyMemberData(
208 self.mm,
209 lobby.0,
210 key.as_ptr(),
211 value.as_ptr(),
212 )
213 }
214 }
215
216 pub fn get_lobby_member_data(
221 &self,
222 lobby: LobbyId,
223 user: SteamId,
224 key: &str,
225 ) -> Option<String> {
226 let key = CString::new(key).unwrap();
227 unsafe {
228 let data = sys::SteamAPI_ISteamMatchmaking_GetLobbyMemberData(
229 self.mm,
230 lobby.0,
231 user.0,
232 key.as_ptr(),
233 );
234 CStr::from_ptr(data)
235 }
236 .to_str()
237 .map(str::to_owned)
238 .ok()
239 }
240
241 pub fn leave_lobby(&self, lobby: LobbyId) {
243 unsafe {
244 sys::SteamAPI_ISteamMatchmaking_LeaveLobby(self.mm, lobby.0);
245 }
246 }
247
248 pub fn lobby_member_limit(&self, lobby: LobbyId) -> Option<usize> {
252 unsafe {
253 let count = sys::SteamAPI_ISteamMatchmaking_GetLobbyMemberLimit(self.mm, lobby.0);
254 match count {
255 0 => None,
256 _ => Some(count as usize),
257 }
258 }
259 }
260
261 pub fn lobby_owner(&self, lobby: LobbyId) -> SteamId {
263 unsafe {
264 SteamId(sys::SteamAPI_ISteamMatchmaking_GetLobbyOwner(
265 self.mm, lobby.0,
266 ))
267 }
268 }
269
270 pub fn lobby_member_count(&self, lobby: LobbyId) -> usize {
274 unsafe {
275 let count = sys::SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(self.mm, lobby.0);
276 count as usize
277 }
278 }
279
280 pub fn lobby_members(&self, lobby: LobbyId) -> Vec<SteamId> {
282 unsafe {
283 let count = sys::SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(self.mm, lobby.0);
284 let mut members = Vec::with_capacity(count as usize);
285 for idx in 0..count {
286 members.push(SteamId(
287 sys::SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(self.mm, lobby.0, idx),
288 ))
289 }
290 members
291 }
292 }
293
294 pub fn set_lobby_joinable(&self, lobby: LobbyId, joinable: bool) -> bool {
304 unsafe { sys::SteamAPI_ISteamMatchmaking_SetLobbyJoinable(self.mm, lobby.0, joinable) }
305 }
306
307 pub fn send_lobby_chat_message(&self, lobby: LobbyId, msg: &[u8]) -> Result<(), SteamError> {
334 match unsafe {
335 steamworks_sys::SteamAPI_ISteamMatchmaking_SendLobbyChatMsg(
336 self.mm,
337 lobby.0,
338 msg.as_ptr().cast(),
339 msg.len() as i32,
340 )
341 } {
342 true => Ok(()),
343 false => Err(SteamError::IOFailure),
344 }
345 }
346
347 pub fn get_lobby_chat_entry<'a>(
358 &self,
359 lobby: LobbyId,
360 chat_id: i32,
361 buffer: &'a mut [u8],
362 ) -> &'a [u8] {
363 let mut steam_user = sys::CSteamID {
364 m_steamid: sys::CSteamID_SteamID_t { m_unAll64Bits: 0 },
365 };
366 let mut chat_type = steamworks_sys::EChatEntryType::k_EChatEntryTypeInvalid;
367 unsafe {
368 let len = sys::SteamAPI_ISteamMatchmaking_GetLobbyChatEntry(
369 self.mm,
370 lobby.0,
371 chat_id,
372 &mut steam_user,
373 buffer.as_mut_ptr().cast(),
374 buffer.len() as _,
375 &mut chat_type,
376 );
377 return &buffer[0..len as usize];
378 }
379 }
380 pub fn add_request_lobby_list_string_filter(
391 &self,
392 StringFilter(LobbyKey(key), value, kind): StringFilter,
393 ) -> &Self {
394 let key = CString::new(key).unwrap();
395 let value = CString::new(value).unwrap();
396 unsafe {
397 sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(
398 self.mm,
399 key.as_ptr(),
400 value.as_ptr(),
401 kind.into(),
402 );
403 }
404 self
405 }
406 pub fn add_request_lobby_list_numerical_filter(
417 &self,
418 NumberFilter(LobbyKey(key), value, comparison): NumberFilter,
419 ) -> &Self {
420 let key = CString::new(key).unwrap();
421 unsafe {
422 sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListNumericalFilter(
423 self.mm,
424 key.as_ptr(),
425 value,
426 comparison.into(),
427 );
428 }
429 self
430 }
431 pub fn add_request_lobby_list_near_value_filter(
442 &self,
443 NearFilter(LobbyKey(key), value): NearFilter,
444 ) -> &Self {
445 let key = CString::new(key).unwrap();
446 unsafe {
447 sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListNearValueFilter(
448 self.mm,
449 key.as_ptr(),
450 value,
451 );
452 }
453 self
454 }
455 pub fn set_request_lobby_list_slots_available_filter(&self, open_slots: u8) -> &Self {
464 unsafe {
465 sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListFilterSlotsAvailable(
466 self.mm,
467 open_slots as i32,
468 );
469 }
470 self
471 }
472 pub fn set_request_lobby_list_distance_filter(&self, distance: DistanceFilter) -> &Self {
481 unsafe {
482 sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter(
483 self.mm,
484 distance.into(),
485 );
486 }
487 self
488 }
489 pub fn set_request_lobby_list_result_count_filter(&self, count: u64) -> &Self {
498 unsafe {
499 sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter(
500 self.mm,
501 count as i32,
502 );
503 }
504 self
505 }
506
507 pub fn set_lobby_list_filter(&self, filter: LobbyListFilter<'_>) -> &Self {
548 filter.string.into_iter().flatten().for_each(|str_filter| {
549 self.add_request_lobby_list_string_filter(str_filter);
550 });
551 filter.number.into_iter().flatten().for_each(|num_filter| {
552 self.add_request_lobby_list_numerical_filter(num_filter);
553 });
554 filter
555 .near_value
556 .into_iter()
557 .flatten()
558 .for_each(|near_filter| {
559 self.add_request_lobby_list_near_value_filter(near_filter);
560 });
561 if let Some(distance) = filter.distance {
562 self.set_request_lobby_list_distance_filter(distance);
563 }
564 if let Some(open_slots) = filter.open_slots {
565 self.set_request_lobby_list_slots_available_filter(open_slots);
566 }
567 if let Some(count) = filter.count {
568 self.set_request_lobby_list_result_count_filter(count);
569 }
570 self
571 }
572
573 pub fn set_lobby_game_server(
585 &self,
586 lobby: LobbyId,
587 server_addr: SocketAddrV4,
588 server_steam_id: Option<SteamId>,
589 ) -> () {
590 unsafe {
591 sys::SteamAPI_ISteamMatchmaking_SetLobbyGameServer(
592 self.mm,
593 lobby.0,
594 server_addr.ip().to_bits(),
595 server_addr.port(),
596 server_steam_id.map(|id| id.0).unwrap_or(0),
597 )
598 }
599 }
600
601 pub fn get_lobby_game_server(&self, lobby: LobbyId) -> Option<(SocketAddrV4, Option<SteamId>)> {
611 unsafe {
612 let mut server_ip = 0;
613 let mut server_port = 0;
614
615 let mut server_steam_id = sys::CSteamID {
616 m_steamid: sys::CSteamID_SteamID_t { m_unAll64Bits: 0 },
617 };
618
619 let success = sys::SteamAPI_ISteamMatchmaking_GetLobbyGameServer(
620 self.mm,
621 lobby.0,
622 &mut server_ip,
623 &mut server_port,
624 &mut server_steam_id,
625 );
626
627 let server_addr = SocketAddrV4::new(Ipv4Addr::from_bits(server_ip), server_port);
628 let server_id = SteamId::from_raw(server_steam_id.m_steamid.m_unAll64Bits);
629
630 let server_id = (!server_id.is_invalid()).then_some(server_id);
632
633 if success {
634 Some((server_addr, server_id))
635 } else {
636 None
637 }
638 }
639 }
640}
641
642#[derive(Debug, Clone, Default, PartialEq)]
656#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
657pub struct LobbyListFilter<'a> {
658 pub string: Option<StringFilters<'a>>,
661 #[cfg_attr(feature = "serde", serde(borrow))]
663 pub number: Option<NumberFilters<'a>>,
664 #[cfg_attr(feature = "serde", serde(borrow))]
666 pub near_value: Option<NearFilters<'a>>,
667 pub open_slots: Option<u8>,
669 pub distance: Option<DistanceFilter>,
671 pub count: Option<u64>,
673}
674
675#[derive(Debug, Default, Clone, Copy, PartialEq)]
680#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
681pub struct LobbyKey<'a>(pub(crate) &'a str);
682
683impl<'a> std::ops::Deref for LobbyKey<'a> {
684 type Target = &'a str;
685
686 fn deref(&self) -> &Self::Target {
687 &self.0
688 }
689}
690
691#[derive(Debug, Clone, Copy, PartialEq, Default, Error)]
692#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
693pub struct LobbyKeyTooLongError;
694
695impl Display for LobbyKeyTooLongError {
696 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
697 write!(
698 f,
699 "Lobby key is greater than {} characters",
700 sys::k_nMaxLobbyKeyLength
701 )
702 }
703}
704
705impl<'a> LobbyKey<'a> {
706 pub fn try_new(key: &'a str) -> Result<Self, LobbyKeyTooLongError> {
717 if key.len() > sys::k_nMaxLobbyKeyLength as usize {
718 Err(LobbyKeyTooLongError)
719 } else {
720 Ok(LobbyKey(key))
721 }
722 }
723 pub fn new(key: &'a str) -> Self {
733 Self::try_new(key).unwrap()
734 }
735}
736
737pub type StringFilters<'a> = Vec<StringFilter<'a>>;
738pub type NumberFilters<'a> = Vec<NumberFilter<'a>>;
739pub type NearFilters<'a> = Vec<NearFilter<'a>>;
740
741#[derive(Debug, Default, Clone, Copy, PartialEq)]
748#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
749pub struct StringFilter<'a>(
750 #[cfg_attr(feature = "serde", serde(borrow))] pub LobbyKey<'a>,
751 pub &'a str,
752 pub StringFilterKind,
753);
754
755#[derive(Debug, Default, Clone, Copy, PartialEq)]
756#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
757pub enum StringFilterKind {
758 #[default]
759 EqualToOrLessThan,
760 LessThan,
761 Equal,
762 GreaterThan,
763 EqualToOrGreaterThan,
764 NotEqual,
765}
766
767impl From<StringFilterKind> for sys::ELobbyComparison {
768 fn from(filter: StringFilterKind) -> Self {
769 match filter {
770 StringFilterKind::EqualToOrLessThan => {
771 sys::ELobbyComparison::k_ELobbyComparisonEqualToOrLessThan
772 }
773 StringFilterKind::LessThan => sys::ELobbyComparison::k_ELobbyComparisonLessThan,
774 StringFilterKind::Equal => sys::ELobbyComparison::k_ELobbyComparisonEqual,
775 StringFilterKind::GreaterThan => sys::ELobbyComparison::k_ELobbyComparisonGreaterThan,
776 StringFilterKind::EqualToOrGreaterThan => {
777 sys::ELobbyComparison::k_ELobbyComparisonEqualToOrGreaterThan
778 }
779 StringFilterKind::NotEqual => sys::ELobbyComparison::k_ELobbyComparisonNotEqual,
780 }
781 }
782}
783
784#[derive(Debug, Default, Clone, Copy, PartialEq)]
804#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
805pub struct NumberFilter<'a>(
806 #[cfg_attr(feature = "serde", serde(borrow))] pub LobbyKey<'a>,
807 pub i32,
808 pub ComparisonFilter,
809);
810
811#[derive(Debug, Default, Clone, Copy, PartialEq)]
823#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
824pub struct NearFilter<'a>(
825 #[cfg_attr(feature = "serde", serde(borrow))] pub LobbyKey<'a>,
826 pub i32,
827);
828
829impl<'a> LobbyListFilter<'a> {
830 pub fn set_string(mut self, string: Option<StringFilters<'a>>) -> Self {
837 self.string = string;
838 self
839 }
840
841 pub fn set_number(mut self, number: Option<NumberFilters<'a>>) -> Self {
848 self.number = number;
849 self
850 }
851
852 pub fn set_near_value(mut self, near_value: Option<NearFilters<'a>>) -> Self {
860 self.near_value = near_value;
861 self
862 }
863
864 pub fn set_open_slots(mut self, open_slots: Option<u8>) -> Self {
871 self.open_slots = open_slots;
872 self
873 }
874
875 pub fn set_distance(mut self, distance: Option<DistanceFilter>) -> Self {
882 self.distance = distance;
883 self
884 }
885
886 pub fn set_count(mut self, count: Option<u64>) -> Self {
893 self.count = count;
894 self
895 }
896}
897
898#[derive(Debug, Clone, Copy, PartialEq, Default)]
899#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
900pub enum DistanceFilter {
901 Close,
902 #[default]
903 Default,
904 Far,
905 Worldwide,
906}
907
908impl From<DistanceFilter> for sys::ELobbyDistanceFilter {
909 fn from(filter: DistanceFilter) -> Self {
910 match filter {
911 DistanceFilter::Close => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterClose,
912 DistanceFilter::Default => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterDefault,
913 DistanceFilter::Far => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterFar,
914 DistanceFilter::Worldwide => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterWorldwide,
915 }
916 }
917}
918
919#[derive(Debug, Clone, Copy, PartialEq, Default)]
920#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
921pub enum ComparisonFilter {
922 #[default]
923 Equal,
924 NotEqual,
925 GreaterThan,
926 GreaterThanEqualTo,
927 LessThan,
928 LessThanEqualTo,
929}
930
931impl From<ComparisonFilter> for sys::ELobbyComparison {
932 fn from(filter: ComparisonFilter) -> Self {
933 match filter {
934 ComparisonFilter::Equal => sys::ELobbyComparison::k_ELobbyComparisonEqual,
935 ComparisonFilter::NotEqual => sys::ELobbyComparison::k_ELobbyComparisonNotEqual,
936 ComparisonFilter::GreaterThan => sys::ELobbyComparison::k_ELobbyComparisonGreaterThan,
937 ComparisonFilter::GreaterThanEqualTo => {
938 sys::ELobbyComparison::k_ELobbyComparisonEqualToOrGreaterThan
939 }
940 ComparisonFilter::LessThan => sys::ELobbyComparison::k_ELobbyComparisonLessThan,
941 ComparisonFilter::LessThanEqualTo => {
942 sys::ELobbyComparison::k_ELobbyComparisonEqualToOrLessThan
943 }
944 }
945 }
946}
947
948#[derive(Clone, Debug, PartialEq)]
950#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
951pub enum ChatMemberStateChange {
952 Entered,
954
955 Left,
957
958 Disconnected,
960
961 Kicked,
963
964 Banned,
966}
967
968#[derive(Debug, Clone, Copy, PartialEq)]
969#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
970pub enum ChatEntryType {
971 Invalid,
972 ChatMsg,
973 Typing,
974 InviteGame,
975 Emote,
976 LeftConversation,
977 Entered,
978 WasKicked,
979 WasBanned,
980 Disconnected,
981 HistoricalChat,
982 LinkBlocked,
983}
984
985impl From<u8> for ChatEntryType {
986 fn from(value: u8) -> Self {
987 match value {
988 x if x == sys::EChatEntryType::k_EChatEntryTypeInvalid as u8 => ChatEntryType::Invalid,
989 x if x == sys::EChatEntryType::k_EChatEntryTypeChatMsg as u8 => ChatEntryType::ChatMsg,
990 x if x == sys::EChatEntryType::k_EChatEntryTypeTyping as u8 => ChatEntryType::Typing,
991 x if x == sys::EChatEntryType::k_EChatEntryTypeInviteGame as u8 => {
992 ChatEntryType::InviteGame
993 }
994 x if x == sys::EChatEntryType::k_EChatEntryTypeEmote as u8 => ChatEntryType::Emote,
995 x if x == sys::EChatEntryType::k_EChatEntryTypeLeftConversation as u8 => {
996 ChatEntryType::LeftConversation
997 }
998 x if x == sys::EChatEntryType::k_EChatEntryTypeEntered as u8 => ChatEntryType::Entered,
999 x if x == sys::EChatEntryType::k_EChatEntryTypeWasKicked as u8 => {
1000 ChatEntryType::WasKicked
1001 }
1002 x if x == sys::EChatEntryType::k_EChatEntryTypeWasBanned as u8 => {
1003 ChatEntryType::WasBanned
1004 }
1005 x if x == sys::EChatEntryType::k_EChatEntryTypeDisconnected as u8 => {
1006 ChatEntryType::Disconnected
1007 }
1008 x if x == sys::EChatEntryType::k_EChatEntryTypeHistoricalChat as u8 => {
1009 ChatEntryType::HistoricalChat
1010 }
1011 x if x == sys::EChatEntryType::k_EChatEntryTypeLinkBlocked as u8 => {
1012 ChatEntryType::LinkBlocked
1013 }
1014 _ => ChatEntryType::Invalid,
1015 }
1016 }
1017}
1018
1019#[derive(Debug, Clone, Copy, PartialEq)]
1020#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1021pub enum ChatRoomEnterResponse {
1022 Success,
1023 DoesntExist,
1024 NotAllowed,
1025 Full,
1026 Error,
1027 Banned,
1028 Limited,
1029 ClanDisabled,
1030 CommunityBan,
1031 MemberBlockedYou,
1032 YouBlockedMember,
1033 RatelimitExceeded,
1034}
1035
1036impl From<u32> for ChatRoomEnterResponse {
1037 fn from(value: u32) -> Self {
1038 match value {
1039 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseSuccess as u32 => {
1040 ChatRoomEnterResponse::Success
1041 }
1042 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseDoesntExist as u32 => {
1043 ChatRoomEnterResponse::DoesntExist
1044 }
1045 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseNotAllowed as u32 => {
1046 ChatRoomEnterResponse::NotAllowed
1047 }
1048 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseFull as u32 => {
1049 ChatRoomEnterResponse::Full
1050 }
1051 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseError as u32 => {
1052 ChatRoomEnterResponse::Error
1053 }
1054 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseBanned as u32 => {
1055 ChatRoomEnterResponse::Banned
1056 }
1057 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseLimited as u32 => {
1058 ChatRoomEnterResponse::Limited
1059 }
1060 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseClanDisabled as u32 => {
1061 ChatRoomEnterResponse::ClanDisabled
1062 }
1063 x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseCommunityBan as u32 => {
1064 ChatRoomEnterResponse::CommunityBan
1065 }
1066 x if x
1067 == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseMemberBlockedYou as u32 =>
1068 {
1069 ChatRoomEnterResponse::MemberBlockedYou
1070 }
1071 x if x
1072 == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseYouBlockedMember as u32 =>
1073 {
1074 ChatRoomEnterResponse::YouBlockedMember
1075 }
1076 x if x
1077 == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseRatelimitExceeded
1078 as u32 =>
1079 {
1080 ChatRoomEnterResponse::RatelimitExceeded
1081 }
1082 _ => ChatRoomEnterResponse::Error,
1083 }
1084 }
1085}
1086
1087#[derive(Clone, Debug)]
1089#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1090pub struct LobbyChatMsg {
1091 pub lobby: LobbyId,
1093 pub user: SteamId,
1095 pub chat_entry_type: ChatEntryType,
1097 pub chat_id: i32,
1099}
1100
1101impl_callback!(cb: LobbyChatMsg_t => LobbyChatMsg {
1102 Self {
1103 lobby: LobbyId(cb.m_ulSteamIDLobby),
1104 user: SteamId(cb.m_ulSteamIDUser),
1105 chat_entry_type: cb.m_eChatEntryType.into(),
1106 chat_id: cb.m_iChatID as i32,
1107 }
1108});
1109
1110#[derive(Clone, Debug)]
1112#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1113pub struct LobbyChatUpdate {
1114 pub lobby: LobbyId,
1116 pub user_changed: SteamId,
1118 pub making_change: SteamId,
1120 pub member_state_change: ChatMemberStateChange,
1122}
1123
1124impl_callback!(cb: LobbyChatUpdate_t => LobbyChatUpdate {
1125 Self {
1126 lobby: LobbyId(cb.m_ulSteamIDLobby),
1127 user_changed: SteamId(cb.m_ulSteamIDUserChanged),
1128 making_change: SteamId(cb.m_ulSteamIDUserChanged),
1129 member_state_change: match cb.m_rgfChatMemberStateChange {
1130 x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeEntered as u32 => {
1131 ChatMemberStateChange::Entered
1132 }
1133 x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeLeft as u32 => {
1134 ChatMemberStateChange::Left
1135 }
1136 x if x
1137 == sys::EChatMemberStateChange::k_EChatMemberStateChangeDisconnected as u32 =>
1138 {
1139 ChatMemberStateChange::Disconnected
1140 }
1141 x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeKicked as u32 => {
1142 ChatMemberStateChange::Kicked
1143 }
1144 x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeBanned as u32 => {
1145 ChatMemberStateChange::Banned
1146 }
1147 _ => unreachable!(),
1148 },
1149 }
1150});
1151
1152#[derive(Clone, Debug)]
1154#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1155pub struct LobbyCreated {
1156 pub result: u32,
1158 pub lobby: LobbyId,
1160}
1161
1162impl_callback!(cb: LobbyCreated_t => LobbyCreated {
1163 Self {
1164 result: cb.m_eResult as u32,
1165 lobby: LobbyId(cb.m_ulSteamIDLobby),
1166 }
1167});
1168
1169#[derive(Clone, Debug)]
1172#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1173pub struct LobbyDataUpdate {
1174 pub lobby: LobbyId,
1176 pub member: SteamId,
1178 pub success: bool,
1180}
1181
1182impl_callback!(cb: LobbyDataUpdate_t => LobbyDataUpdate {
1183 Self {
1184 lobby: LobbyId(cb.m_ulSteamIDLobby),
1185 member: SteamId(cb.m_ulSteamIDMember),
1186 success: cb.m_bSuccess != 0,
1187 }
1188});
1189
1190#[derive(Clone, Debug)]
1192#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1193pub struct LobbyEnter {
1194 pub lobby: LobbyId,
1196 pub chat_permissions: u32,
1198 pub blocked: bool,
1200 pub chat_room_enter_response: ChatRoomEnterResponse,
1202}
1203
1204impl_callback!(cb: LobbyEnter_t => LobbyEnter {
1205 Self {
1206 lobby: LobbyId(cb.m_ulSteamIDLobby),
1207 chat_permissions: cb.m_rgfChatPermissions,
1208 blocked: cb.m_bLocked,
1209 chat_room_enter_response: cb.m_EChatRoomEnterResponse.into(),
1210 }
1211});
1212
1213#[test]
1214#[serial]
1215fn test_lobby() {
1216 let client = Client::init().unwrap();
1217 let mm = client.matchmaking();
1218
1219 mm.request_lobby_list(|v| {
1220 println!("List: {:?}", v);
1221 });
1222
1223 mm.create_lobby(LobbyType::Private, 4, |v| {
1224 println!("Create: {:?}", v);
1225 });
1226
1227 mm.set_lobby_list_filter(LobbyListFilter {
1228 string: Some(vec![StringFilter(
1229 LobbyKey::new("name"),
1230 "My Lobby",
1231 StringFilterKind::Equal,
1232 )]),
1233 ..Default::default()
1234 });
1235
1236 for _ in 0..100 {
1237 client.run_callbacks();
1238 ::std::thread::sleep(::std::time::Duration::from_millis(100));
1239 }
1240}
1241
1242#[test]
1243#[serial]
1244fn test_set_lobby_game_server() {
1245 let client = Client::init().unwrap();
1246 let mm = client.matchmaking();
1247
1248 let client2 = client.clone();
1249 mm.create_lobby(LobbyType::Private, 4, move |v| {
1250 println!("Create: {:?}", v);
1251 let mm2 = client2.matchmaking();
1252
1253 let lobby_id = v.unwrap(); let test_addr = SocketAddrV4::new(Ipv4Addr::new(192, 168, 1, 1), 27015); let test_steam_id = Some(SteamId(76561197960287930)); mm2.set_lobby_game_server(lobby_id, test_addr, test_steam_id);
1260
1261 if let Some((addr, steam_id)) = mm2.get_lobby_game_server(lobby_id) {
1263 assert_eq!(test_addr, addr, "Server address mismatch");
1264 assert_eq!(steam_id, test_steam_id, "Server SteamID mismatch");
1265 println!("Game server info verified: {addr} {steam_id:?}");
1266 } else {
1267 panic!("Failed to retrieve lobby game server info");
1268 }
1269
1270 let empty_lobby = LobbyId(54321);
1272 assert!(
1273 mm2.get_lobby_game_server(empty_lobby).is_none(),
1274 "Expected None for lobby with no game server set"
1275 );
1276 });
1277
1278 for _ in 0..100 {
1279 client.run_callbacks();
1280 ::std::thread::sleep(::std::time::Duration::from_millis(100));
1281 }
1282}