2019-04-15 15:41:01 +01:00
using System ;
using System.Collections.Generic ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Threading.Tasks ;
2019-04-16 15:00:22 +01:00
using Steamworks.Data ;
2019-04-15 15:41:01 +01:00
namespace Steamworks
{
/// <summary>
/// Undocumented Parental Settings
/// </summary>
2020-02-23 18:34:56 +00:00
public class SteamFriends : SteamClientClass < SteamFriends >
2019-04-15 15:41:01 +01:00
{
2020-02-23 11:25:56 +00:00
internal static ISteamFriends Internal = > Interface as ISteamFriends ;
2019-04-15 16:47:21 +01:00
2020-02-22 20:23:19 +00:00
internal override void InitializeInterface ( bool server )
2019-04-29 12:55:38 +01:00
{
2020-02-23 11:25:56 +00:00
SetInterface ( server , new ISteamFriends ( server ) ) ;
2020-02-22 20:23:19 +00:00
richPresence = new Dictionary < string , string > ( ) ;
2020-02-25 19:03:56 +00:00
InstallEvents ( ) ;
2019-04-29 12:55:38 +01:00
}
2019-04-15 15:41:01 +01:00
2019-04-15 16:47:21 +01:00
static Dictionary < string , string > richPresence ;
2020-02-25 19:03:56 +00:00
internal void InstallEvents ( )
2019-04-15 15:41:01 +01:00
{
2020-02-22 20:23:19 +00:00
Dispatch . Install < PersonaStateChange_t > ( x = > OnPersonaStateChange ? . Invoke ( new Friend ( x . SteamID ) ) ) ;
Dispatch . Install < GameRichPresenceJoinRequested_t > ( x = > OnGameRichPresenceJoinRequested ? . Invoke ( new Friend ( x . SteamIDFriend ) , x . ConnectUTF8 ( ) ) ) ;
Dispatch . Install < GameConnectedFriendChatMsg_t > ( OnFriendChatMessage ) ;
2020-08-28 13:07:27 +02:00
Dispatch . Install < GameConnectedClanChatMsg_t > ( OnGameConnectedClanChatMessage ) ;
2020-02-22 20:49:47 +00:00
Dispatch . Install < GameOverlayActivated_t > ( x = > OnGameOverlayActivated ? . Invoke ( x . Active ! = 0 ) ) ;
2020-02-22 20:23:19 +00:00
Dispatch . Install < GameServerChangeRequested_t > ( x = > OnGameServerChangeRequested ? . Invoke ( x . ServerUTF8 ( ) , x . PasswordUTF8 ( ) ) ) ;
Dispatch . Install < GameLobbyJoinRequested_t > ( x = > OnGameLobbyJoinRequested ? . Invoke ( new Lobby ( x . SteamIDLobby ) , x . SteamIDFriend ) ) ;
Dispatch . Install < FriendRichPresenceUpdate_t > ( x = > OnFriendRichPresenceUpdate ? . Invoke ( new Friend ( x . SteamIDFriend ) ) ) ;
2020-08-18 11:36:50 +01:00
Dispatch . Install < OverlayBrowserProtocolNavigation_t > ( x = > OnOverlayBrowserProtocol ? . Invoke ( x . RgchURIUTF8 ( ) ) ) ;
2019-04-15 15:41:01 +01:00
}
2019-04-15 17:11:01 +01:00
/// <summary>
/// Called when chat message has been received from a friend. You'll need to turn on
/// ListenForFriendsMessages to recieve this. (friend, msgtype, message)
/// </summary>
public static event Action < Friend , string , string > OnChatMessage ;
2020-08-28 13:07:27 +02:00
/// <summary>
/// Called when a chat message has been received in a Steam group chat that we are in. Associated Functions: JoinClanChatRoom. (friend, msgtype, message)
/// </summary>
public static event Action < Friend , string , string > OnClanChatMessage ;
2019-04-15 17:11:01 +01:00
/// <summary>
/// called when a friends' status changes
/// </summary>
public static event Action < Friend > OnPersonaStateChange ;
/// <summary>
/// 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
/// </summary>
public static event Action < Friend , string > OnGameRichPresenceJoinRequested ;
/// <summary>
/// Posted when game overlay activates or deactivates
/// the game can use this to be pause or resume single player games
/// </summary>
2020-02-09 09:25:43 -05:00
public static event Action < bool > OnGameOverlayActivated ;
2019-04-15 17:11:01 +01:00
/// <summary>
/// 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
/// </summary>
public static event Action < string , string > OnGameServerChangeRequested ;
/// <summary>
/// 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
/// </summary>
2019-05-09 16:18:34 +01:00
public static event Action < Lobby , SteamId > OnGameLobbyJoinRequested ;
2019-04-15 17:11:01 +01:00
/// <summary>
/// Callback indicating updated data about friends rich presence information
/// </summary>
public static event Action < Friend > OnFriendRichPresenceUpdate ;
2020-08-18 11:36:50 +01:00
/// <summary>
/// Dispatched when an overlay browser instance is navigated to a
/// protocol/scheme registered by RegisterProtocolInOverlayBrowser()
/// </summary>
public static event Action < string > OnOverlayBrowserProtocol ;
2019-04-15 17:11:01 +01:00
static unsafe void OnFriendChatMessage ( GameConnectedFriendChatMsg_t data )
{
if ( OnChatMessage = = null ) return ;
var friend = new Friend ( data . SteamIDUser ) ;
2020-03-31 08:24:12 +01:00
var buffer = Helpers . TakeMemory ( ) ;
2019-04-15 17:11:01 +01:00
var type = ChatEntryType . ChatMsg ;
2020-03-31 08:24:12 +01:00
var len = Internal . GetFriendMessage ( data . SteamIDUser , data . MessageID , buffer , Helpers . MemoryBufferSize , ref type ) ;
2019-04-15 17:11:01 +01:00
2020-03-31 08:24:12 +01:00
if ( len = = 0 & & type = = ChatEntryType . Invalid )
return ;
2019-04-15 17:11:01 +01:00
2020-03-31 08:24:12 +01:00
var typeName = type . ToString ( ) ;
var message = Helpers . MemoryToString ( buffer ) ;
2019-04-15 17:11:01 +01:00
2020-03-31 08:24:12 +01:00
OnChatMessage ( friend , typeName , message ) ;
2019-04-15 17:11:01 +01:00
}
2020-08-28 13:07:27 +02:00
static unsafe void OnGameConnectedClanChatMessage ( GameConnectedClanChatMsg_t data )
{
if ( OnClanChatMessage = = null ) return ;
var friend = new Friend ( data . SteamIDUser ) ;
var buffer = Helpers . TakeMemory ( ) ;
var type = ChatEntryType . ChatMsg ;
SteamId chatter = data . SteamIDUser ;
var len = Internal . GetClanChatMessage ( data . SteamIDClanChat , data . MessageID , buffer , Helpers . MemoryBufferSize , ref type , ref chatter ) ;
if ( len = = 0 & & type = = ChatEntryType . Invalid )
return ;
var typeName = type . ToString ( ) ;
var message = Helpers . MemoryToString ( buffer ) ;
OnClanChatMessage ( friend , typeName , message ) ;
}
2020-02-24 13:07:50 +00:00
private static IEnumerable < Friend > GetFriendsWithFlag ( FriendFlags flag )
2019-04-15 15:41:01 +01:00
{
2019-10-11 16:44:27 +02:00
for ( int i = 0 ; i < Internal . GetFriendCount ( ( int ) flag ) ; i + + )
2019-04-15 15:41:01 +01:00
{
2019-10-11 16:44:27 +02:00
yield return new Friend ( Internal . GetFriendByIndex ( i , ( int ) flag ) ) ;
2019-04-15 15:41:01 +01:00
}
}
2019-10-11 16:44:27 +02:00
public static IEnumerable < Friend > GetFriends ( )
{
return GetFriendsWithFlag ( FriendFlags . Immediate ) ;
}
2019-04-15 15:41:01 +01:00
public static IEnumerable < Friend > GetBlocked ( )
{
2019-10-11 16:44:27 +02:00
return GetFriendsWithFlag ( FriendFlags . Blocked ) ;
2019-04-15 15:41:01 +01:00
}
2020-02-24 13:07:50 +00:00
public static IEnumerable < Friend > GetFriendsRequested ( )
{
return GetFriendsWithFlag ( FriendFlags . FriendshipRequested ) ;
}
public static IEnumerable < Friend > GetFriendsClanMembers ( )
{
return GetFriendsWithFlag ( FriendFlags . ClanMember ) ;
}
public static IEnumerable < Friend > GetFriendsOnGameServer ( )
{
return GetFriendsWithFlag ( FriendFlags . OnGameServer ) ;
}
public static IEnumerable < Friend > GetFriendsRequestingFriendship ( )
{
return GetFriendsWithFlag ( FriendFlags . RequestingFriendship ) ;
}
2019-04-15 15:41:01 +01:00
public static IEnumerable < Friend > GetPlayedWith ( )
{
2019-05-01 13:22:50 +01:00
for ( int i = 0 ; i < Internal . GetCoplayFriendCount ( ) ; i + + )
2019-04-15 15:41:01 +01:00
{
2019-05-01 13:22:50 +01:00
yield return new Friend ( Internal . GetCoplayFriend ( i ) ) ;
2019-04-15 15:41:01 +01:00
}
}
2019-09-19 16:25:43 +03:00
public static IEnumerable < Friend > GetFromSource ( SteamId steamid )
{
for ( int i = 0 ; i < Internal . GetFriendCountFromSource ( steamid ) ; i + + )
{
yield return new Friend ( Internal . GetFriendFromSourceByIndex ( steamid , i ) ) ;
}
}
2020-05-25 16:51:01 +02:00
public static IEnumerable < Clan > GetClans ( )
{
for ( int i = 0 ; i < Internal . GetClanCount ( ) ; i + + )
{
yield return new Clan ( Internal . GetClanByIndex ( i ) ) ;
}
}
2019-04-15 15:54:10 +01:00
/// <summary>
/// The dialog to open. Valid options are:
/// "friends",
/// "community",
/// "players",
/// "settings",
/// "officialgamegroup",
/// "stats",
/// "achievements".
/// </summary>
public static void OpenOverlay ( string type ) = > Internal . ActivateGameOverlay ( type ) ;
/// <summary>
/// "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.
/// </summary>
2019-04-16 11:45:44 +01:00
public static void OpenUserOverlay ( SteamId id , string type ) = > Internal . ActivateGameOverlayToUser ( type , id ) ;
2019-04-15 15:54:10 +01:00
/// <summary>
/// Activates the Steam Overlay to the Steam store page for the provided app.
/// </summary>
2020-08-31 18:03:45 -07:00
public static void OpenStoreOverlay ( AppId id , OverlayToStoreFlag overlayToStoreFlag = OverlayToStoreFlag . None ) = > Internal . ActivateGameOverlayToStore ( id . Value , overlayToStoreFlag ) ;
2019-04-15 15:41:01 +01:00
2019-04-15 15:54:10 +01:00
/// <summary>
/// Activates Steam Overlay web browser directly to the specified URL.
/// </summary>
public static void OpenWebOverlay ( string url , bool modal = false ) = > Internal . ActivateGameOverlayToWebPage ( url , modal ? ActivateGameOverlayToWebPageMode . Modal : ActivateGameOverlayToWebPageMode . Default ) ;
/// <summary>
/// Activates the Steam Overlay to open the invite dialog. Invitations sent from this dialog will be for the provided lobby.
/// </summary>
2019-04-16 11:45:44 +01:00
public static void OpenGameInviteOverlay ( SteamId lobby ) = > Internal . ActivateGameOverlayInviteDialog ( lobby ) ;
2019-04-15 15:54:10 +01:00
/// <summary>
/// Mark a target user as 'played with'.
/// NOTE: The current user must be in game with the other player for the association to work.
/// </summary>
2019-04-16 11:45:44 +01:00
public static void SetPlayedWith ( SteamId steamid ) = > Internal . SetPlayedWith ( steamid ) ;
2019-04-15 15:41:01 +01:00
2019-05-06 15:44:51 +01:00
/// <summary>
/// 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
/// </summary>
public static bool RequestUserInformation ( SteamId steamid , bool nameonly = true ) = > Internal . RequestUserInformation ( steamid , nameonly ) ;
2019-05-01 13:24:05 +01:00
internal static async Task CacheUserInformationAsync ( SteamId steamid , bool nameonly )
2019-04-15 16:43:55 +01:00
{
// Got it straight away, skip any waiting.
2019-05-06 15:44:51 +01:00
if ( ! RequestUserInformation ( steamid , nameonly ) )
2019-04-15 16:43:55 +01:00
return ;
await Task . Delay ( 100 ) ;
2019-05-06 15:44:51 +01:00
while ( RequestUserInformation ( steamid , nameonly ) )
2019-04-15 16:43:55 +01:00
{
await Task . Delay ( 50 ) ;
}
//
// And extra wait here seems to solve avatars loading as [?]
//
await Task . Delay ( 500 ) ;
}
2019-04-16 14:38:10 +01:00
public static async Task < Data . Image ? > GetSmallAvatarAsync ( SteamId steamid )
2019-04-15 16:43:55 +01:00
{
await CacheUserInformationAsync ( steamid , false ) ;
2019-04-16 14:21:48 +01:00
return SteamUtils . GetImage ( Internal . GetSmallFriendAvatar ( steamid ) ) ;
2019-04-15 16:43:55 +01:00
}
2019-04-16 14:38:10 +01:00
public static async Task < Data . Image ? > GetMediumAvatarAsync ( SteamId steamid )
2019-04-15 16:43:55 +01:00
{
await CacheUserInformationAsync ( steamid , false ) ;
2019-04-16 14:21:48 +01:00
return SteamUtils . GetImage ( Internal . GetMediumFriendAvatar ( steamid ) ) ;
2019-04-15 16:43:55 +01:00
}
2019-04-16 14:38:10 +01:00
public static async Task < Data . Image ? > GetLargeAvatarAsync ( SteamId steamid )
2019-04-15 16:43:55 +01:00
{
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 ) ;
}
2019-04-16 14:21:48 +01:00
return SteamUtils . GetImage ( imageid ) ;
2019-04-15 16:43:55 +01:00
}
2019-04-15 16:47:21 +01:00
/// <summary>
/// Find a rich presence value by key for current user. Will be null if not found.
/// </summary>
public static string GetRichPresence ( string key )
{
if ( richPresence . TryGetValue ( key , out var val ) )
return val ;
return null ;
}
/// <summary>
/// Sets a rich presence value by key for current user.
/// </summary>
public static bool SetRichPresence ( string key , string value )
{
2019-09-15 00:48:49 +03:00
bool success = Internal . SetRichPresence ( key , value ) ;
if ( success )
richPresence [ key ] = value ;
return success ;
2019-04-15 16:47:21 +01:00
}
/// <summary>
/// Clears all of the current user's rich presence data.
/// </summary>
public static void ClearRichPresence ( )
{
richPresence . Clear ( ) ;
Internal . ClearRichPresence ( ) ;
}
2019-04-15 17:11:01 +01:00
static bool _listenForFriendsMessages ;
/// <summary>
/// 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.
/// </summary>
public static bool ListenForFriendsMessages
{
get = > _listenForFriendsMessages ;
set
{
_listenForFriendsMessages = value ;
Internal . SetListenForFriendsMessages ( value ) ;
}
}
2020-01-21 17:35:04 -05:00
public static async Task < bool > IsFollowing ( SteamId steamID )
{
var r = await Internal . IsFollowing ( steamID ) ;
return r . Value . IsFollowing ;
}
public static async Task < int > GetFollowerCount ( SteamId steamID )
{
var r = await Internal . GetFollowerCount ( steamID ) ;
return r . Value . Count ;
}
2020-01-22 22:52:03 -05:00
public static async Task < SteamId [ ] > GetFollowingList ( )
{
int resultCount = 0 ;
var steamIds = new List < SteamId > ( ) ;
FriendsEnumerateFollowingList_t ? result ;
do
{
2020-02-22 20:23:19 +00:00
if ( ( result = await Internal . EnumerateFollowingList ( ( uint ) resultCount ) ) ! = null )
2020-01-22 22:52:03 -05:00
{
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 ( ) ;
}
2020-08-18 11:36:57 +01:00
/// <summary>
/// Call this before calling ActivateGameOverlayToWebPage() to have the Steam Overlay Browser block navigations
/// to your specified protocol (scheme) uris and instead dispatch a OverlayBrowserProtocolNavigation callback to your game.
/// </summary>
public static bool RegisterProtocolInOverlayBrowser ( string protocol )
{
return Internal . RegisterProtocolInOverlayBrowser ( protocol ) ;
}
2020-08-28 13:07:27 +02:00
public static async Task < bool > JoinClanChatRoom ( SteamId chatId )
{
var result = await Internal . JoinClanChatRoom ( chatId ) ;
if ( ! result . HasValue )
return false ;
return result . Value . ChatRoomEnterResponse = = RoomEnter . Success ;
}
public static bool SendClanChatRoomMessage ( SteamId chatId , string message )
{
return Internal . SendClanChatMessage ( chatId , message ) ;
}
2020-08-18 11:36:57 +01:00
}
2019-09-15 00:48:49 +03:00
}