use std::{
fmt::Display,
net::{Ipv4Addr, SocketAddrV4},
};
use super::*;
#[cfg(test)]
use serial_test::serial;
pub struct Matchmaking {
pub(crate) mm: *mut sys::ISteamMatchmaking,
pub(crate) inner: Arc<Inner>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LobbyType {
Private,
FriendsOnly,
Public,
Invisible,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyId(pub(crate) u64);
impl LobbyId {
pub fn from_raw(id: u64) -> LobbyId {
LobbyId(id)
}
pub fn raw(&self) -> u64 {
self.0
}
}
impl Matchmaking {
pub fn request_lobby_list<F>(&self, cb: F)
where
F: FnOnce(SResult<Vec<LobbyId>>) + 'static + Send,
{
unsafe {
let api_call = sys::SteamAPI_ISteamMatchmaking_RequestLobbyList(self.mm);
register_call_result::<sys::LobbyMatchList_t, _>(
&self.inner,
api_call,
move |v, io_error| {
cb(if io_error {
Err(SteamError::IOFailure)
} else {
let mut out = Vec::with_capacity(v.m_nLobbiesMatching as usize);
for idx in 0..v.m_nLobbiesMatching {
out.push(LobbyId(sys::SteamAPI_ISteamMatchmaking_GetLobbyByIndex(
sys::SteamAPI_SteamMatchmaking_v009(),
idx as _,
)));
}
Ok(out)
})
},
);
}
}
pub fn create_lobby<F>(&self, ty: LobbyType, max_members: u32, cb: F)
where
F: FnOnce(SResult<LobbyId>) + 'static + Send,
{
assert!(max_members <= 250); unsafe {
let ty = match ty {
LobbyType::Private => sys::ELobbyType::k_ELobbyTypePrivate,
LobbyType::FriendsOnly => sys::ELobbyType::k_ELobbyTypeFriendsOnly,
LobbyType::Public => sys::ELobbyType::k_ELobbyTypePublic,
LobbyType::Invisible => sys::ELobbyType::k_ELobbyTypeInvisible,
};
let api_call =
sys::SteamAPI_ISteamMatchmaking_CreateLobby(self.mm, ty, max_members as _);
register_call_result::<sys::LobbyCreated_t, _>(
&self.inner,
api_call,
move |v, io_error| {
cb(if io_error {
Err(SteamError::IOFailure)
} else {
crate::to_steam_result(v.m_eResult).map(|_| LobbyId(v.m_ulSteamIDLobby))
})
},
);
}
}
pub fn join_lobby<F>(&self, lobby: LobbyId, cb: F)
where
F: FnOnce(Result<LobbyId, ()>) + 'static + Send,
{
unsafe {
let api_call = sys::SteamAPI_ISteamMatchmaking_JoinLobby(self.mm, lobby.0);
register_call_result::<sys::LobbyEnter_t, _>(
&self.inner,
api_call,
move |v, io_error| {
cb(if io_error || v.m_EChatRoomEnterResponse != 1 {
Err(())
} else {
Ok(LobbyId(v.m_ulSteamIDLobby))
})
},
);
}
}
pub fn lobby_data_count(&self, lobby: LobbyId) -> u32 {
unsafe { sys::SteamAPI_ISteamMatchmaking_GetLobbyDataCount(self.mm, lobby.0) as _ }
}
pub fn lobby_data(&self, lobby: LobbyId, key: &str) -> Option<String> {
let key = CString::new(key).unwrap();
unsafe {
let data = sys::SteamAPI_ISteamMatchmaking_GetLobbyData(self.mm, lobby.0, key.as_ptr());
CStr::from_ptr(data)
}
.to_str()
.ok()
.filter(|s| !s.is_empty())
.map(str::to_owned)
}
pub fn lobby_data_by_index(&self, lobby: LobbyId, idx: u32) -> Option<(String, String)> {
let mut key = [0 as c_char; sys::k_nMaxLobbyKeyLength as usize];
let mut value = [0 as c_char; sys::k_cubChatMetadataMax as usize];
unsafe {
let success = sys::SteamAPI_ISteamMatchmaking_GetLobbyDataByIndex(
self.mm,
lobby.0,
idx as _,
key.as_mut_ptr() as _,
key.len() as _,
value.as_mut_ptr() as _,
value.len() as _,
);
match success {
true => Some((
CStr::from_ptr(key.as_ptr()).to_string_lossy().into_owned(),
CStr::from_ptr(value.as_ptr())
.to_string_lossy()
.into_owned(),
)),
false => None,
}
}
}
pub fn set_lobby_data(&self, lobby: LobbyId, key: &str, value: &str) -> bool {
let key = CString::new(key).unwrap();
let value = CString::new(value).unwrap();
unsafe {
sys::SteamAPI_ISteamMatchmaking_SetLobbyData(
self.mm,
lobby.0,
key.as_ptr(),
value.as_ptr(),
)
}
}
pub fn delete_lobby_data(&self, lobby: LobbyId, key: &str) -> bool {
let key = CString::new(key).unwrap();
unsafe { sys::SteamAPI_ISteamMatchmaking_DeleteLobbyData(self.mm, lobby.0, key.as_ptr()) }
}
pub fn set_lobby_member_data(&self, lobby: LobbyId, key: &str, value: &str) {
let key = CString::new(key).unwrap();
let value = CString::new(value).unwrap();
unsafe {
sys::SteamAPI_ISteamMatchmaking_SetLobbyMemberData(
self.mm,
lobby.0,
key.as_ptr(),
value.as_ptr(),
)
}
}
pub fn get_lobby_member_data(
&self,
lobby: LobbyId,
user: SteamId,
key: &str,
) -> Option<String> {
let key = CString::new(key).unwrap();
unsafe {
let data = sys::SteamAPI_ISteamMatchmaking_GetLobbyMemberData(
self.mm,
lobby.0,
user.0,
key.as_ptr(),
);
CStr::from_ptr(data)
}
.to_str()
.map(str::to_owned)
.ok()
}
pub fn leave_lobby(&self, lobby: LobbyId) {
unsafe {
sys::SteamAPI_ISteamMatchmaking_LeaveLobby(self.mm, lobby.0);
}
}
pub fn lobby_member_limit(&self, lobby: LobbyId) -> Option<usize> {
unsafe {
let count = sys::SteamAPI_ISteamMatchmaking_GetLobbyMemberLimit(self.mm, lobby.0);
match count {
0 => None,
_ => Some(count as usize),
}
}
}
pub fn lobby_owner(&self, lobby: LobbyId) -> SteamId {
unsafe {
SteamId(sys::SteamAPI_ISteamMatchmaking_GetLobbyOwner(
self.mm, lobby.0,
))
}
}
pub fn lobby_member_count(&self, lobby: LobbyId) -> usize {
unsafe {
let count = sys::SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(self.mm, lobby.0);
count as usize
}
}
pub fn lobby_members(&self, lobby: LobbyId) -> Vec<SteamId> {
unsafe {
let count = sys::SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(self.mm, lobby.0);
let mut members = Vec::with_capacity(count as usize);
for idx in 0..count {
members.push(SteamId(
sys::SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(self.mm, lobby.0, idx),
))
}
members
}
}
pub fn set_lobby_joinable(&self, lobby: LobbyId, joinable: bool) -> bool {
unsafe { sys::SteamAPI_ISteamMatchmaking_SetLobbyJoinable(self.mm, lobby.0, joinable) }
}
pub fn send_lobby_chat_message(&self, lobby: LobbyId, msg: &[u8]) -> Result<(), SteamError> {
match unsafe {
steamworks_sys::SteamAPI_ISteamMatchmaking_SendLobbyChatMsg(
self.mm,
lobby.0,
msg.as_ptr().cast(),
msg.len() as i32,
)
} {
true => Ok(()),
false => Err(SteamError::IOFailure),
}
}
pub fn get_lobby_chat_entry<'a>(
&self,
lobby: LobbyId,
chat_id: i32,
buffer: &'a mut [u8],
) -> &'a [u8] {
let mut steam_user = sys::CSteamID {
m_steamid: sys::CSteamID_SteamID_t { m_unAll64Bits: 0 },
};
let mut chat_type = steamworks_sys::EChatEntryType::k_EChatEntryTypeInvalid;
unsafe {
let len = sys::SteamAPI_ISteamMatchmaking_GetLobbyChatEntry(
self.mm,
lobby.0,
chat_id,
&mut steam_user,
buffer.as_mut_ptr().cast(),
buffer.len() as _,
&mut chat_type,
);
return &buffer[0..len as usize];
}
}
pub fn add_request_lobby_list_string_filter(
&self,
StringFilter(LobbyKey(key), value, kind): StringFilter,
) -> &Self {
let key = CString::new(key).unwrap();
let value = CString::new(value).unwrap();
unsafe {
sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(
self.mm,
key.as_ptr(),
value.as_ptr(),
kind.into(),
);
}
self
}
pub fn add_request_lobby_list_numerical_filter(
&self,
NumberFilter(LobbyKey(key), value, comparison): NumberFilter,
) -> &Self {
let key = CString::new(key).unwrap();
unsafe {
sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListNumericalFilter(
self.mm,
key.as_ptr(),
value,
comparison.into(),
);
}
self
}
pub fn add_request_lobby_list_near_value_filter(
&self,
NearFilter(LobbyKey(key), value): NearFilter,
) -> &Self {
let key = CString::new(key).unwrap();
unsafe {
sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListNearValueFilter(
self.mm,
key.as_ptr(),
value,
);
}
self
}
pub fn set_request_lobby_list_slots_available_filter(&self, open_slots: u8) -> &Self {
unsafe {
sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListFilterSlotsAvailable(
self.mm,
open_slots as i32,
);
}
self
}
pub fn set_request_lobby_list_distance_filter(&self, distance: DistanceFilter) -> &Self {
unsafe {
sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter(
self.mm,
distance.into(),
);
}
self
}
pub fn set_request_lobby_list_result_count_filter(&self, count: u64) -> &Self {
unsafe {
sys::SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter(
self.mm,
count as i32,
);
}
self
}
pub fn set_lobby_list_filter(&self, filter: LobbyListFilter<'_>) -> &Self {
filter.string.into_iter().flatten().for_each(|str_filter| {
self.add_request_lobby_list_string_filter(str_filter);
});
filter.number.into_iter().flatten().for_each(|num_filter| {
self.add_request_lobby_list_numerical_filter(num_filter);
});
filter
.near_value
.into_iter()
.flatten()
.for_each(|near_filter| {
self.add_request_lobby_list_near_value_filter(near_filter);
});
if let Some(distance) = filter.distance {
self.set_request_lobby_list_distance_filter(distance);
}
if let Some(open_slots) = filter.open_slots {
self.set_request_lobby_list_slots_available_filter(open_slots);
}
if let Some(count) = filter.count {
self.set_request_lobby_list_result_count_filter(count);
}
self
}
pub fn set_lobby_game_server(
&self,
lobby: LobbyId,
server_addr: SocketAddrV4,
server_steam_id: Option<SteamId>,
) -> () {
unsafe {
sys::SteamAPI_ISteamMatchmaking_SetLobbyGameServer(
self.mm,
lobby.0,
server_addr.ip().to_bits(),
server_addr.port(),
server_steam_id.map(|id| id.0).unwrap_or(0),
)
}
}
pub fn get_lobby_game_server(&self, lobby: LobbyId) -> Option<(SocketAddrV4, Option<SteamId>)> {
unsafe {
let mut server_ip = 0;
let mut server_port = 0;
let mut server_steam_id = sys::CSteamID {
m_steamid: sys::CSteamID_SteamID_t { m_unAll64Bits: 0 },
};
let success = sys::SteamAPI_ISteamMatchmaking_GetLobbyGameServer(
self.mm,
lobby.0,
&mut server_ip,
&mut server_port,
&mut server_steam_id,
);
let server_addr = SocketAddrV4::new(Ipv4Addr::from_bits(server_ip), server_port);
let server_id = SteamId::from_raw(server_steam_id.m_steamid.m_unAll64Bits);
let server_id = (!server_id.is_invalid()).then_some(server_id);
if success {
Some((server_addr, server_id))
} else {
None
}
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyListFilter<'a> {
pub string: Option<StringFilters<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub number: Option<NumberFilters<'a>>,
#[cfg_attr(feature = "serde", serde(borrow))]
pub near_value: Option<NearFilters<'a>>,
pub open_slots: Option<u8>,
pub distance: Option<DistanceFilter>,
pub count: Option<u64>,
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyKey<'a>(pub(crate) &'a str);
impl<'a> std::ops::Deref for LobbyKey<'a> {
type Target = &'a str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Error)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyKeyTooLongError;
impl Display for LobbyKeyTooLongError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Lobby key is greater than {} characters",
sys::k_nMaxLobbyKeyLength
)
}
}
impl<'a> LobbyKey<'a> {
pub fn try_new(key: &'a str) -> Result<Self, LobbyKeyTooLongError> {
if key.len() > sys::k_nMaxLobbyKeyLength as usize {
Err(LobbyKeyTooLongError)
} else {
Ok(LobbyKey(key))
}
}
pub fn new(key: &'a str) -> Self {
Self::try_new(key).unwrap()
}
}
pub type StringFilters<'a> = Vec<StringFilter<'a>>;
pub type NumberFilters<'a> = Vec<NumberFilter<'a>>;
pub type NearFilters<'a> = Vec<NearFilter<'a>>;
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StringFilter<'a>(
#[cfg_attr(feature = "serde", serde(borrow))] pub LobbyKey<'a>,
pub &'a str,
pub StringFilterKind,
);
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum StringFilterKind {
#[default]
EqualToOrLessThan,
LessThan,
Equal,
GreaterThan,
EqualToOrGreaterThan,
NotEqual,
}
impl From<StringFilterKind> for sys::ELobbyComparison {
fn from(filter: StringFilterKind) -> Self {
match filter {
StringFilterKind::EqualToOrLessThan => {
sys::ELobbyComparison::k_ELobbyComparisonEqualToOrLessThan
}
StringFilterKind::LessThan => sys::ELobbyComparison::k_ELobbyComparisonLessThan,
StringFilterKind::Equal => sys::ELobbyComparison::k_ELobbyComparisonEqual,
StringFilterKind::GreaterThan => sys::ELobbyComparison::k_ELobbyComparisonGreaterThan,
StringFilterKind::EqualToOrGreaterThan => {
sys::ELobbyComparison::k_ELobbyComparisonEqualToOrGreaterThan
}
StringFilterKind::NotEqual => sys::ELobbyComparison::k_ELobbyComparisonNotEqual,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NumberFilter<'a>(
#[cfg_attr(feature = "serde", serde(borrow))] pub LobbyKey<'a>,
pub i32,
pub ComparisonFilter,
);
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NearFilter<'a>(
#[cfg_attr(feature = "serde", serde(borrow))] pub LobbyKey<'a>,
pub i32,
);
impl<'a> LobbyListFilter<'a> {
pub fn set_string(mut self, string: Option<StringFilters<'a>>) -> Self {
self.string = string;
self
}
pub fn set_number(mut self, number: Option<NumberFilters<'a>>) -> Self {
self.number = number;
self
}
pub fn set_near_value(mut self, near_value: Option<NearFilters<'a>>) -> Self {
self.near_value = near_value;
self
}
pub fn set_open_slots(mut self, open_slots: Option<u8>) -> Self {
self.open_slots = open_slots;
self
}
pub fn set_distance(mut self, distance: Option<DistanceFilter>) -> Self {
self.distance = distance;
self
}
pub fn set_count(mut self, count: Option<u64>) -> Self {
self.count = count;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum DistanceFilter {
Close,
#[default]
Default,
Far,
Worldwide,
}
impl From<DistanceFilter> for sys::ELobbyDistanceFilter {
fn from(filter: DistanceFilter) -> Self {
match filter {
DistanceFilter::Close => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterClose,
DistanceFilter::Default => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterDefault,
DistanceFilter::Far => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterFar,
DistanceFilter::Worldwide => sys::ELobbyDistanceFilter::k_ELobbyDistanceFilterWorldwide,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ComparisonFilter {
#[default]
Equal,
NotEqual,
GreaterThan,
GreaterThanEqualTo,
LessThan,
LessThanEqualTo,
}
impl From<ComparisonFilter> for sys::ELobbyComparison {
fn from(filter: ComparisonFilter) -> Self {
match filter {
ComparisonFilter::Equal => sys::ELobbyComparison::k_ELobbyComparisonEqual,
ComparisonFilter::NotEqual => sys::ELobbyComparison::k_ELobbyComparisonNotEqual,
ComparisonFilter::GreaterThan => sys::ELobbyComparison::k_ELobbyComparisonGreaterThan,
ComparisonFilter::GreaterThanEqualTo => {
sys::ELobbyComparison::k_ELobbyComparisonEqualToOrGreaterThan
}
ComparisonFilter::LessThan => sys::ELobbyComparison::k_ELobbyComparisonLessThan,
ComparisonFilter::LessThanEqualTo => {
sys::ELobbyComparison::k_ELobbyComparisonEqualToOrLessThan
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ChatMemberStateChange {
Entered,
Left,
Disconnected,
Kicked,
Banned,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ChatEntryType {
Invalid,
ChatMsg,
Typing,
InviteGame,
Emote,
LeftConversation,
Entered,
WasKicked,
WasBanned,
Disconnected,
HistoricalChat,
LinkBlocked,
}
impl From<u8> for ChatEntryType {
fn from(value: u8) -> Self {
match value {
x if x == sys::EChatEntryType::k_EChatEntryTypeInvalid as u8 => ChatEntryType::Invalid,
x if x == sys::EChatEntryType::k_EChatEntryTypeChatMsg as u8 => ChatEntryType::ChatMsg,
x if x == sys::EChatEntryType::k_EChatEntryTypeTyping as u8 => ChatEntryType::Typing,
x if x == sys::EChatEntryType::k_EChatEntryTypeInviteGame as u8 => {
ChatEntryType::InviteGame
}
x if x == sys::EChatEntryType::k_EChatEntryTypeEmote as u8 => ChatEntryType::Emote,
x if x == sys::EChatEntryType::k_EChatEntryTypeLeftConversation as u8 => {
ChatEntryType::LeftConversation
}
x if x == sys::EChatEntryType::k_EChatEntryTypeEntered as u8 => ChatEntryType::Entered,
x if x == sys::EChatEntryType::k_EChatEntryTypeWasKicked as u8 => {
ChatEntryType::WasKicked
}
x if x == sys::EChatEntryType::k_EChatEntryTypeWasBanned as u8 => {
ChatEntryType::WasBanned
}
x if x == sys::EChatEntryType::k_EChatEntryTypeDisconnected as u8 => {
ChatEntryType::Disconnected
}
x if x == sys::EChatEntryType::k_EChatEntryTypeHistoricalChat as u8 => {
ChatEntryType::HistoricalChat
}
x if x == sys::EChatEntryType::k_EChatEntryTypeLinkBlocked as u8 => {
ChatEntryType::LinkBlocked
}
_ => ChatEntryType::Invalid,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ChatRoomEnterResponse {
Success,
DoesntExist,
NotAllowed,
Full,
Error,
Banned,
Limited,
ClanDisabled,
CommunityBan,
MemberBlockedYou,
YouBlockedMember,
RatelimitExceeded,
}
impl From<u32> for ChatRoomEnterResponse {
fn from(value: u32) -> Self {
match value {
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseSuccess as u32 => {
ChatRoomEnterResponse::Success
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseDoesntExist as u32 => {
ChatRoomEnterResponse::DoesntExist
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseNotAllowed as u32 => {
ChatRoomEnterResponse::NotAllowed
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseFull as u32 => {
ChatRoomEnterResponse::Full
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseError as u32 => {
ChatRoomEnterResponse::Error
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseBanned as u32 => {
ChatRoomEnterResponse::Banned
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseLimited as u32 => {
ChatRoomEnterResponse::Limited
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseClanDisabled as u32 => {
ChatRoomEnterResponse::ClanDisabled
}
x if x == sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseCommunityBan as u32 => {
ChatRoomEnterResponse::CommunityBan
}
x if x
== sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseMemberBlockedYou as u32 =>
{
ChatRoomEnterResponse::MemberBlockedYou
}
x if x
== sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseYouBlockedMember as u32 =>
{
ChatRoomEnterResponse::YouBlockedMember
}
x if x
== sys::EChatRoomEnterResponse::k_EChatRoomEnterResponseRatelimitExceeded
as u32 =>
{
ChatRoomEnterResponse::RatelimitExceeded
}
_ => ChatRoomEnterResponse::Error,
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyChatMsg {
pub lobby: LobbyId,
pub user: SteamId,
pub chat_entry_type: ChatEntryType,
pub chat_id: i32,
}
impl_callback!(cb: LobbyChatMsg_t => LobbyChatMsg {
Self {
lobby: LobbyId(cb.m_ulSteamIDLobby),
user: SteamId(cb.m_ulSteamIDUser),
chat_entry_type: cb.m_eChatEntryType.into(),
chat_id: cb.m_iChatID as i32,
}
});
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyChatUpdate {
pub lobby: LobbyId,
pub user_changed: SteamId,
pub making_change: SteamId,
pub member_state_change: ChatMemberStateChange,
}
impl_callback!(cb: LobbyChatUpdate_t => LobbyChatUpdate {
Self {
lobby: LobbyId(cb.m_ulSteamIDLobby),
user_changed: SteamId(cb.m_ulSteamIDUserChanged),
making_change: SteamId(cb.m_ulSteamIDUserChanged),
member_state_change: match cb.m_rgfChatMemberStateChange {
x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeEntered as u32 => {
ChatMemberStateChange::Entered
}
x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeLeft as u32 => {
ChatMemberStateChange::Left
}
x if x
== sys::EChatMemberStateChange::k_EChatMemberStateChangeDisconnected as u32 =>
{
ChatMemberStateChange::Disconnected
}
x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeKicked as u32 => {
ChatMemberStateChange::Kicked
}
x if x == sys::EChatMemberStateChange::k_EChatMemberStateChangeBanned as u32 => {
ChatMemberStateChange::Banned
}
_ => unreachable!(),
},
}
});
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyCreated {
pub result: u32,
pub lobby: LobbyId,
}
impl_callback!(cb: LobbyCreated_t => LobbyCreated {
Self {
result: cb.m_eResult as u32,
lobby: LobbyId(cb.m_ulSteamIDLobby),
}
});
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyDataUpdate {
pub lobby: LobbyId,
pub member: SteamId,
pub success: bool,
}
impl_callback!(cb: LobbyDataUpdate_t => LobbyDataUpdate {
Self {
lobby: LobbyId(cb.m_ulSteamIDLobby),
member: SteamId(cb.m_ulSteamIDMember),
success: cb.m_bSuccess != 0,
}
});
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LobbyEnter {
pub lobby: LobbyId,
pub chat_permissions: u32,
pub blocked: bool,
pub chat_room_enter_response: ChatRoomEnterResponse,
}
impl_callback!(cb: LobbyEnter_t => LobbyEnter {
Self {
lobby: LobbyId(cb.m_ulSteamIDLobby),
chat_permissions: cb.m_rgfChatPermissions,
blocked: cb.m_bLocked,
chat_room_enter_response: cb.m_EChatRoomEnterResponse.into(),
}
});
#[test]
#[serial]
fn test_lobby() {
let client = Client::init().unwrap();
let mm = client.matchmaking();
mm.request_lobby_list(|v| {
println!("List: {:?}", v);
});
mm.create_lobby(LobbyType::Private, 4, |v| {
println!("Create: {:?}", v);
});
mm.set_lobby_list_filter(LobbyListFilter {
string: Some(vec![StringFilter(
LobbyKey::new("name"),
"My Lobby",
StringFilterKind::Equal,
)]),
..Default::default()
});
for _ in 0..100 {
client.run_callbacks();
::std::thread::sleep(::std::time::Duration::from_millis(100));
}
}
#[test]
#[serial]
fn test_set_lobby_game_server() {
let client = Client::init().unwrap();
let mm = client.matchmaking();
let client2 = client.clone();
mm.create_lobby(LobbyType::Private, 4, move |v| {
println!("Create: {:?}", v);
let mm2 = client2.matchmaking();
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);
if let Some((addr, steam_id)) = mm2.get_lobby_game_server(lobby_id) {
assert_eq!(test_addr, addr, "Server address mismatch");
assert_eq!(steam_id, test_steam_id, "Server SteamID mismatch");
println!("Game server info verified: {addr} {steam_id:?}");
} else {
panic!("Failed to retrieve lobby game server info");
}
let empty_lobby = LobbyId(54321);
assert!(
mm2.get_lobby_game_server(empty_lobby).is_none(),
"Expected None for lobby with no game server set"
);
});
for _ in 0..100 {
client.run_callbacks();
::std::thread::sleep(::std::time::Duration::from_millis(100));
}
}