Files
sbox-public/engine/Sandbox.Engine/Platform/Steam/Structs/Friend.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

248 lines
7.2 KiB
C#

using Sandbox;
using Steamworks.Data;
namespace Steamworks;
internal struct Friend
{
public SteamId Id;
public Friend( SteamId steamid )
{
Id = steamid;
}
public override string ToString()
{
return $"{Name} ({Id})";
}
/// <summary>
/// Returns true if this is the local user
/// </summary>
public bool IsMe => Id == SteamClient.SteamId;
/// <summary>
/// Return true if this is a friend
/// </summary>
public bool IsFriend => Relationship == Relationship.Friend;
/// <summary>
/// Returns true if you have this user blocked
/// </summary>
public bool IsBlocked => Relationship == Relationship.Blocked;
/// <summary>
/// Return true if this user is playing the game we're running
/// </summary>
public bool IsPlayingThisGame => GameInfo?.GameID == SteamClient.AppId;
/// <summary>
/// Return true if this user is playing another game
/// </summary>
public bool IsPlaying => GameInfo?.GameID > 0;
/// <summary>
/// Returns true if this friend is online
/// </summary>
public bool IsOnline => State != FriendState.Offline;
/// <summary>
/// Sometimes we don't know the user's name. This will wait until we have
/// downloaded the information on this user.
/// </summary>
public async Task RequestInfoAsync()
{
await SteamFriends.CacheUserInformationAsync( Id, true );
}
/// <summary>
/// Returns true if this friend is marked as away
/// </summary>
public bool IsAway => State == FriendState.Away;
/// <summary>
/// Returns true if this friend is marked as busy
/// </summary>
public bool IsBusy => State == FriendState.Busy;
/// <summary>
/// Returns true if this friend is marked as snoozing
/// </summary>
public bool IsSnoozing => State == FriendState.Snooze;
//
// These take an annoying amount of time to call, innocently iterating on a friends list of 20 was eating 1-2ms
// And some people have 500+, this could be done a bit neater but I didn't want to break any APIs and this seemed
// like the best layer to do it on.
// We get a PersonaStateChange_t whenever any of these are dirty, and clear the cache(s) depending on flags
//
internal static Dictionary<ulong, string> _nameCache = new();
internal static Dictionary<ulong, Relationship> _relationshipCache = new();
internal static Dictionary<ulong, FriendState> _stateCache = new();
internal static Dictionary<ulong, int> _steamLevelCache = new();
internal static Dictionary<ulong, FriendGameInfo?> _gameInfoCache = new();
public string Name => _nameCache.GetOrCreate( Id.Value, SteamFriends.Internal.GetFriendPersonaName );
public Relationship Relationship => _relationshipCache.GetOrCreate( Id.Value, SteamFriends.Internal.GetFriendRelationship );
public FriendState State
{
get
{
if ( !_stateCache.TryGetValue( Id.Value, out var val ) )
{
val = SteamFriends.Internal.GetFriendPersonaState( Id.Value );
_stateCache[Id.Value] = val;
}
return val;
}
}
public int SteamLevel => _steamLevelCache.GetOrCreate( Id.Value, SteamFriends.Internal.GetFriendSteamLevel );
public FriendGameInfo? GameInfo => _gameInfoCache.GetOrCreate( Id, x =>
{
FriendGameInfo_t gameInfo = default;
if ( !SteamFriends.Internal.GetFriendGamePlayed( x, ref gameInfo ) )
return null;
return FriendGameInfo.From( gameInfo );
} );
public IEnumerable<string> NameHistory
{
get
{
for ( int i = 0; i < 32; i++ )
{
var n = SteamFriends.Internal.GetFriendPersonaNameHistory( Id, i );
if ( string.IsNullOrEmpty( n ) )
break;
yield return n;
}
}
}
public bool IsIn( SteamId group_or_room )
{
return SteamFriends.Internal.IsUserInSource( Id, group_or_room );
}
public struct FriendGameInfo
{
internal ulong GameID; // m_gameID class CGameID
internal uint GameIP; // m_unGameIP uint32
internal ulong SteamIDLobby; // m_steamIDLobby class CSteamID
public ulong GameId => GameID;
public int ConnectionPort;
public int QueryPort;
public uint IpAddressRaw => GameIP;
public System.Net.IPAddress IpAddress => Utility.Int32ToIp( GameIP );
internal static FriendGameInfo From( FriendGameInfo_t i )
{
return new FriendGameInfo
{
GameID = i.GameID,
GameIP = i.GameIP,
ConnectionPort = i.GamePort,
QueryPort = i.QueryPort,
SteamIDLobby = i.SteamIDLobby,
};
}
}
internal async Task<Data.Image?> GetSmallAvatarAsync()
{
return await SteamFriends.GetSmallAvatarAsync( Id );
}
internal async Task<Data.Image?> GetMediumAvatarAsync()
{
return await SteamFriends.GetMediumAvatarAsync( Id );
}
internal async Task<Data.Image?> GetLargeAvatarAsync()
{
return await SteamFriends.GetLargeAvatarAsync( Id );
}
public string GetRichPresence( string key )
{
var val = SteamFriends.Internal.GetFriendRichPresence( Id, key );
if ( string.IsNullOrEmpty( val ) ) return null;
return val;
}
/// <summary>
/// Invite this friend to the game that we are playing
/// </summary>
internal bool InviteToGame( string Text )
{
return SteamFriends.Internal.InviteUserToGame( Id, Text );
}
/// <summary>
/// Activates the Steam Overlay to a specific dialog
/// </summary>
/// <param name="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.
/// </param>
internal void OpenInOverlay( string type = "steamid" )
{
SteamFriends.OpenUserOverlay( Id, type );
}
/// <summary>
/// Sends a message to a Steam friend. Returns true if success
/// </summary>
internal bool SendMessage( string message )
{
return SteamFriends.Internal.ReplyToFriendMessage( Id, message );
}
internal static void SteamFriends_OnPersonaStateChange( SteamId id, PersonaChange changeFlags )
{
if ( changeFlags.Contains( PersonaChange.Name ) )
_nameCache.Remove( id );
if ( changeFlags.Contains( PersonaChange.RelationshipChanged ) )
_relationshipCache.Remove( id );
if ( changeFlags.Contains( PersonaChange.Status ) || changeFlags.Contains( PersonaChange.GoneOffline ) || changeFlags.Contains( PersonaChange.ComeOnline ) )
_stateCache.Remove( id );
if ( changeFlags.Contains( PersonaChange.SteamLevel ) )
_steamLevelCache.Remove( id );
if ( changeFlags.Contains( PersonaChange.GamePlayed ) || changeFlags.Contains( PersonaChange.GameServer ) )
_gameInfoCache.Remove( id );
}
}
static class DictionaryExtensions
{
public static TValue GetOrCreate<TKey, TValue>( this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> createNew )
{
if ( !dict.TryGetValue( key, out var val ) )
{
val = createNew( key );
dict.Add( key, val );
}
return val;
}
}