From 90d9ae8c4c73e4c15e49016cc4a2529cf5323776 Mon Sep 17 00:00:00 2001 From: Garry Newman Date: Mon, 15 Apr 2019 17:11:01 +0100 Subject: [PATCH] Friends Swap --- Facepunch.Steamworks.Test/Client/LobbyTest.cs | 4 +- Facepunch.Steamworks/Client.cs | 4 - Facepunch.Steamworks/Client/Friends.cs | 396 ------------------ Facepunch.Steamworks/Client/Leaderboard.cs | 2 +- Facepunch.Steamworks/Client/SteamFriend.cs | 136 ------ .../Interfaces/Workshop.Item.cs | 5 +- .../Interfaces/Workshop.Query.cs | 1 - Facepunch.Steamworks/Interfaces/Workshop.cs | 3 - Facepunch.Steamworks/Redux/Friends.cs | 91 +++- Facepunch.Steamworks/Redux/Steam.cs | 1 + Facepunch.Steamworks/Redux/Structs/Friend.cs | 45 ++ 11 files changed, 140 insertions(+), 548 deletions(-) delete mode 100644 Facepunch.Steamworks/Client/Friends.cs delete mode 100644 Facepunch.Steamworks/Client/SteamFriend.cs diff --git a/Facepunch.Steamworks.Test/Client/LobbyTest.cs b/Facepunch.Steamworks.Test/Client/LobbyTest.cs index b14398d..340cf4d 100644 --- a/Facepunch.Steamworks.Test/Client/LobbyTest.cs +++ b/Facepunch.Steamworks.Test/Client/LobbyTest.cs @@ -364,8 +364,8 @@ namespace Facepunch.Steamworks.Test client.Lobby.OnLobbyMemberDataUpdated = (steamID) => { - string name = client.Friends.GetName(steamID); - Console.WriteLine(name + " updated data"); + var friend = new global::Steamworks.Friend( steamID ); + Console.WriteLine( friend.Name + " updated data"); Assert.IsTrue(client.Lobby.GetMemberData(steamID, "testkey") == "testvalue"); Console.WriteLine("testkey is now: " + client.Lobby.GetMemberData(steamID, "testkey")); }; diff --git a/Facepunch.Steamworks/Client.cs b/Facepunch.Steamworks/Client.cs index 9160b0a..2ca3c29 100644 --- a/Facepunch.Steamworks/Client.cs +++ b/Facepunch.Steamworks/Client.cs @@ -107,8 +107,6 @@ namespace Facepunch.Steamworks MicroTransactions = new MicroTransactions( this ); RemoteStorage = new RemoteStorage( this ); - Workshop.friends = Friends; - Stats.UpdateStats(); // @@ -149,8 +147,6 @@ namespace Facepunch.Steamworks return; RunCallbacks(); - Friends.Cycle(); - base.Update(); } diff --git a/Facepunch.Steamworks/Client/Friends.cs b/Facepunch.Steamworks/Client/Friends.cs deleted file mode 100644 index f49a950..0000000 --- a/Facepunch.Steamworks/Client/Friends.cs +++ /dev/null @@ -1,396 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SteamNative; - -namespace Facepunch.Steamworks -{ - public partial class Client : IDisposable - { - Friends _friends; - - public Friends Friends - { - get - { - if ( _friends == null ) - _friends = new Friends( this ); - - return _friends; - } - } - } - - /// - /// Handles most interactions with people in Steam, not just friends as the name would suggest. - /// - /// - /// foreach ( var friend in client.Friends.AllFriends ) - /// { - /// Console.WriteLine( $"{friend.Id}: {friend.Name}" ); - /// } - /// - public class Friends - { - internal Client client; - private byte[] buffer = new byte[1024 * 128]; - - internal Friends( Client c ) - { - client = c; - - client.RegisterCallback( OnAvatarImageLoaded ); - client.RegisterCallback( OnPersonaStateChange ); - client.RegisterCallback( OnGameJoinRequested ); - client.RegisterCallback( OnFriendChatMessage ); - } - - public delegate void ChatMessageDelegate( SteamFriend friend, string type, string message ); - - /// - /// Called when chat message has been received from a friend. You'll need to turn on - /// ListenForFriendsMessages to recieve this. - /// - public event ChatMessageDelegate OnChatMessage; - - private unsafe void OnFriendChatMessage( GameConnectedFriendChatMsg_t data ) - { - if ( OnChatMessage == null ) return; - - var friend = Get( data.SteamIDUser ); - var type = ChatEntryType.ChatMsg; - fixed ( byte* ptr = buffer ) - { - var len = client.native.friends.GetFriendMessage( data.SteamIDUser, data.MessageID, (IntPtr)ptr, buffer.Length, out type ); - - if ( len == 0 && type == ChatEntryType.Invalid ) - return; - - var typeName = type.ToString(); - var message = Encoding.UTF8.GetString( buffer, 0, len ); - - OnChatMessage( friend, typeName, message ); - } - } - - private 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 bool ListenForFriendsMessages - { - get - { - return _listenForFriendsMessages; - } - - set - { - _listenForFriendsMessages = value; - client.native.friends.SetListenForFriendsMessages( value ); - } - } - - - public delegate void JoinRequestedDelegate( SteamFriend friend, string connect ); - - // - // Called when a friend has invited you to their game (using InviteToGame) - // - public event JoinRequestedDelegate OnInvitedToGame; - - - private void OnGameJoinRequested( GameRichPresenceJoinRequested_t data ) - { - if ( OnInvitedToGame != null ) - { - OnInvitedToGame( Get( data.SteamIDFriend ), data.Connect ); - } - } - - /// - /// Try to get information about this user - which as name and avatar. - /// If returns true, we already have this user's information. - /// - public bool UpdateInformation( ulong steamid ) - { - return !client.native.friends.RequestUserInformation( steamid, false ); - } - - /// - /// Get this user's name - /// - public string GetName( ulong steamid ) - { - client.native.friends.RequestUserInformation( steamid, true ); - return client.native.friends.GetFriendPersonaName( steamid ); - } - - private List _allFriends; - - /// - /// Returns all friends, even blocked, ignored, friend requests etc - /// - public IEnumerable All - { - get - { - if ( _allFriends == null ) - { - _allFriends = new List(); - Refresh(); - } - - return _allFriends; - } - } - - /// - /// Returns only friends - /// - public IEnumerable AllFriends - { - get - { - foreach ( var friend in All ) - { - if ( !friend.IsFriend ) continue; - - yield return friend; - } - } - } - - /// - /// Returns all blocked users - /// - public IEnumerable AllBlocked - { - get - { - foreach ( var friend in All ) - { - if ( !friend.IsBlocked ) continue; - - yield return friend; - } - } - } - - public void Refresh() - { - if ( _allFriends == null ) - { - _allFriends = new List(); - } - - _allFriends.Clear(); - - var flags = (int) SteamNative.FriendFlags.All; - var count = client.native.friends.GetFriendCount( flags ); - - for ( int i=0; i - /// Should be 32x32 - but make sure to check! - /// - Small, - - /// - /// Should be 64x64 - but make sure to check! - /// - Medium, - - /// - /// Should be 184x184 - but make sure to check! - /// - Large - } - - /// - /// Try to get the avatar immediately. This should work for people on your friends list. - /// - public Image GetCachedAvatar( AvatarSize size, ulong steamid ) - { - var imageid = 0; - - switch (size) - { - case AvatarSize.Small: - imageid = client.native.friends.GetSmallFriendAvatar(steamid); - break; - case AvatarSize.Medium: - imageid = client.native.friends.GetMediumFriendAvatar(steamid); - break; - case AvatarSize.Large: - imageid = client.native.friends.GetLargeFriendAvatar(steamid); - break; - } - - if ( imageid == 1 ) return null; // Placeholder large - if ( imageid == 2 ) return null; // Placeholder medium - if ( imageid == 3 ) return null; // Placeholder small - - var img = new Image() - { - Id = imageid - }; - - if ( !img.TryLoad( client.native.utils ) ) - return null; - - return img; - } - - - /// - /// Callback will be called when the avatar is ready. If we fail to get an - /// avatar, might be called with a null Image. - /// - public void GetAvatar( AvatarSize size, ulong steamid, Action callback ) - { - // Maybe we already have it downloaded? - var image = GetCachedAvatar(size, steamid); - if ( image != null ) - { - callback(image); - return; - } - - // Lets request it from Steam - if (!client.native.friends.RequestUserInformation(steamid, false)) - { - // from docs: false means that we already have all the details about that user, and functions that require this information can be used immediately - // but that's probably not true because we just checked the cache - - // check again in case it was just a race - image = GetCachedAvatar(size, steamid); - if ( image != null ) - { - callback(image); - return; - } - - // maybe Steam returns false if it was already requested? just add another callback and hope it comes - // if not it'll time out anyway - } - - PersonaCallbacks.Add( new PersonaCallback - { - SteamId = steamid, - Size = size, - Callback = callback, - Time = DateTime.Now - }); - } - - private class PersonaCallback - { - public ulong SteamId; - public AvatarSize Size; - public Action Callback; - public DateTime Time; - } - - List PersonaCallbacks = new List(); - - public SteamFriend Get( ulong steamid ) - { - var friend = All.Where( x => x.Id == steamid ).FirstOrDefault(); - if ( friend != null ) return friend; - - var f = new SteamFriend() - { - Id = steamid, - Client = client - }; - - f.Refresh(); - - return f; - } - - internal void Cycle() - { - if ( PersonaCallbacks.Count == 0 ) return; - - var timeOut = DateTime.Now.AddSeconds( -10 ); - - for ( int i = PersonaCallbacks.Count-1; i >= 0; i-- ) - { - var cb = PersonaCallbacks[i]; - - // Timeout - if ( cb.Time < timeOut ) - { - if ( cb.Callback != null ) - { - // final attempt, will be null unless the callback was missed somehow - var image = GetCachedAvatar( cb.Size, cb.SteamId ); - - cb.Callback( image ); - } - - PersonaCallbacks.Remove( cb ); - continue; - } - } - } - - - private void OnPersonaStateChange( PersonaStateChange_t data ) - { - // k_EPersonaChangeAvatar - if ( (data.ChangeFlags & 0x0040) == 0x0040 ) - { - LoadAvatarForSteamId( data.SteamID ); - } - - // - // Find and refresh this friend's status - // - foreach ( var friend in All ) - { - if ( friend.Id != data.SteamID ) continue; - - friend.Refresh(); - } - } - - void LoadAvatarForSteamId( ulong Steamid ) - { - for ( int i = PersonaCallbacks.Count - 1; i >= 0; i-- ) - { - var cb = PersonaCallbacks[i]; - if ( cb.SteamId != Steamid ) continue; - - var image = GetCachedAvatar( cb.Size, cb.SteamId ); - if ( image == null ) continue; - - PersonaCallbacks.Remove( cb ); - - if ( cb.Callback != null ) - { - cb.Callback( image ); - } - } - } - - private void OnAvatarImageLoaded( AvatarImageLoaded_t data ) - { - LoadAvatarForSteamId( data.SteamID ); - } - - } -} diff --git a/Facepunch.Steamworks/Client/Leaderboard.cs b/Facepunch.Steamworks/Client/Leaderboard.cs index c338906..ad5c6ae 100644 --- a/Facepunch.Steamworks/Client/Leaderboard.cs +++ b/Facepunch.Steamworks/Client/Leaderboard.cs @@ -283,7 +283,7 @@ namespace Facepunch.Steamworks Score = entry.Score, SteamId = entry.SteamIDUser, SubScores = entry.CDetails == 0 ? null : subEntriesBuffer.Take( entry.CDetails ).ToArray(), - Name = client.Friends.GetName( entry.SteamIDUser ), + // Name = client.Friends.GetName( entry.SteamIDUser ), AttachedFile = (entry.UGC >> 32) == 0xffffffff ? null : new RemoteFile( client.RemoteStorage, entry.UGC ) } ); } diff --git a/Facepunch.Steamworks/Client/SteamFriend.cs b/Facepunch.Steamworks/Client/SteamFriend.cs deleted file mode 100644 index 91206dd..0000000 --- a/Facepunch.Steamworks/Client/SteamFriend.cs +++ /dev/null @@ -1,136 +0,0 @@ -using SteamNative; - -namespace Facepunch.Steamworks -{ - public class SteamFriend - { - /// - /// Steam Id - /// - public ulong Id { get; internal set; } - - - /// - /// Return true if blocked - /// - public bool IsBlocked => relationship == FriendRelationship.Blocked; - - /// - /// Return true if is a friend. Returns false if blocked, request etc. - /// - public bool IsFriend => relationship == FriendRelationship.Friend; - - /// - /// Their current display name - /// - public string Name; - - /// - /// Returns true if this friend is online - /// - public bool IsOnline => personaState != PersonaState.Offline; - - /// - /// Returns true if this friend is marked as away - /// - public bool IsAway => personaState == PersonaState.Away; - - /// - /// Returns true if this friend is marked as busy - /// - public bool IsBusy => personaState == PersonaState.Busy; - - /// - /// Returns true if this friend is marked as snoozing - /// - public bool IsSnoozing => personaState == PersonaState.Snooze; - - /// - /// Returns true if this friend is online and playing this game - /// - public bool IsPlayingThisGame => CurrentAppId == Client.AppId; - - /// - /// Returns true if this friend is online and playing this game - /// - public bool IsPlaying => CurrentAppId != 0; - - /// - /// The AppId this guy is playing - /// - public ulong CurrentAppId { get; internal set; } - - public uint ServerIp { get; internal set; } - public int ServerGamePort { get; internal set; } - public int ServerQueryPort { get; internal set; } - public ulong ServerLobbyId { get; internal set; } - - internal Client Client { get; set; } - private PersonaState personaState; - private FriendRelationship relationship; - - /// - /// Returns null if the value doesn't exist - /// - public string GetRichPresence( string key ) - { - var val = Client.native.friends.GetFriendRichPresence( Id, key ); - if ( string.IsNullOrEmpty( val ) ) return null; - return val; - } - - /// - /// Update this friend, request the latest data from Steam's servers - /// - public void Refresh() - { - Name = Client.native.friends.GetFriendPersonaName( Id ); - - relationship = Client.native.friends.GetFriendRelationship( Id ); - personaState = Client.native.friends.GetFriendPersonaState( Id ); - - CurrentAppId = 0; - ServerIp = 0; - ServerGamePort = 0; - ServerQueryPort = 0; - ServerLobbyId = 0; - - var gameInfo = new SteamNative.FriendGameInfo_t(); - if ( Client.native.friends.GetFriendGamePlayed( Id, ref gameInfo ) && gameInfo.GameID > 0 ) - { - CurrentAppId = gameInfo.GameID; - ServerIp = gameInfo.GameIP; - ServerGamePort = gameInfo.GamePort; - ServerQueryPort = gameInfo.QueryPort; - ServerLobbyId = gameInfo.SteamIDLobby; - } - - Client.native.friends.RequestFriendRichPresence( Id ); - } - - /// - /// This will return null if you don't have the target user's avatar in your cache. - /// Which usually happens for people not on your friends list. - /// - public Image GetAvatar( Friends.AvatarSize size ) - { - return Client.Friends.GetCachedAvatar( size, Id ); - } - - /// - /// Invite this friend to the game that we are playing - /// - public bool InviteToGame(string Text) - { - return Client.native.friends.InviteUserToGame(Id, Text); - } - - /// - /// Sends a message to a Steam friend. Returns true if success - /// - public bool SendMessage( string message ) - { - return Client.native.friends.ReplyToFriendMessage( Id, message ); - } - } -} diff --git a/Facepunch.Steamworks/Interfaces/Workshop.Item.cs b/Facepunch.Steamworks/Interfaces/Workshop.Item.cs index 24fda87..46c7032 100644 --- a/Facepunch.Steamworks/Interfaces/Workshop.Item.cs +++ b/Facepunch.Steamworks/Interfaces/Workshop.Item.cs @@ -203,11 +203,12 @@ namespace Facepunch.Steamworks public int ReportScore { get; internal set; } public string PreviewImageUrl { get; internal set; } + /* string _ownerName = null; - public string OwnerName + public Friend OwnerName { get { @@ -226,7 +227,7 @@ namespace Facepunch.Steamworks return _ownerName; } - } + }*/ } } } diff --git a/Facepunch.Steamworks/Interfaces/Workshop.Query.cs b/Facepunch.Steamworks/Interfaces/Workshop.Query.cs index 4b26d47..4fd835d 100644 --- a/Facepunch.Steamworks/Interfaces/Workshop.Query.cs +++ b/Facepunch.Steamworks/Interfaces/Workshop.Query.cs @@ -56,7 +56,6 @@ namespace Facepunch.Steamworks public int PerPage { get; set; } = SteamResponseSize; internal Workshop workshop; - internal Friends friends; private int _resultPage = 0; private int _resultsRemain = 0; diff --git a/Facepunch.Steamworks/Interfaces/Workshop.cs b/Facepunch.Steamworks/Interfaces/Workshop.cs index 7bf4e2a..e373b3a 100644 --- a/Facepunch.Steamworks/Interfaces/Workshop.cs +++ b/Facepunch.Steamworks/Interfaces/Workshop.cs @@ -31,7 +31,6 @@ namespace Facepunch.Steamworks internal const ulong InvalidHandle = 0xffffffffffffffff; internal SteamNative.SteamUGC ugc; - internal Friends friends; internal BaseSteamworks steamworks; internal SteamNative.SteamRemoteStorage remoteStorage; @@ -66,7 +65,6 @@ namespace Facepunch.Steamworks ugc = null; steamworks = null; remoteStorage = null; - friends = null; OnFileDownloaded = null; OnItemInstalled = null; @@ -150,7 +148,6 @@ namespace Facepunch.Steamworks { AppId = steamworks.AppId, workshop = this, - friends = friends }; } diff --git a/Facepunch.Steamworks/Redux/Friends.cs b/Facepunch.Steamworks/Redux/Friends.cs index 4cedc0f..f361f0f 100644 --- a/Facepunch.Steamworks/Redux/Friends.cs +++ b/Facepunch.Steamworks/Redux/Friends.cs @@ -33,11 +33,78 @@ namespace Steamworks internal static void InstallEvents() { - //new Event( x => OnBroadcastStarted?.Invoke() ); - //new Event( x => OnBroadcastStopped?.Invoke( x.Result ) ); + new Event( x => OnPersonaStateChange?.Invoke( new Friend( x.SteamID ) ) ); + new Event( x => OnGameRichPresenceJoinRequested?.Invoke( new Friend( x.SteamIDFriend), x.Connect ) ); + new Event( OnFriendChatMessage ); + new Event( x => OnGameOverlayActivated?.Invoke() ); + new Event( x => OnGameServerChangeRequested?.Invoke( x.Server, x.Password ) ); + new Event( x => OnGameLobbyJoinRequested?.Invoke( x.SteamIDLobby, x.SteamIDFriend ) ); + new Event( x => OnFriendRichPresenceUpdate?.Invoke( new Friend( x.SteamIDFriend ) ) ); } - // public static event Action OnBroadcastStarted; + /// + /// 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 ); + } + } /// /// returns the local players name - guaranteed to not be NULL. @@ -196,5 +263,23 @@ namespace Steamworks 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 ); + } + } + } } \ No newline at end of file diff --git a/Facepunch.Steamworks/Redux/Steam.cs b/Facepunch.Steamworks/Redux/Steam.cs index accc51e..89fbec9 100644 --- a/Facepunch.Steamworks/Redux/Steam.cs +++ b/Facepunch.Steamworks/Redux/Steam.cs @@ -37,6 +37,7 @@ namespace Steamworks Music.InstallEvents(); Video.InstallEvents(); User.InstallEvents(); + Friends.InstallEvents(); RunCallbacks(); } diff --git a/Facepunch.Steamworks/Redux/Structs/Friend.cs b/Facepunch.Steamworks/Redux/Structs/Friend.cs index 9df1757..be32c87 100644 --- a/Facepunch.Steamworks/Redux/Structs/Friend.cs +++ b/Facepunch.Steamworks/Redux/Structs/Friend.cs @@ -18,6 +18,28 @@ namespace Steamworks public bool IsFriend => Relationship == FriendRelationship.Friend; public bool IsBlocked => Relationship == FriendRelationship.Blocked; + public bool IsPlayingThisGame => GameInfo?.GameID == Utils.AppId; + + /// + /// Returns true if this friend is online + /// + public bool IsOnline => State != PersonaState.Offline; + + /// + /// Returns true if this friend is marked as away + /// + public bool IsAway => State == PersonaState.Away; + + /// + /// Returns true if this friend is marked as busy + /// + public bool IsBusy => State == PersonaState.Busy; + + /// + /// Returns true if this friend is marked as snoozing + /// + public bool IsSnoozing => State == PersonaState.Snooze; + public FriendRelationship Relationship => Friends.Internal.GetFriendRelationship( Id ); @@ -95,5 +117,28 @@ namespace Steamworks return await Friends.GetLargeAvatarAsync( Id ); } + public string GetRichPresence( string key ) + { + var val = Friends.Internal.GetFriendRichPresence( Id, key ); + if ( string.IsNullOrEmpty( val ) ) return null; + return val; + } + + /// + /// Invite this friend to the game that we are playing + /// + public bool InviteToGame( string Text ) + { + return Friends.Internal.InviteUserToGame( Id, Text ); + } + + /// + /// Sends a message to a Steam friend. Returns true if success + /// + public bool SendMessage( string message ) + { + return Friends.Internal.ReplyToFriendMessage( Id, message ); + } + } } \ No newline at end of file