using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Steamworks.Data; namespace Steamworks { /// /// Undocumented Parental Settings /// public static class SteamFriends { static ISteamFriends _internal; internal static ISteamFriends Internal { get { SteamClient.ValidCheck(); if ( _internal == null ) { _internal = new ISteamFriends(); _internal.Init(); richPresence = new Dictionary(); } return _internal; } } internal static void Shutdown() { _internal = null; } static Dictionary richPresence; internal static void InstallEvents() { FriendStateChange_t.Install( x => OnPersonaStateChange?.Invoke( new Friend( x.SteamID ) ) ); GameRichPresenceJoinRequested_t.Install( x => OnGameRichPresenceJoinRequested?.Invoke( new Friend( x.SteamIDFriend), x.ConnectUTF8() ) ); GameConnectedFriendChatMsg_t.Install( OnFriendChatMessage ); GameOverlayActivated_t.Install( x => OnGameOverlayActivated?.Invoke() ); GameServerChangeRequested_t.Install( x => OnGameServerChangeRequested?.Invoke( x.ServerUTF8(), x.PasswordUTF8() ) ); GameLobbyJoinRequested_t.Install( x => OnGameLobbyJoinRequested?.Invoke( new Lobby( x.SteamIDLobby ), x.SteamIDFriend ) ); FriendRichPresenceUpdate_t.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.TakeBuffer( 1024 * 32 ); var type = ChatEntryType.ChatMsg; fixed ( byte* ptr = buffer ) { var len = Internal.GetFriendMessage( data.SteamIDUser, data.MessageID, (IntPtr)ptr, buffer.Length, ref type ); if ( len == 0 && type == ChatEntryType.Invalid ) return; var typeName = type.ToString(); var message = Encoding.UTF8.GetString( buffer, 0, len ); OnChatMessage( friend, typeName, message ); } } public static string GetFriendPersonaName( SteamId steamId ) { return Internal.GetFriendPersonaName(steamId); } public static IEnumerable GetFriends() { for ( int i=0; i GetBlocked() { for ( int i = 0; i < Internal.GetFriendCount( (int)FriendFlags.Blocked ); i++ ) { yield return new Friend( Internal.GetFriendByIndex( i, (int)FriendFlags.Blocked) ); } } public static IEnumerable GetPlayedWith() { for ( int i = 0; i < Internal.GetCoplayFriendCount(); i++ ) { yield return new Friend( Internal.GetCoplayFriend( 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 ) { richPresence[key] = value; return Internal.SetRichPresence( key, value ); } /// /// 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 ); } } } }