using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Facepunch.Steamworks.Data; namespace Facepunch.Steamworks { /// /// Undocumented Parental Settings /// public class SteamFriends : SteamClientClass { internal static ISteamFriends Internal => Interface as ISteamFriends; internal override void InitializeInterface( bool server ) { SetInterface( server, new ISteamFriends( server ) ); richPresence = new Dictionary(); InstallEvents(); } static Dictionary richPresence; internal void InstallEvents() { Dispatch.Install( x => OnPersonaStateChange?.Invoke( new Friend( x.SteamID ) ) ); Dispatch.Install( x => OnGameRichPresenceJoinRequested?.Invoke( new Friend( x.SteamIDFriend), x.ConnectUTF8() ) ); Dispatch.Install( OnFriendChatMessage ); Dispatch.Install( x => OnGameOverlayActivated?.Invoke( x.Active != 0 ) ); Dispatch.Install( x => OnGameServerChangeRequested?.Invoke( x.ServerUTF8(), x.PasswordUTF8() ) ); Dispatch.Install( x => OnGameLobbyJoinRequested?.Invoke( new Lobby( x.SteamIDLobby ), x.SteamIDFriend ) ); Dispatch.Install( x => OnFriendRichPresenceUpdate?.Invoke( new Friend( x.SteamIDFriend ) ) ); } /// /// Called when chat message has been received from a friend. You'll need to turn on /// ListenForFriendsMessages to recieve this. (friend, msgtype, message) /// public static event Action OnChatMessage; /// /// called when a friends' status changes /// public static event Action OnPersonaStateChange; /// /// Called when the user tries to join a game from their friends list /// rich presence will have been set with the "connect" key which is set here /// public static event Action OnGameRichPresenceJoinRequested; /// /// Posted when game overlay activates or deactivates /// the game can use this to be pause or resume single player games /// public static event Action OnGameOverlayActivated; /// /// Called when the user tries to join a different game server from their friends list /// game client should attempt to connect to specified server when this is received /// public static event Action OnGameServerChangeRequested; /// /// Called when the user tries to join a lobby from their friends list /// game client should attempt to connect to specified lobby when this is received /// public static event Action OnGameLobbyJoinRequested; /// /// Callback indicating updated data about friends rich presence information /// public static event Action OnFriendRichPresenceUpdate; static unsafe void OnFriendChatMessage( GameConnectedFriendChatMsg_t data ) { if ( OnChatMessage == null ) return; var friend = new Friend( data.SteamIDUser ); var buffer = Helpers.TakeMemory(); var type = ChatEntryType.ChatMsg; var len = Internal.GetFriendMessage( data.SteamIDUser, data.MessageID, buffer, Helpers.MemoryBufferSize, ref type ); if ( len == 0 && type == ChatEntryType.Invalid ) return; var typeName = type.ToString(); var message = Helpers.MemoryToString( buffer ); OnChatMessage( friend, typeName, message ); } private static IEnumerable GetFriendsWithFlag(FriendFlags flag) { for ( int i=0; i GetFriends() { return GetFriendsWithFlag(FriendFlags.Immediate); } public static IEnumerable GetBlocked() { return GetFriendsWithFlag(FriendFlags.Blocked); } public static IEnumerable GetFriendsRequested() { return GetFriendsWithFlag( FriendFlags.FriendshipRequested ); } public static IEnumerable GetFriendsClanMembers() { return GetFriendsWithFlag( FriendFlags.ClanMember ); } public static IEnumerable GetFriendsOnGameServer() { return GetFriendsWithFlag( FriendFlags.OnGameServer ); } public static IEnumerable GetFriendsRequestingFriendship() { return GetFriendsWithFlag( FriendFlags.RequestingFriendship ); } public static IEnumerable GetPlayedWith() { for ( int i = 0; i < Internal.GetCoplayFriendCount(); i++ ) { yield return new Friend( Internal.GetCoplayFriend( i ) ); } } public static IEnumerable GetFromSource( SteamId steamid ) { for ( int i = 0; i < Internal.GetFriendCountFromSource( steamid ); i++ ) { yield return new Friend( Internal.GetFriendFromSourceByIndex( steamid, i ) ); } } /// /// The dialog to open. Valid options are: /// "friends", /// "community", /// "players", /// "settings", /// "officialgamegroup", /// "stats", /// "achievements". /// public static void OpenOverlay( string type ) => Internal.ActivateGameOverlay( type ); /// /// "steamid" - Opens the overlay web browser to the specified user or groups profile. /// "chat" - Opens a chat window to the specified user, or joins the group chat. /// "jointrade" - Opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API. /// "stats" - Opens the overlay web browser to the specified user's stats. /// "achievements" - Opens the overlay web browser to the specified user's achievements. /// "friendadd" - Opens the overlay in minimal mode prompting the user to add the target user as a friend. /// "friendremove" - Opens the overlay in minimal mode prompting the user to remove the target friend. /// "friendrequestaccept" - Opens the overlay in minimal mode prompting the user to accept an incoming friend invite. /// "friendrequestignore" - Opens the overlay in minimal mode prompting the user to ignore an incoming friend invite. /// public static void OpenUserOverlay( SteamId id, string type ) => Internal.ActivateGameOverlayToUser( type, id ); /// /// Activates the Steam Overlay to the Steam store page for the provided app. /// public static void OpenStoreOverlay( AppId id ) => Internal.ActivateGameOverlayToStore( id.Value, OverlayToStoreFlag.None ); /// /// Activates Steam Overlay web browser directly to the specified URL. /// public static void OpenWebOverlay( string url, bool modal = false ) => Internal.ActivateGameOverlayToWebPage( url, modal ? ActivateGameOverlayToWebPageMode.Modal : ActivateGameOverlayToWebPageMode.Default ); /// /// Activates the Steam Overlay to open the invite dialog. Invitations sent from this dialog will be for the provided lobby. /// public static void OpenGameInviteOverlay( SteamId lobby ) => Internal.ActivateGameOverlayInviteDialog( lobby ); /// /// Mark a target user as 'played with'. /// NOTE: The current user must be in game with the other player for the association to work. /// public static void SetPlayedWith( SteamId steamid ) => Internal.SetPlayedWith( steamid ); /// /// Requests the persona name and optionally the avatar of a specified user. /// NOTE: It's a lot slower to download avatars and churns the local cache, so if you don't need avatars, don't request them. /// returns true if we're fetching the data, false if we already have it /// public static bool RequestUserInformation( SteamId steamid, bool nameonly = true ) => Internal.RequestUserInformation( steamid, nameonly ); internal static async Task CacheUserInformationAsync( SteamId steamid, bool nameonly ) { // Got it straight away, skip any waiting. if ( !RequestUserInformation( steamid, nameonly ) ) return; await Task.Delay( 100 ); while ( RequestUserInformation( steamid, nameonly ) ) { await Task.Delay( 50 ); } // // And extra wait here seems to solve avatars loading as [?] // await Task.Delay( 500 ); } public static async Task GetSmallAvatarAsync( SteamId steamid ) { await CacheUserInformationAsync( steamid, false ); return SteamUtils.GetImage( Internal.GetSmallFriendAvatar( steamid ) ); } public static async Task GetMediumAvatarAsync( SteamId steamid ) { await CacheUserInformationAsync( steamid, false ); return SteamUtils.GetImage( Internal.GetMediumFriendAvatar( steamid ) ); } public static async Task GetLargeAvatarAsync( SteamId steamid ) { await CacheUserInformationAsync( steamid, false ); var imageid = Internal.GetLargeFriendAvatar( steamid ); // Wait for the image to download while ( imageid == -1 ) { await Task.Delay( 50 ); imageid = Internal.GetLargeFriendAvatar( steamid ); } return SteamUtils.GetImage( imageid ); } /// /// Find a rich presence value by key for current user. Will be null if not found. /// public static string GetRichPresence( string key ) { if ( richPresence.TryGetValue( key, out var val ) ) return val; return null; } /// /// Sets a rich presence value by key for current user. /// public static bool SetRichPresence( string key, string value ) { bool success = Internal.SetRichPresence( key, value ); if ( success ) richPresence[key] = value; return success; } /// /// Clears all of the current user's rich presence data. /// public static void ClearRichPresence() { richPresence.Clear(); Internal.ClearRichPresence(); } static bool _listenForFriendsMessages; /// /// Listens for Steam friends chat messages. /// You can then show these chats inline in the game. For example with a Blizzard style chat message system or the chat system in Dota 2. /// After enabling this you will receive callbacks when ever the user receives a chat message. /// public static bool ListenForFriendsMessages { get => _listenForFriendsMessages; set { _listenForFriendsMessages = value; Internal.SetListenForFriendsMessages( value ); } } public static async Task IsFollowing(SteamId steamID) { var r = await Internal.IsFollowing(steamID); return r.Value.IsFollowing; } public static async Task GetFollowerCount(SteamId steamID) { var r = await Internal.GetFollowerCount(steamID); return r.Value.Count; } public static async Task GetFollowingList() { int resultCount = 0; var steamIds = new List(); FriendsEnumerateFollowingList_t? result; do { if ( (result = await Internal.EnumerateFollowingList((uint)resultCount)) != null) { resultCount += result.Value.ResultsReturned; Array.ForEach(result.Value.GSteamID, id => { if (id > 0) steamIds.Add(id); }); } } while (result != null && resultCount < result.Value.TotalResultCount); return steamIds.ToArray(); } } }