Restructuring of the files
This commit is contained in:
148
Facepunch.Steamworks/Structs/Achievement.cs
Normal file
148
Facepunch.Steamworks/Structs/Achievement.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct Achievement
|
||||
{
|
||||
internal string Value;
|
||||
|
||||
public Achievement( string name )
|
||||
{
|
||||
Value = name;
|
||||
}
|
||||
|
||||
public override string ToString() => Value;
|
||||
|
||||
/// <summary>
|
||||
/// True if unlocked
|
||||
/// </summary>
|
||||
public bool State
|
||||
{
|
||||
get
|
||||
{
|
||||
var state = false;
|
||||
SteamUserStats.Internal.GetAchievement( Value, ref state );
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
public string Identifier => Value;
|
||||
|
||||
public string Name => SteamUserStats.Internal.GetAchievementDisplayAttribute( Value, "name" );
|
||||
|
||||
public string Description => SteamUserStats.Internal.GetAchievementDisplayAttribute( Value, "desc" );
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should hold the unlock time if State is true
|
||||
/// </summary>
|
||||
public DateTime? UnlockTime
|
||||
{
|
||||
get
|
||||
{
|
||||
var state = false;
|
||||
uint time = 0;
|
||||
|
||||
if ( !SteamUserStats.Internal.GetAchievementAndUnlockTime( Value, ref state, ref time ) || !state )
|
||||
return null;
|
||||
|
||||
return Epoch.ToDateTime( time );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon of the achievement. This can return a null image even though the image exists if the image
|
||||
/// hasn't been downloaded by Steam yet. You can use GetIconAsync if you want to wait for the image to be downloaded.
|
||||
/// </summary>
|
||||
public Image? GetIcon()
|
||||
{
|
||||
return SteamUtils.GetImage( SteamUserStats.Internal.GetAchievementIcon( Value ) );
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon of the achievement, waits for it to load if we have to
|
||||
/// </summary>
|
||||
public async Task<Image?> GetIconAsync( int timeout = 5000 )
|
||||
{
|
||||
var i = SteamUserStats.Internal.GetAchievementIcon( Value );
|
||||
if ( i != 0 ) return SteamUtils.GetImage( i );
|
||||
|
||||
var ident = Identifier;
|
||||
bool gotCallback = false;
|
||||
|
||||
void f( string x, int icon )
|
||||
{
|
||||
if ( x != ident ) return;
|
||||
i = icon;
|
||||
gotCallback = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SteamUserStats.OnAchievementIconFetched += f;
|
||||
|
||||
int waited = 0;
|
||||
while ( !gotCallback )
|
||||
{
|
||||
await Task.Delay( 10 );
|
||||
waited += 10;
|
||||
|
||||
// Time out after x milliseconds
|
||||
if ( waited > timeout )
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( i == 0 ) return null;
|
||||
return SteamUtils.GetImage( i );
|
||||
}
|
||||
finally
|
||||
{
|
||||
SteamUserStats.OnAchievementIconFetched -= f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the fraction (0-1) of users who have unlocked the specified achievement, or -1 if no data available.
|
||||
/// </summary>
|
||||
public float GlobalUnlocked
|
||||
{
|
||||
get
|
||||
{
|
||||
float pct = 0;
|
||||
|
||||
if ( !SteamUserStats.Internal.GetAchievementAchievedPercent( Value, ref pct ) )
|
||||
return -1.0f;
|
||||
|
||||
return pct / 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make this achievement earned
|
||||
/// </summary>
|
||||
public bool Trigger( bool apply = true )
|
||||
{
|
||||
var r = SteamUserStats.Internal.SetAchievement( Value );
|
||||
|
||||
if ( apply && r )
|
||||
{
|
||||
SteamUserStats.Internal.StoreStats();
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset this achievement to not achieved
|
||||
/// </summary>
|
||||
public bool Clear()
|
||||
{
|
||||
return SteamUserStats.Internal.ClearAchievement( Value );
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Facepunch.Steamworks/Structs/AppId.cs
Normal file
30
Facepunch.Steamworks/Structs/AppId.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public struct AppId
|
||||
{
|
||||
public uint Value;
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
|
||||
public static implicit operator AppId( uint value )
|
||||
{
|
||||
return new AppId{ Value = value };
|
||||
}
|
||||
|
||||
public static implicit operator AppId( int value )
|
||||
{
|
||||
return new AppId { Value = (uint) value };
|
||||
}
|
||||
|
||||
public static implicit operator uint( AppId value )
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Facepunch.Steamworks/Structs/Controller.cs
Normal file
97
Facepunch.Steamworks/Structs/Controller.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Facepunch.Steamworks.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public struct Controller
|
||||
{
|
||||
internal InputHandle_t Handle;
|
||||
|
||||
internal Controller( InputHandle_t inputHandle_t )
|
||||
{
|
||||
this.Handle = inputHandle_t;
|
||||
}
|
||||
|
||||
public ulong Id => Handle.Value;
|
||||
public InputType InputType => SteamInput.Internal.GetInputTypeForHandle( Handle );
|
||||
|
||||
/// <summary>
|
||||
/// Reconfigure the controller to use the specified action set (ie 'Menu', 'Walk' or 'Drive')
|
||||
/// This is cheap, and can be safely called repeatedly. It's often easier to repeatedly call it in
|
||||
/// our state loops, instead of trying to place it in all of your state transitions.
|
||||
/// </summary>
|
||||
public string ActionSet
|
||||
{
|
||||
set => SteamInput.Internal.ActivateActionSet( Handle, SteamInput.Internal.GetActionSetHandle( value ) );
|
||||
}
|
||||
|
||||
public void DeactivateLayer( string layer ) => SteamInput.Internal.DeactivateActionSetLayer( Handle, SteamInput.Internal.GetActionSetHandle( layer ) );
|
||||
public void ActivateLayer( string layer ) => SteamInput.Internal.ActivateActionSetLayer( Handle, SteamInput.Internal.GetActionSetHandle( layer ) );
|
||||
public void ClearLayers() => SteamInput.Internal.DeactivateAllActionSetLayers( Handle );
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current state of the supplied digital game action
|
||||
/// </summary>
|
||||
public DigitalState GetDigitalState( string actionName )
|
||||
{
|
||||
return SteamInput.Internal.GetDigitalActionData( Handle, SteamInput.GetDigitalActionHandle( actionName ) );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current state of these supplied analog game action
|
||||
/// </summary>
|
||||
public AnalogState GetAnalogState( string actionName )
|
||||
{
|
||||
return SteamInput.Internal.GetAnalogActionData( Handle, SteamInput.GetAnalogActionHandle( actionName ) );
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() => $"{InputType}.{Handle.Value}";
|
||||
|
||||
|
||||
public static bool operator ==( Controller a, Controller b ) => a.Equals( b );
|
||||
public static bool operator !=( Controller a, Controller b ) => !(a == b);
|
||||
public override bool Equals( object p ) => this.Equals( (Controller)p );
|
||||
public override int GetHashCode() => Handle.GetHashCode();
|
||||
public bool Equals( Controller p ) => p.Handle == Handle;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
||||
public struct AnalogState
|
||||
{
|
||||
public InputSourceMode EMode; // eMode EInputSourceMode
|
||||
public float X; // x float
|
||||
public float Y; // y float
|
||||
internal byte BActive; // bActive byte
|
||||
public bool Active => BActive != 0;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
||||
internal struct MotionState
|
||||
{
|
||||
public float RotQuatX; // rotQuatX float
|
||||
public float RotQuatY; // rotQuatY float
|
||||
public float RotQuatZ; // rotQuatZ float
|
||||
public float RotQuatW; // rotQuatW float
|
||||
public float PosAccelX; // posAccelX float
|
||||
public float PosAccelY; // posAccelY float
|
||||
public float PosAccelZ; // posAccelZ float
|
||||
public float RotVelX; // rotVelX float
|
||||
public float RotVelY; // rotVelY float
|
||||
public float RotVelZ; // rotVelZ float
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
||||
public struct DigitalState
|
||||
{
|
||||
[MarshalAs( UnmanagedType.I1 )]
|
||||
internal byte BState; // bState byte
|
||||
[MarshalAs( UnmanagedType.I1 )]
|
||||
internal byte BActive; // bActive byte
|
||||
|
||||
public bool Pressed => BState != 0;
|
||||
public bool Active => BActive != 0;
|
||||
}
|
||||
}
|
||||
30
Facepunch.Steamworks/Structs/DepotId.cs
Normal file
30
Facepunch.Steamworks/Structs/DepotId.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct DepotId
|
||||
{
|
||||
public uint Value;
|
||||
|
||||
public static implicit operator DepotId( uint value )
|
||||
{
|
||||
return new DepotId { Value = value };
|
||||
}
|
||||
|
||||
public static implicit operator DepotId( int value )
|
||||
{
|
||||
return new DepotId { Value = (uint) value };
|
||||
}
|
||||
|
||||
public static implicit operator uint( DepotId value )
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
}
|
||||
15
Facepunch.Steamworks/Structs/DlcInformation.cs
Normal file
15
Facepunch.Steamworks/Structs/DlcInformation.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct DlcInformation
|
||||
{
|
||||
public AppId AppId { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
public bool Available { get; internal set; }
|
||||
}
|
||||
}
|
||||
15
Facepunch.Steamworks/Structs/DownloadProgress.cs
Normal file
15
Facepunch.Steamworks/Structs/DownloadProgress.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct DownloadProgress
|
||||
{
|
||||
public bool Active;
|
||||
public ulong BytesDownloaded;
|
||||
public ulong BytesTotal;
|
||||
}
|
||||
}
|
||||
42
Facepunch.Steamworks/Structs/DurationControl.cs
Normal file
42
Facepunch.Steamworks/Structs/DurationControl.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Sent for games with enabled anti indulgence / duration control, for enabled users.
|
||||
/// Lets the game know whether persistent rewards or XP should be granted at normal rate, half rate, or zero rate.
|
||||
/// </summary>
|
||||
public struct DurationControl
|
||||
{
|
||||
internal DurationControl_t _inner;
|
||||
|
||||
/// <summary>
|
||||
/// appid generating playtime
|
||||
/// </summary>
|
||||
public AppId Appid => _inner.Appid;
|
||||
|
||||
/// <summary>
|
||||
/// is duration control applicable to user + game combination
|
||||
/// </summary>
|
||||
public bool Applicable => _inner.Applicable;
|
||||
|
||||
/// <summary>
|
||||
/// playtime since most recent 5 hour gap in playtime, only counting up to regulatory limit of playtime, in seconds
|
||||
/// </summary>
|
||||
internal TimeSpan PlaytimeInLastFiveHours => TimeSpan.FromSeconds( _inner.CsecsLast5h );
|
||||
|
||||
/// <summary>
|
||||
/// playtime on current calendar day
|
||||
/// </summary>
|
||||
internal TimeSpan PlaytimeToday => TimeSpan.FromSeconds( _inner.CsecsLast5h );
|
||||
|
||||
/// <summary>
|
||||
/// recommended progress
|
||||
/// </summary>
|
||||
internal DurationControlProgress Progress => _inner.Progress;
|
||||
}
|
||||
}
|
||||
15
Facepunch.Steamworks/Structs/FileDetails.cs
Normal file
15
Facepunch.Steamworks/Structs/FileDetails.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct FileDetails
|
||||
{
|
||||
public ulong SizeInBytes;
|
||||
public string Sha1;
|
||||
public uint Flags;
|
||||
}
|
||||
}
|
||||
263
Facepunch.Steamworks/Structs/Friend.cs
Normal file
263
Facepunch.Steamworks/Structs/Friend.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public struct Friend
|
||||
{
|
||||
public SteamId Id;
|
||||
|
||||
public Friend( SteamId steamid )
|
||||
{
|
||||
Id = steamid;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} ({Id.ToString()})";
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
/// 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;
|
||||
|
||||
|
||||
|
||||
public Relationship Relationship => SteamFriends.Internal.GetFriendRelationship( Id );
|
||||
public FriendState State => SteamFriends.Internal.GetFriendPersonaState( Id );
|
||||
public string Name => SteamFriends.Internal.GetFriendPersonaName( Id );
|
||||
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 int SteamLevel => SteamFriends.Internal.GetFriendSteamLevel( Id );
|
||||
|
||||
|
||||
|
||||
public FriendGameInfo? GameInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
FriendGameInfo_t gameInfo = default;
|
||||
if ( !SteamFriends.Internal.GetFriendGamePlayed( Id, ref gameInfo ) )
|
||||
return null;
|
||||
|
||||
return FriendGameInfo.From( gameInfo );
|
||||
}
|
||||
}
|
||||
|
||||
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 int ConnectionPort;
|
||||
public int QueryPort;
|
||||
|
||||
public uint IpAddressRaw => GameIP;
|
||||
public System.Net.IPAddress IpAddress => Utility.Int32ToIp( GameIP );
|
||||
|
||||
public Lobby? Lobby
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( SteamIDLobby == 0 ) return null;
|
||||
return new Lobby( SteamIDLobby );
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Data.Image?> GetSmallAvatarAsync()
|
||||
{
|
||||
return await SteamFriends.GetSmallAvatarAsync( Id );
|
||||
}
|
||||
|
||||
public async Task<Data.Image?> GetMediumAvatarAsync()
|
||||
{
|
||||
return await SteamFriends.GetMediumAvatarAsync( Id );
|
||||
}
|
||||
|
||||
public 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>
|
||||
public bool InviteToGame( string Text )
|
||||
{
|
||||
return SteamFriends.Internal.InviteUserToGame( Id, Text );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to a Steam friend. Returns true if success
|
||||
/// </summary>
|
||||
public bool SendMessage( string message )
|
||||
{
|
||||
return SteamFriends.Internal.ReplyToFriendMessage( Id, message );
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get download the latest user stats
|
||||
/// </summary>
|
||||
/// <returns>True if successful, False if failure</returns>
|
||||
public async Task<bool> RequestUserStatsAsync()
|
||||
{
|
||||
var result = await SteamUserStats.Internal.RequestUserStats( Id );
|
||||
return result.HasValue && result.Value.Result == Result.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a user stat. Must call RequestUserStats first.
|
||||
/// </summary>
|
||||
/// <param name="statName">The name of the stat you want to get</param>
|
||||
/// <param name="defult">Will return this value if not available</param>
|
||||
/// <returns>The value, or defult if not available</returns>
|
||||
public float GetStatFloat( string statName, float defult = 0 )
|
||||
{
|
||||
var val = defult;
|
||||
|
||||
if ( !SteamUserStats.Internal.GetUserStat( Id, statName, ref val ) )
|
||||
return defult;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a user stat. Must call RequestUserStats first.
|
||||
/// </summary>
|
||||
/// <param name="statName">The name of the stat you want to get</param>
|
||||
/// <param name="defult">Will return this value if not available</param>
|
||||
/// <returns>The value, or defult if not available</returns>
|
||||
public int GetStatInt( string statName, int defult = 0 )
|
||||
{
|
||||
var val = defult;
|
||||
|
||||
if ( !SteamUserStats.Internal.GetUserStat( Id, statName, ref val ) )
|
||||
return defult;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a user achievement state. Must call RequestUserStats first.
|
||||
/// </summary>
|
||||
/// <param name="statName">The name of the achievement you want to get</param>
|
||||
/// <param name="defult">Will return this value if not available</param>
|
||||
/// <returns>The value, or defult if not available</returns>
|
||||
public bool GetAchievement( string statName, bool defult = false )
|
||||
{
|
||||
var val = defult;
|
||||
|
||||
if ( !SteamUserStats.Internal.GetUserAchievement( Id, statName, ref val ) )
|
||||
return defult;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a the time this achievement was unlocked.
|
||||
/// </summary>
|
||||
/// <param name="statName">The name of the achievement you want to get</param>
|
||||
/// <returns>The time unlocked. If it wasn't unlocked, or you haven't downloaded the stats yet - will return DateTime.MinValue</returns>
|
||||
public DateTime GetAchievementUnlockTime( string statName )
|
||||
{
|
||||
bool val = false;
|
||||
uint time = 0;
|
||||
|
||||
if ( !SteamUserStats.Internal.GetUserAchievementAndUnlockTime( Id, statName, ref val, ref time ) || !val )
|
||||
return DateTime.MinValue;
|
||||
|
||||
return Epoch.ToDateTime( time );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
45
Facepunch.Steamworks/Structs/GameId.cs
Normal file
45
Facepunch.Steamworks/Structs/GameId.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct GameId
|
||||
{
|
||||
// TODO - Be able to access these vars
|
||||
|
||||
/*
|
||||
|
||||
enum EGameIDType
|
||||
{
|
||||
k_EGameIDTypeApp = 0,
|
||||
k_EGameIDTypeGameMod = 1,
|
||||
k_EGameIDTypeShortcut = 2,
|
||||
k_EGameIDTypeP2P = 3,
|
||||
};
|
||||
|
||||
# ifdef VALVE_BIG_ENDIAN
|
||||
unsigned int m_nModID : 32;
|
||||
unsigned int m_nType : 8;
|
||||
unsigned int m_nAppID : 24;
|
||||
#else
|
||||
unsigned int m_nAppID : 24;
|
||||
unsigned int m_nType : 8;
|
||||
unsigned int m_nModID : 32;
|
||||
#endif
|
||||
*/
|
||||
public ulong Value;
|
||||
|
||||
public static implicit operator GameId( ulong value )
|
||||
{
|
||||
return new GameId { Value = value };
|
||||
}
|
||||
|
||||
public static implicit operator ulong( GameId value )
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Facepunch.Steamworks/Structs/Image.cs
Normal file
37
Facepunch.Steamworks/Structs/Image.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct Image
|
||||
{
|
||||
public uint Width;
|
||||
public uint Height;
|
||||
public byte[] Data;
|
||||
|
||||
public Color GetPixel( int x, int y )
|
||||
{
|
||||
if ( x < 0 || x >= Width ) throw new System.Exception( "x out of bounds" );
|
||||
if ( y < 0 || y >= Height ) throw new System.Exception( "y out of bounds" );
|
||||
|
||||
Color c = new Color();
|
||||
|
||||
var i = (y * Width + x) * 4;
|
||||
|
||||
c.r = Data[i + 0];
|
||||
c.g = Data[i + 1];
|
||||
c.b = Data[i + 2];
|
||||
c.a = Data[i + 3];
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Width}x{Height} ({Data.Length}bytes)";
|
||||
}
|
||||
}
|
||||
|
||||
public struct Color
|
||||
{
|
||||
public byte r, g, b, a;
|
||||
}
|
||||
}
|
||||
241
Facepunch.Steamworks/Structs/InventoryDef.cs
Normal file
241
Facepunch.Steamworks/Structs/InventoryDef.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public class InventoryDef : IEquatable<InventoryDef>
|
||||
{
|
||||
internal InventoryDefId _id;
|
||||
internal Dictionary<string, string> _properties;
|
||||
|
||||
public InventoryDef( InventoryDefId defId )
|
||||
{
|
||||
_id = defId;
|
||||
}
|
||||
|
||||
public int Id => _id.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetProperty( "name" )
|
||||
/// </summary>
|
||||
public string Name => GetProperty( "name" );
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetProperty( "description" )
|
||||
/// </summary>
|
||||
public string Description => GetProperty( "description" );
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetProperty( "icon_url" )
|
||||
/// </summary>
|
||||
public string IconUrl => GetProperty( "icon_url" );
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetProperty( "icon_url_large" )
|
||||
/// </summary>
|
||||
public string IconUrlLarge => GetProperty( "icon_url_large" );
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetProperty( "price_category" )
|
||||
/// </summary>
|
||||
public string PriceCategory => GetProperty( "price_category" );
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetProperty( "type" )
|
||||
/// </summary>
|
||||
public string Type => GetProperty( "type" );
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is an item that generates an item, rather
|
||||
/// than something that is actual an item
|
||||
/// </summary>
|
||||
public bool IsGenerator => Type == "generator";
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetProperty( "exchange" )
|
||||
/// </summary>
|
||||
public string ExchangeSchema => GetProperty( "exchange" );
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of exchanges that are available to make this item
|
||||
/// </summary>
|
||||
public InventoryRecipe[] GetRecipes()
|
||||
{
|
||||
if ( string.IsNullOrEmpty( ExchangeSchema ) ) return null;
|
||||
|
||||
var parts = ExchangeSchema.Split( new[] { ';' }, StringSplitOptions.RemoveEmptyEntries );
|
||||
|
||||
return parts.Select( x => InventoryRecipe.FromString( x, this ) ).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetBoolProperty( "marketable" )
|
||||
/// </summary>
|
||||
public bool Marketable => GetBoolProperty( "marketable" );
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut to call GetBoolProperty( "tradable" )
|
||||
/// </summary>
|
||||
public bool Tradable => GetBoolProperty( "tradable" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property timestamp
|
||||
/// </summary>
|
||||
public DateTime Created => GetProperty<DateTime>( "timestamp" );
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property modified
|
||||
/// </summary>
|
||||
public DateTime Modified => GetProperty<DateTime>( "modified" );
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific property by name
|
||||
/// </summary>
|
||||
public string GetProperty( string name )
|
||||
{
|
||||
if ( _properties!= null && _properties.TryGetValue( name, out string val ) )
|
||||
return val;
|
||||
|
||||
uint _ = (uint)Helpers.MemoryBufferSize;
|
||||
|
||||
if ( !SteamInventory.Internal.GetItemDefinitionProperty( Id, name, out var vl, ref _ ) )
|
||||
return null;
|
||||
|
||||
if (name == null) //return keys string
|
||||
return vl;
|
||||
|
||||
if ( _properties == null )
|
||||
_properties = new Dictionary<string, string>();
|
||||
|
||||
_properties[name] = vl;
|
||||
|
||||
return vl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a raw property from the definition schema
|
||||
/// </summary>
|
||||
public bool GetBoolProperty( string name )
|
||||
{
|
||||
string val = GetProperty( name );
|
||||
|
||||
if ( val.Length == 0 ) return false;
|
||||
if ( val[0] == '0' || val[0] == 'F' || val[0] == 'f' ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a raw property from the definition schema
|
||||
/// </summary>
|
||||
public T GetProperty<T>( string name )
|
||||
{
|
||||
string val = GetProperty( name );
|
||||
|
||||
if ( string.IsNullOrEmpty( val ) )
|
||||
return default;
|
||||
|
||||
try
|
||||
{
|
||||
return (T)Convert.ChangeType( val, typeof( T ) );
|
||||
}
|
||||
catch ( System.Exception )
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all properties on this item
|
||||
/// </summary>
|
||||
public IEnumerable<KeyValuePair<string, string>> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = GetProperty( null );
|
||||
var keys = list.Split( ',' );
|
||||
|
||||
foreach ( var key in keys )
|
||||
{
|
||||
yield return new KeyValuePair<string, string>( key, GetProperty( key ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the price of this item in the local currency (SteamInventory.Currency)
|
||||
/// </summary>
|
||||
public int LocalPrice
|
||||
{
|
||||
get
|
||||
{
|
||||
ulong curprice = 0;
|
||||
ulong baseprice = 0;
|
||||
|
||||
if ( !SteamInventory.Internal.GetItemPrice( Id, ref curprice, ref baseprice ) )
|
||||
return 0;
|
||||
|
||||
return (int) curprice;
|
||||
}
|
||||
}
|
||||
|
||||
public string LocalPriceFormatted => Utility.FormatPrice( SteamInventory.Currency, LocalPrice / 100.0 );
|
||||
|
||||
/// <summary>
|
||||
/// If the price has been discounted, LocalPrice will differ from LocalBasePrice
|
||||
/// (assumed, this isn't documented)
|
||||
/// </summary>
|
||||
public int LocalBasePrice
|
||||
{
|
||||
get
|
||||
{
|
||||
ulong curprice = 0;
|
||||
ulong baseprice = 0;
|
||||
|
||||
if ( !SteamInventory.Internal.GetItemPrice( Id, ref curprice, ref baseprice ) )
|
||||
return 0;
|
||||
|
||||
return (int)baseprice;
|
||||
}
|
||||
}
|
||||
|
||||
public string LocalBasePriceFormatted => Utility.FormatPrice( SteamInventory.Currency, LocalPrice / 100.0 );
|
||||
|
||||
InventoryRecipe[] _recContaining;
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of recepies that contain this item
|
||||
/// </summary>
|
||||
public InventoryRecipe[] GetRecipesContainingThis()
|
||||
{
|
||||
if ( _recContaining != null ) return _recContaining;
|
||||
|
||||
var allRec = SteamInventory.Definitions
|
||||
.Select( x => x.GetRecipes() )
|
||||
.Where( x => x != null )
|
||||
.SelectMany( x => x );
|
||||
|
||||
_recContaining = allRec.Where( x => x.ContainsIngredient( this ) ).ToArray();
|
||||
return _recContaining;
|
||||
}
|
||||
|
||||
public static bool operator ==( InventoryDef a, InventoryDef b )
|
||||
{
|
||||
if ( Object.ReferenceEquals( a, null ) )
|
||||
return Object.ReferenceEquals( b, null );
|
||||
|
||||
return a.Equals( b );
|
||||
}
|
||||
public static bool operator !=( InventoryDef a, InventoryDef b ) => !(a == b);
|
||||
public override bool Equals( object p ) => this.Equals( (InventoryDef)p );
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
public bool Equals( InventoryDef p )
|
||||
{
|
||||
if ( p == null ) return false;
|
||||
return p.Id == Id;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
182
Facepunch.Steamworks/Structs/InventoryItem.cs
Normal file
182
Facepunch.Steamworks/Structs/InventoryItem.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public struct InventoryItem : IEquatable<InventoryItem>
|
||||
{
|
||||
internal InventoryItemId _id;
|
||||
internal InventoryDefId _def;
|
||||
internal SteamItemFlags _flags;
|
||||
internal ushort _quantity;
|
||||
internal Dictionary<string, string> _properties;
|
||||
|
||||
public InventoryItemId Id => _id;
|
||||
|
||||
public InventoryDefId DefId => _def;
|
||||
|
||||
public int Quantity => _quantity;
|
||||
|
||||
public InventoryDef Def => SteamInventory.FindDefinition( DefId );
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Only available if the result set was created with the getproperties
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Properties => _properties;
|
||||
|
||||
/// <summary>
|
||||
/// This item is account-locked and cannot be traded or given away.
|
||||
/// This is an item status flag which is permanently attached to specific item instances
|
||||
/// </summary>
|
||||
public bool IsNoTrade => _flags.HasFlag( SteamItemFlags.NoTrade );
|
||||
|
||||
/// <summary>
|
||||
/// The item has been destroyed, traded away, expired, or otherwise invalidated.
|
||||
/// This is an action confirmation flag which is only set one time, as part of a result set.
|
||||
/// </summary>
|
||||
public bool IsRemoved => _flags.HasFlag( SteamItemFlags.Removed );
|
||||
|
||||
/// <summary>
|
||||
/// The item quantity has been decreased by 1 via ConsumeItem API.
|
||||
/// This is an action confirmation flag which is only set one time, as part of a result set.
|
||||
/// </summary>
|
||||
public bool IsConsumed => _flags.HasFlag( SteamItemFlags.Consumed );
|
||||
|
||||
/// <summary>
|
||||
/// Consumes items from a user's inventory. If the quantity of the given item goes to zero, it is permanently removed.
|
||||
/// Once an item is removed it cannot be recovered.This is not for the faint of heart - if your game implements item removal at all,
|
||||
/// a high-friction UI confirmation process is highly recommended.ConsumeItem can be restricted to certain item definitions or fully
|
||||
/// blocked via the Steamworks website to minimize support/abuse issues such as the classic "my brother borrowed my laptop and deleted all of my rare items".
|
||||
/// </summary>
|
||||
public async Task<InventoryResult?> ConsumeAsync( int amount = 1 )
|
||||
{
|
||||
var sresult = Defines.k_SteamInventoryResultInvalid;
|
||||
if ( !SteamInventory.Internal.ConsumeItem( ref sresult, Id, (uint)amount ) )
|
||||
return null;
|
||||
|
||||
return await InventoryResult.GetAsync( sresult );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split stack into two items
|
||||
/// </summary>
|
||||
public async Task<InventoryResult?> SplitStackAsync( int quantity = 1 )
|
||||
{
|
||||
var sresult = Defines.k_SteamInventoryResultInvalid;
|
||||
if ( !SteamInventory.Internal.TransferItemQuantity( ref sresult, Id, (uint)quantity, ulong.MaxValue ) )
|
||||
return null;
|
||||
|
||||
return await InventoryResult.GetAsync( sresult );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add x units of the target item to this item
|
||||
/// </summary>
|
||||
public async Task<InventoryResult?> AddAsync( InventoryItem add, int quantity = 1 )
|
||||
{
|
||||
var sresult = Defines.k_SteamInventoryResultInvalid;
|
||||
if ( !SteamInventory.Internal.TransferItemQuantity( ref sresult, add.Id, (uint)quantity, Id ) )
|
||||
return null;
|
||||
|
||||
return await InventoryResult.GetAsync( sresult );
|
||||
}
|
||||
|
||||
|
||||
internal static InventoryItem From( SteamItemDetails_t details )
|
||||
{
|
||||
var i = new InventoryItem
|
||||
{
|
||||
_id = details.ItemId,
|
||||
_def = details.Definition,
|
||||
_flags = (SteamItemFlags) details.Flags,
|
||||
_quantity = details.Quantity
|
||||
};
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
internal static Dictionary<string, string> GetProperties( SteamInventoryResult_t result, int index )
|
||||
{
|
||||
var strlen = (uint) Helpers.MemoryBufferSize;
|
||||
|
||||
if ( !SteamInventory.Internal.GetResultItemProperty( result, (uint)index, null, out var propNames, ref strlen ) )
|
||||
return null;
|
||||
|
||||
var props = new Dictionary<string, string>();
|
||||
|
||||
foreach ( var propertyName in propNames.Split( ',' ) )
|
||||
{
|
||||
strlen = (uint)Helpers.MemoryBufferSize;
|
||||
|
||||
if ( SteamInventory.Internal.GetResultItemProperty( result, (uint)index, propertyName, out var strVal, ref strlen ) )
|
||||
{
|
||||
props.Add( propertyName, strVal );
|
||||
}
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will try to return the date that this item was aquired. You need to have for the items
|
||||
/// with their properties for this to work.
|
||||
/// </summary>
|
||||
public DateTime Acquired
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( Properties == null ) return DateTime.UtcNow;
|
||||
|
||||
if ( Properties.TryGetValue( "acquired", out var str ) )
|
||||
{
|
||||
var y = int.Parse( str.Substring( 0, 4 ) );
|
||||
var m = int.Parse( str.Substring( 4, 2 ) );
|
||||
var d = int.Parse( str.Substring( 6, 2 ) );
|
||||
|
||||
var h = int.Parse( str.Substring( 9, 2 ) );
|
||||
var mn = int.Parse( str.Substring( 11, 2 ) );
|
||||
var s = int.Parse( str.Substring( 13, 2 ) );
|
||||
|
||||
return new DateTime( y, m, d, h, mn, s, DateTimeKind.Utc );
|
||||
}
|
||||
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the origin property. Need properties for this to work.
|
||||
/// Will return a string like "market"
|
||||
/// </summary>
|
||||
public string Origin
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( Properties == null ) return null;
|
||||
|
||||
if ( Properties.TryGetValue( "origin", out var str ) )
|
||||
return str;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Small utility class to describe an item with a quantity
|
||||
/// </summary>
|
||||
public struct Amount
|
||||
{
|
||||
public InventoryItem Item;
|
||||
public int Quantity;
|
||||
}
|
||||
|
||||
public static bool operator ==( InventoryItem a, InventoryItem b ) => a._id == b._id;
|
||||
public static bool operator !=( InventoryItem a, InventoryItem b ) => a._id != b._id;
|
||||
public override bool Equals( object p ) => this.Equals( (InventoryItem)p );
|
||||
public override int GetHashCode() => _id.GetHashCode();
|
||||
public bool Equals( InventoryItem p ) => p._id == _id;
|
||||
}
|
||||
}
|
||||
16
Facepunch.Steamworks/Structs/InventoryPurchaseResult.cs
Normal file
16
Facepunch.Steamworks/Structs/InventoryPurchaseResult.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct InventoryPurchaseResult
|
||||
{
|
||||
public Result Result;
|
||||
public ulong OrderID;
|
||||
public ulong TransID;
|
||||
}
|
||||
}
|
||||
104
Facepunch.Steamworks/Structs/InventoryRecipe.cs
Normal file
104
Facepunch.Steamworks/Structs/InventoryRecipe.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
/// <summary>
|
||||
/// A structured description of an item exchange
|
||||
/// </summary>
|
||||
public struct InventoryRecipe : IEquatable<InventoryRecipe>
|
||||
{
|
||||
public struct Ingredient
|
||||
{
|
||||
/// <summary>
|
||||
/// The definition ID of the ingredient.
|
||||
/// </summary>
|
||||
public int DefinitionId;
|
||||
|
||||
/// <summary>
|
||||
/// If we don't know about this item definition this might be null.
|
||||
/// In which case, DefinitionId should still hold the correct id.
|
||||
/// </summary>
|
||||
public InventoryDef Definition;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of this item needed. Generally this will be 1.
|
||||
/// </summary>
|
||||
public int Count;
|
||||
|
||||
internal static Ingredient FromString( string part )
|
||||
{
|
||||
var i = new Ingredient();
|
||||
i.Count = 1;
|
||||
|
||||
try
|
||||
{
|
||||
if ( part.Contains( "x" ) )
|
||||
{
|
||||
var idx = part.IndexOf( 'x' );
|
||||
|
||||
int count = 0;
|
||||
if ( int.TryParse( part.Substring( idx + 1 ), out count ) )
|
||||
i.Count = count;
|
||||
|
||||
part = part.Substring( 0, idx );
|
||||
}
|
||||
|
||||
i.DefinitionId = int.Parse( part );
|
||||
i.Definition = SteamInventory.FindDefinition( i.DefinitionId );
|
||||
|
||||
}
|
||||
catch ( System.Exception )
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The item that this will create.
|
||||
/// </summary>
|
||||
public InventoryDef Result;
|
||||
|
||||
/// <summary>
|
||||
/// The items, with quantity required to create this item.
|
||||
/// </summary>
|
||||
public Ingredient[] Ingredients;
|
||||
|
||||
public string Source;
|
||||
|
||||
internal static InventoryRecipe FromString( string part, InventoryDef Result )
|
||||
{
|
||||
var r = new InventoryRecipe
|
||||
{
|
||||
Result = Result,
|
||||
Source = part
|
||||
};
|
||||
|
||||
var parts = part.Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );
|
||||
|
||||
r.Ingredients = parts.Select( x => Ingredient.FromString( x ) ).Where( x => x.DefinitionId != 0 ).ToArray();
|
||||
return r;
|
||||
}
|
||||
|
||||
internal bool ContainsIngredient( InventoryDef inventoryDef )
|
||||
{
|
||||
return Ingredients.Any( x => x.DefinitionId == inventoryDef.Id );
|
||||
}
|
||||
|
||||
public static bool operator ==( InventoryRecipe a, InventoryRecipe b ) => a.GetHashCode() == b.GetHashCode();
|
||||
public static bool operator !=( InventoryRecipe a, InventoryRecipe b ) => a.GetHashCode() != b.GetHashCode();
|
||||
public override bool Equals( object p ) => this.Equals( (InventoryRecipe)p );
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Source.GetHashCode();
|
||||
}
|
||||
|
||||
public bool Equals( InventoryRecipe p ) => p.GetHashCode() == GetHashCode();
|
||||
}
|
||||
}
|
||||
118
Facepunch.Steamworks/Structs/InventoryResult.cs
Normal file
118
Facepunch.Steamworks/Structs/InventoryResult.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public struct InventoryResult : IDisposable
|
||||
{
|
||||
internal SteamInventoryResult_t _id;
|
||||
|
||||
public bool Expired { get; internal set; }
|
||||
|
||||
internal InventoryResult( SteamInventoryResult_t id, bool expired )
|
||||
{
|
||||
_id = id;
|
||||
Expired = expired;
|
||||
}
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get
|
||||
{
|
||||
uint cnt = 0;
|
||||
|
||||
if ( !SteamInventory.Internal.GetResultItems( _id, null, ref cnt ) )
|
||||
return 0;
|
||||
|
||||
return (int) cnt;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an inventory result handle belongs to the specified Steam ID.
|
||||
/// This is important when using Deserialize, to verify that a remote player is not pretending to have a different user's inventory
|
||||
/// </summary>
|
||||
public bool BelongsTo( SteamId steamId )
|
||||
{
|
||||
return SteamInventory.Internal.CheckResultSteamID( _id, steamId );
|
||||
}
|
||||
|
||||
public InventoryItem[] GetItems( bool includeProperties = false )
|
||||
{
|
||||
uint cnt = (uint) ItemCount;
|
||||
if ( cnt <= 0 ) return null;
|
||||
|
||||
var pOutItemsArray = new SteamItemDetails_t[cnt];
|
||||
|
||||
if ( !SteamInventory.Internal.GetResultItems( _id, pOutItemsArray, ref cnt ) )
|
||||
return null;
|
||||
|
||||
var items = new InventoryItem[cnt];
|
||||
|
||||
for( int i=0; i< cnt; i++ )
|
||||
{
|
||||
var item = InventoryItem.From( pOutItemsArray[i] );
|
||||
|
||||
if ( includeProperties )
|
||||
item._properties = InventoryItem.GetProperties( _id, i );
|
||||
|
||||
items[i] = item;
|
||||
}
|
||||
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if ( _id.Value == -1 ) return;
|
||||
|
||||
SteamInventory.Internal.DestroyResult( _id );
|
||||
}
|
||||
|
||||
internal static async Task<InventoryResult?> GetAsync( SteamInventoryResult_t sresult )
|
||||
{
|
||||
var _result = Result.Pending;
|
||||
while ( _result == Result.Pending )
|
||||
{
|
||||
_result = SteamInventory.Internal.GetResultStatus( sresult );
|
||||
await Task.Delay( 10 );
|
||||
}
|
||||
|
||||
if ( _result != Result.OK && _result != Result.Expired )
|
||||
return null;
|
||||
|
||||
return new InventoryResult( sresult, _result == Result.Expired );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialized result sets contain a short signature which can't be forged or replayed across different game sessions.
|
||||
/// A result set can be serialized on the local client, transmitted to other players via your game networking, and
|
||||
/// deserialized by the remote players.This is a secure way of preventing hackers from lying about posessing
|
||||
/// rare/high-value items. Serializes a result set with signature bytes to an output buffer.The size of a serialized
|
||||
/// result depends on the number items which are being serialized.When securely transmitting items to other players,
|
||||
/// it is recommended to use GetItemsByID first to create a minimal result set.
|
||||
/// Results have a built-in timestamp which will be considered "expired" after an hour has elapsed.See DeserializeResult
|
||||
/// for expiration handling.
|
||||
/// </summary>
|
||||
public unsafe byte[] Serialize()
|
||||
{
|
||||
uint size = 0;
|
||||
|
||||
if ( !SteamInventory.Internal.SerializeResult( _id, IntPtr.Zero, ref size ) )
|
||||
return null;
|
||||
|
||||
var data = new byte[size];
|
||||
|
||||
fixed ( byte* ptr = data )
|
||||
{
|
||||
if ( !SteamInventory.Internal.SerializeResult( _id, (IntPtr)ptr, ref size ) )
|
||||
return null;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Facepunch.Steamworks/Structs/Leaderboard.cs
Normal file
145
Facepunch.Steamworks/Structs/Leaderboard.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct Leaderboard
|
||||
{
|
||||
internal SteamLeaderboard_t Id;
|
||||
|
||||
/// <summary>
|
||||
/// the name of a leaderboard
|
||||
/// </summary>
|
||||
public string Name => SteamUserStats.Internal.GetLeaderboardName( Id );
|
||||
public LeaderboardSort Sort => SteamUserStats.Internal.GetLeaderboardSortMethod( Id );
|
||||
public LeaderboardDisplay Display => SteamUserStats.Internal.GetLeaderboardDisplayType( Id );
|
||||
public int EntryCount => SteamUserStats.Internal.GetLeaderboardEntryCount(Id);
|
||||
|
||||
static int[] detailsBuffer = new int[64];
|
||||
static int[] noDetails = Array.Empty<int>();
|
||||
|
||||
/// <summary>
|
||||
/// Submit your score and replace your old score even if it was better
|
||||
/// </summary>
|
||||
public async Task<LeaderboardUpdate?> ReplaceScore( int score, int[] details = null )
|
||||
{
|
||||
if ( details == null ) details = noDetails;
|
||||
|
||||
var r = await SteamUserStats.Internal.UploadLeaderboardScore( Id, LeaderboardUploadScoreMethod.ForceUpdate, score, details, details.Length );
|
||||
if ( !r.HasValue ) return null;
|
||||
|
||||
return LeaderboardUpdate.From( r.Value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submit your new score, but won't replace your high score if it's lower
|
||||
/// </summary>
|
||||
public async Task<LeaderboardUpdate?> SubmitScoreAsync( int score, int[] details = null )
|
||||
{
|
||||
if ( details == null ) details = noDetails;
|
||||
|
||||
var r = await SteamUserStats.Internal.UploadLeaderboardScore( Id, LeaderboardUploadScoreMethod.KeepBest, score, details, details.Length );
|
||||
if ( !r.HasValue ) return null;
|
||||
|
||||
return LeaderboardUpdate.From( r.Value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a piece of user generated content the user's entry on a leaderboard
|
||||
/// </summary>
|
||||
public async Task<Result> AttachUgc( Ugc file )
|
||||
{
|
||||
var r = await SteamUserStats.Internal.AttachLeaderboardUGC( Id, file.Handle );
|
||||
if ( !r.HasValue ) return Result.Fail;
|
||||
|
||||
return r.Value.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to query for a sequential range of leaderboard entries by leaderboard Sort.
|
||||
/// </summary>
|
||||
public async Task<LeaderboardEntry[]> GetScoresAsync( int count, int offset = 1 )
|
||||
{
|
||||
if ( offset <= 0 ) throw new System.ArgumentException( "Should be 1+", nameof( offset ) );
|
||||
|
||||
var r = await SteamUserStats.Internal.DownloadLeaderboardEntries( Id, LeaderboardDataRequest.Global, offset, offset + count );
|
||||
if ( !r.HasValue )
|
||||
return null;
|
||||
|
||||
return await LeaderboardResultToEntries( r.Value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to retrieve leaderboard entries relative a user's entry. If there are not enough entries in the leaderboard
|
||||
/// before or after the user's entry, Steam will adjust the range to try to return the number of entries requested.
|
||||
/// For example, if the user is #1 on the leaderboard and start is set to -2, end is set to 2, Steam will return the first
|
||||
/// 5 entries in the leaderboard. If The current user has no entry, this will return null.
|
||||
/// </summary>
|
||||
public async Task<LeaderboardEntry[]> GetScoresAroundUserAsync( int start = -10, int end = 10 )
|
||||
{
|
||||
var r = await SteamUserStats.Internal.DownloadLeaderboardEntries( Id, LeaderboardDataRequest.GlobalAroundUser, start, end );
|
||||
if ( !r.HasValue )
|
||||
return null;
|
||||
|
||||
return await LeaderboardResultToEntries( r.Value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to retrieve all leaderboard entries for friends of the current user
|
||||
/// </summary>
|
||||
public async Task<LeaderboardEntry[]> GetScoresFromFriendsAsync()
|
||||
{
|
||||
var r = await SteamUserStats.Internal.DownloadLeaderboardEntries( Id, LeaderboardDataRequest.Friends, 0, 0 );
|
||||
if ( !r.HasValue )
|
||||
return null;
|
||||
|
||||
return await LeaderboardResultToEntries( r.Value );
|
||||
}
|
||||
|
||||
#region util
|
||||
internal async Task<LeaderboardEntry[]> LeaderboardResultToEntries( LeaderboardScoresDownloaded_t r )
|
||||
{
|
||||
if ( r.CEntryCount <= 0 )
|
||||
return null;
|
||||
|
||||
var output = new LeaderboardEntry[r.CEntryCount];
|
||||
var e = default( LeaderboardEntry_t );
|
||||
|
||||
for ( int i = 0; i < output.Length; i++ )
|
||||
{
|
||||
if ( SteamUserStats.Internal.GetDownloadedLeaderboardEntry( r.SteamLeaderboardEntries, i, ref e, detailsBuffer, detailsBuffer.Length ) )
|
||||
{
|
||||
output[i] = LeaderboardEntry.From( e, detailsBuffer );
|
||||
}
|
||||
}
|
||||
|
||||
await WaitForUserNames( output );
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
internal static async Task WaitForUserNames( LeaderboardEntry[] entries)
|
||||
{
|
||||
bool gotAll = false;
|
||||
while ( !gotAll )
|
||||
{
|
||||
gotAll = true;
|
||||
|
||||
foreach ( var entry in entries )
|
||||
{
|
||||
if ( entry.User.Id == 0 ) continue;
|
||||
if ( !SteamFriends.Internal.RequestUserInformation( entry.User.Id, true ) ) continue;
|
||||
|
||||
gotAll = false;
|
||||
}
|
||||
|
||||
await Task.Delay( 1 );
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
31
Facepunch.Steamworks/Structs/LeaderboardEntry.cs
Normal file
31
Facepunch.Steamworks/Structs/LeaderboardEntry.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct LeaderboardEntry
|
||||
{
|
||||
public Friend User;
|
||||
public int GlobalRank;
|
||||
public int Score;
|
||||
public int[] Details;
|
||||
// UGCHandle_t m_hUGC
|
||||
|
||||
internal static LeaderboardEntry From( LeaderboardEntry_t e, int[] detailsBuffer )
|
||||
{
|
||||
var r = new LeaderboardEntry
|
||||
{
|
||||
User = new Friend( e.SteamIDUser ),
|
||||
GlobalRank = e.GlobalRank,
|
||||
Score = e.Score,
|
||||
Details = null
|
||||
};
|
||||
|
||||
if ( e.CDetails > 0 )
|
||||
{
|
||||
r.Details = detailsBuffer.Take( e.CDetails ).ToArray();
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Facepunch.Steamworks/Structs/LeaderboardUpdate.cs
Normal file
22
Facepunch.Steamworks/Structs/LeaderboardUpdate.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct LeaderboardUpdate
|
||||
{
|
||||
public int Score;
|
||||
public bool Changed;
|
||||
public int NewGlobalRank;
|
||||
public int OldGlobalRank;
|
||||
public int RankChange => NewGlobalRank - OldGlobalRank;
|
||||
|
||||
internal static LeaderboardUpdate From( LeaderboardScoreUploaded_t e ) =>
|
||||
new LeaderboardUpdate
|
||||
{
|
||||
Score = e.Score,
|
||||
Changed = e.ScoreChanged == 1,
|
||||
NewGlobalRank = e.GlobalRankNew,
|
||||
OldGlobalRank = e.GlobalRankPrevious
|
||||
};
|
||||
}
|
||||
}
|
||||
251
Facepunch.Steamworks/Structs/Lobby.cs
Normal file
251
Facepunch.Steamworks/Structs/Lobby.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct Lobby
|
||||
{
|
||||
public SteamId Id { get; internal set; }
|
||||
|
||||
|
||||
public Lobby( SteamId id )
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to join this room. Will return RoomEnter.Success on success,
|
||||
/// and anything else is a failure
|
||||
/// </summary>
|
||||
public async Task<RoomEnter> Join()
|
||||
{
|
||||
var result = await SteamMatchmaking.Internal.JoinLobby( Id );
|
||||
if ( !result.HasValue ) return RoomEnter.Error;
|
||||
|
||||
return (RoomEnter) result.Value.EChatRoomEnterResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Leave a lobby; this will take effect immediately on the client side
|
||||
/// other users in the lobby will be notified by a LobbyChatUpdate_t callback
|
||||
/// </summary>
|
||||
public void Leave()
|
||||
{
|
||||
SteamMatchmaking.Internal.LeaveLobby( Id );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invite another user to the lobby
|
||||
/// will return true if the invite is successfully sent, whether or not the target responds
|
||||
/// returns false if the local user is not connected to the Steam servers
|
||||
/// </summary>
|
||||
public bool InviteFriend( SteamId steamid )
|
||||
{
|
||||
return SteamMatchmaking.Internal.InviteUserToLobby( Id, steamid );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the number of users in the specified lobby
|
||||
/// </summary>
|
||||
public int MemberCount => SteamMatchmaking.Internal.GetNumLobbyMembers( Id );
|
||||
|
||||
/// <summary>
|
||||
/// Returns current members. Need to be in the lobby to see the users.
|
||||
/// </summary>
|
||||
public IEnumerable<Friend> Members
|
||||
{
|
||||
get
|
||||
{
|
||||
for( int i = 0; i < MemberCount; i++ )
|
||||
{
|
||||
yield return new Friend( SteamMatchmaking.Internal.GetLobbyMemberByIndex( Id, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get data associated with this lobby
|
||||
/// </summary>
|
||||
public string GetData( string key )
|
||||
{
|
||||
return SteamMatchmaking.Internal.GetLobbyData( Id, key );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get data associated with this lobby
|
||||
/// </summary>
|
||||
public bool SetData( string key, string value )
|
||||
{
|
||||
if ( key.Length > 255 ) throw new System.ArgumentException( "Key should be < 255 chars", nameof( key ) );
|
||||
if ( value.Length > 8192 ) throw new System.ArgumentException( "Value should be < 8192 chars", nameof( key ) );
|
||||
|
||||
return SteamMatchmaking.Internal.SetLobbyData( Id, key, value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a metadata key from the lobby
|
||||
/// </summary>
|
||||
public bool DeleteData( string key )
|
||||
{
|
||||
return SteamMatchmaking.Internal.DeleteLobbyData( Id, key );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all data for this lobby
|
||||
/// </summary>
|
||||
public IEnumerable<KeyValuePair<string, string>> Data
|
||||
{
|
||||
get
|
||||
{
|
||||
var cnt = SteamMatchmaking.Internal.GetLobbyDataCount( Id );
|
||||
|
||||
for ( int i =0; i<cnt; i++)
|
||||
{
|
||||
if ( SteamMatchmaking.Internal.GetLobbyDataByIndex( Id, i, out var a, out var b ) )
|
||||
{
|
||||
yield return new KeyValuePair<string, string>( a, b );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets per-user metadata for someone in this lobby
|
||||
/// </summary>
|
||||
public string GetMemberData( Friend member, string key )
|
||||
{
|
||||
return SteamMatchmaking.Internal.GetLobbyMemberData( Id, member.Id, key );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets per-user metadata (for the local user implicitly)
|
||||
/// </summary>
|
||||
public void SetMemberData( string key, string value )
|
||||
{
|
||||
SteamMatchmaking.Internal.SetLobbyMemberData( Id, key, value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a string to the chat room
|
||||
/// </summary>
|
||||
public bool SendChatString( string message )
|
||||
{
|
||||
var data = System.Text.Encoding.UTF8.GetBytes( message );
|
||||
return SendChatBytes( data );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends bytes the the chat room
|
||||
/// this isn't exposed because there's no way to read raw bytes atm,
|
||||
/// and I figure people can send json if they want something more advanced
|
||||
/// </summary>
|
||||
internal unsafe bool SendChatBytes( byte[] data )
|
||||
{
|
||||
fixed ( byte* ptr = data )
|
||||
{
|
||||
return SteamMatchmaking.Internal.SendLobbyChatMsg( Id, (IntPtr)ptr, data.Length );
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes metadata for a lobby you're not necessarily in right now
|
||||
/// you never do this for lobbies you're a member of, only if your
|
||||
/// this will send down all the metadata associated with a lobby
|
||||
/// this is an asynchronous call
|
||||
/// returns false if the local user is not connected to the Steam servers
|
||||
/// results will be returned by a LobbyDataUpdate_t callback
|
||||
/// if the specified lobby doesn't exist, LobbyDataUpdate_t::m_bSuccess will be set to false
|
||||
/// </summary>
|
||||
public bool Refresh()
|
||||
{
|
||||
return SteamMatchmaking.Internal.RequestLobbyData( Id );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Max members able to join this lobby. Cannot be over 250.
|
||||
/// Can only be set by the owner
|
||||
/// </summary>
|
||||
public int MaxMembers
|
||||
{
|
||||
get => SteamMatchmaking.Internal.GetLobbyMemberLimit( Id );
|
||||
set => SteamMatchmaking.Internal.SetLobbyMemberLimit( Id, value );
|
||||
}
|
||||
|
||||
public bool SetPublic()
|
||||
{
|
||||
return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.Public );
|
||||
}
|
||||
|
||||
public bool SetPrivate()
|
||||
{
|
||||
return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.Private );
|
||||
}
|
||||
|
||||
public bool SetInvisible()
|
||||
{
|
||||
return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.Invisible );
|
||||
}
|
||||
|
||||
public bool SetFriendsOnly()
|
||||
{
|
||||
return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.FriendsOnly );
|
||||
}
|
||||
|
||||
public bool SetJoinable( bool b )
|
||||
{
|
||||
return SteamMatchmaking.Internal.SetLobbyJoinable( Id, b );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [SteamID variant]
|
||||
/// Allows the owner to set the game server associated with the lobby. Triggers the
|
||||
/// Steammatchmaking.OnLobbyGameCreated event.
|
||||
/// </summary>
|
||||
public void SetGameServer( SteamId steamServer )
|
||||
{
|
||||
if ( !steamServer.IsValid )
|
||||
throw new ArgumentException( $"SteamId for server is invalid" );
|
||||
|
||||
SteamMatchmaking.Internal.SetLobbyGameServer( Id, 0, 0, steamServer );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [IP/Port variant]
|
||||
/// Allows the owner to set the game server associated with the lobby. Triggers the
|
||||
/// Steammatchmaking.OnLobbyGameCreated event.
|
||||
/// </summary>
|
||||
public void SetGameServer( string ip, ushort port )
|
||||
{
|
||||
if ( !IPAddress.TryParse( ip, out IPAddress add ) )
|
||||
throw new ArgumentException( $"IP address for server is invalid" );
|
||||
|
||||
SteamMatchmaking.Internal.SetLobbyGameServer( Id, add.IpToInt32(), port, new SteamId() );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the details of the lobby's game server, if set. Returns true if the lobby is
|
||||
/// valid and has a server set, otherwise returns false.
|
||||
/// </summary>
|
||||
public bool GetGameServer( ref uint ip, ref ushort port, ref SteamId serverId )
|
||||
{
|
||||
return SteamMatchmaking.Internal.GetLobbyGameServer( Id, ref ip, ref port, ref serverId );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// You must be the lobby owner to set the owner
|
||||
/// </summary>
|
||||
public Friend Owner
|
||||
{
|
||||
get => new Friend( SteamMatchmaking.Internal.GetLobbyOwner( Id ) );
|
||||
set => SteamMatchmaking.Internal.SetLobbyOwner( Id, value.Id );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the specified SteamId owns the lobby
|
||||
/// </summary>
|
||||
public bool IsOwnedBy( SteamId k ) => Owner.Id == k;
|
||||
}
|
||||
}
|
||||
240
Facepunch.Steamworks/Structs/LobbyQuery.cs
Normal file
240
Facepunch.Steamworks/Structs/LobbyQuery.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct LobbyQuery
|
||||
{
|
||||
// TODO FILTERS
|
||||
// AddRequestLobbyListStringFilter
|
||||
// - WithoutKeyValue
|
||||
|
||||
#region Distance Filter
|
||||
internal LobbyDistanceFilter? distance;
|
||||
|
||||
/// <summary>
|
||||
/// only lobbies in the same immediate region will be returned
|
||||
/// </summary>
|
||||
public LobbyQuery FilterDistanceClose()
|
||||
{
|
||||
distance = LobbyDistanceFilter.Close;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// only lobbies in the same immediate region will be returned
|
||||
/// </summary>
|
||||
public LobbyQuery FilterDistanceFar()
|
||||
{
|
||||
distance = LobbyDistanceFilter.Far;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// only lobbies in the same immediate region will be returned
|
||||
/// </summary>
|
||||
public LobbyQuery FilterDistanceWorldwide()
|
||||
{
|
||||
distance = LobbyDistanceFilter.Worldwide;
|
||||
return this;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region String key/value filter
|
||||
internal Dictionary<string, string> stringFilters;
|
||||
|
||||
/// <summary>
|
||||
/// Filter by specified key/value pair; string parameters
|
||||
/// </summary>
|
||||
public LobbyQuery WithKeyValue( string key, string value )
|
||||
{
|
||||
if ( string.IsNullOrEmpty( key ) )
|
||||
throw new System.ArgumentException( "Key string provided for LobbyQuery filter is null or empty", nameof( key ) );
|
||||
|
||||
if ( key.Length > SteamMatchmaking.MaxLobbyKeyLength )
|
||||
throw new System.ArgumentException( $"Key length is longer than {SteamMatchmaking.MaxLobbyKeyLength}", nameof( key ) );
|
||||
|
||||
if ( stringFilters == null )
|
||||
stringFilters = new Dictionary<string, string>();
|
||||
|
||||
stringFilters.Add( key, value );
|
||||
|
||||
return this;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Numerical filters
|
||||
internal List<NumericalFilter> numericalFilters;
|
||||
|
||||
/// <summary>
|
||||
/// Numerical filter where value is less than the value provided
|
||||
/// </summary>
|
||||
public LobbyQuery WithLower( string key, int value )
|
||||
{
|
||||
AddNumericalFilter( key, value, LobbyComparison.LessThan );
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numerical filter where value is greater than the value provided
|
||||
/// </summary>
|
||||
public LobbyQuery WithHigher( string key, int value )
|
||||
{
|
||||
AddNumericalFilter( key, value, LobbyComparison.GreaterThan );
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numerical filter where value must be equal to the value provided
|
||||
/// </summary>
|
||||
public LobbyQuery WithEqual( string key, int value )
|
||||
{
|
||||
AddNumericalFilter( key, value, LobbyComparison.Equal );
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numerical filter where value must not equal the value provided
|
||||
/// </summary>
|
||||
public LobbyQuery WithNotEqual( string key, int value )
|
||||
{
|
||||
AddNumericalFilter( key, value, LobbyComparison.NotEqual );
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test key, initialize numerical filter list if necessary, then add new numerical filter
|
||||
/// </summary>
|
||||
internal void AddNumericalFilter( string key, int value, LobbyComparison compare )
|
||||
{
|
||||
if ( string.IsNullOrEmpty( key ) )
|
||||
throw new System.ArgumentException( "Key string provided for LobbyQuery filter is null or empty", nameof( key ) );
|
||||
|
||||
if ( key.Length > SteamMatchmaking.MaxLobbyKeyLength )
|
||||
throw new System.ArgumentException( $"Key length is longer than {SteamMatchmaking.MaxLobbyKeyLength}", nameof( key ) );
|
||||
|
||||
if ( numericalFilters == null )
|
||||
numericalFilters = new List<NumericalFilter>();
|
||||
|
||||
numericalFilters.Add( new NumericalFilter( key, value, compare ) );
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Near value filter
|
||||
internal Dictionary<string, int> nearValFilters;
|
||||
|
||||
/// <summary>
|
||||
/// Order filtered results according to key/values nearest the provided key/value pair.
|
||||
/// Can specify multiple near value filters; each successive filter is lower priority than the previous.
|
||||
/// </summary>
|
||||
public LobbyQuery OrderByNear( string key, int value )
|
||||
{
|
||||
if ( string.IsNullOrEmpty( key ) )
|
||||
throw new System.ArgumentException( "Key string provided for LobbyQuery filter is null or empty", nameof( key ) );
|
||||
|
||||
if ( key.Length > SteamMatchmaking.MaxLobbyKeyLength )
|
||||
throw new System.ArgumentException( $"Key length is longer than {SteamMatchmaking.MaxLobbyKeyLength}", nameof( key ) );
|
||||
|
||||
if ( nearValFilters == null )
|
||||
nearValFilters = new Dictionary<string, int>();
|
||||
|
||||
nearValFilters.Add( key, value );
|
||||
|
||||
return this;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Slots Filter
|
||||
internal int? slotsAvailable;
|
||||
|
||||
/// <summary>
|
||||
/// returns only lobbies with the specified number of slots available
|
||||
/// </summary>
|
||||
public LobbyQuery WithSlotsAvailable( int minSlots )
|
||||
{
|
||||
slotsAvailable = minSlots;
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Max results filter
|
||||
internal int? maxResults;
|
||||
|
||||
/// <summary>
|
||||
/// sets how many results to return, the lower the count the faster it is to download the lobby results
|
||||
/// </summary>
|
||||
public LobbyQuery WithMaxResults( int max )
|
||||
{
|
||||
maxResults = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void ApplyFilters()
|
||||
{
|
||||
if ( distance.HasValue )
|
||||
{
|
||||
SteamMatchmaking.Internal.AddRequestLobbyListDistanceFilter( distance.Value );
|
||||
}
|
||||
|
||||
if ( slotsAvailable.HasValue )
|
||||
{
|
||||
SteamMatchmaking.Internal.AddRequestLobbyListFilterSlotsAvailable( slotsAvailable.Value );
|
||||
}
|
||||
|
||||
if ( maxResults.HasValue )
|
||||
{
|
||||
SteamMatchmaking.Internal.AddRequestLobbyListResultCountFilter( maxResults.Value );
|
||||
}
|
||||
|
||||
if ( stringFilters != null )
|
||||
{
|
||||
foreach ( var k in stringFilters )
|
||||
{
|
||||
SteamMatchmaking.Internal.AddRequestLobbyListStringFilter( k.Key, k.Value, LobbyComparison.Equal );
|
||||
}
|
||||
}
|
||||
|
||||
if( numericalFilters != null )
|
||||
{
|
||||
foreach ( var n in numericalFilters )
|
||||
{
|
||||
SteamMatchmaking.Internal.AddRequestLobbyListNumericalFilter( n.Key, n.Value, n.Comparer );
|
||||
}
|
||||
}
|
||||
|
||||
if( nearValFilters != null )
|
||||
{
|
||||
foreach (var v in nearValFilters )
|
||||
{
|
||||
SteamMatchmaking.Internal.AddRequestLobbyListNearValueFilter( v.Key, v.Value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the query, get the matching lobbies
|
||||
/// </summary>
|
||||
public async Task<Lobby[]> RequestAsync()
|
||||
{
|
||||
ApplyFilters();
|
||||
|
||||
LobbyMatchList_t? list = await SteamMatchmaking.Internal.RequestLobbyList();
|
||||
if ( !list.HasValue || list.Value.LobbiesMatching == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Lobby[] lobbies = new Lobby[list.Value.LobbiesMatching];
|
||||
|
||||
for ( int i = 0; i < list.Value.LobbiesMatching; i++ )
|
||||
{
|
||||
lobbies[i] = new Lobby { Id = SteamMatchmaking.Internal.GetLobbyByIndex( i ) };
|
||||
}
|
||||
|
||||
return lobbies;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Facepunch.Steamworks/Structs/MatchMakingKeyValuePair.cs
Normal file
16
Facepunch.Steamworks/Structs/MatchMakingKeyValuePair.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
[StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )]
|
||||
internal partial struct MatchMakingKeyValuePair
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
internal string Key;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
internal string Value;
|
||||
}
|
||||
|
||||
}
|
||||
16
Facepunch.Steamworks/Structs/NumericalFilter.cs
Normal file
16
Facepunch.Steamworks/Structs/NumericalFilter.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
struct NumericalFilter
|
||||
{
|
||||
public string Key { get; internal set; }
|
||||
public int Value { get; internal set; }
|
||||
public LobbyComparison Comparer { get; internal set; }
|
||||
|
||||
internal NumericalFilter ( string k, int v, LobbyComparison c )
|
||||
{
|
||||
Key = k;
|
||||
Value = v;
|
||||
Comparer = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Facepunch.Steamworks/Structs/OutgoingPacket.cs
Normal file
30
Facepunch.Steamworks/Structs/OutgoingPacket.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// A server query packet.
|
||||
/// </summary>
|
||||
public struct OutgoingPacket
|
||||
{
|
||||
/// <summary>
|
||||
/// Target IP address
|
||||
/// </summary>
|
||||
public uint Address { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Target port
|
||||
/// </summary>
|
||||
public ushort Port { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// This data is pooled. Make a copy if you don't use it immediately.
|
||||
/// This buffer is also quite large - so pay attention to Size.
|
||||
/// </summary>
|
||||
public byte[] Data { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the data
|
||||
/// </summary>
|
||||
public int Size { get; internal set; }
|
||||
}
|
||||
|
||||
}
|
||||
8
Facepunch.Steamworks/Structs/P2Packet.cs
Normal file
8
Facepunch.Steamworks/Structs/P2Packet.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct P2Packet
|
||||
{
|
||||
public SteamId SteamId;
|
||||
public byte[] Data;
|
||||
}
|
||||
}
|
||||
80
Facepunch.Steamworks/Structs/PartyBeacon.cs
Normal file
80
Facepunch.Steamworks/Structs/PartyBeacon.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public struct PartyBeacon
|
||||
{
|
||||
static ISteamParties Internal => SteamParties.Internal;
|
||||
|
||||
internal PartyBeaconID_t Id;
|
||||
|
||||
/// <summary>
|
||||
/// Creator of the beacon
|
||||
/// </summary>
|
||||
public SteamId Owner
|
||||
{
|
||||
get
|
||||
{
|
||||
var owner = default( SteamId );
|
||||
var location = default( SteamPartyBeaconLocation_t );
|
||||
Internal.GetBeaconDetails( Id, ref owner, ref location, out _ );
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creator of the beacon
|
||||
/// </summary>
|
||||
public string MetaData
|
||||
{
|
||||
get
|
||||
{
|
||||
var owner = default( SteamId );
|
||||
var location = default( SteamPartyBeaconLocation_t );
|
||||
_ = Internal.GetBeaconDetails( Id, ref owner, ref location, out var strVal );
|
||||
return strVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will attempt to join the party. If successful will return a connection string.
|
||||
/// If failed, will return null
|
||||
/// </summary>
|
||||
public async Task<string> JoinAsync()
|
||||
{
|
||||
var result = await Internal.JoinParty( Id );
|
||||
if ( !result.HasValue || result.Value.Result != Result.OK )
|
||||
return null;
|
||||
|
||||
return result.Value.ConnectStringUTF8();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a user follows your beacon, Steam will reserve one of the open party slots for them, and send your game a ReservationNotification callback.
|
||||
/// When that user joins your party, call OnReservationCompleted to notify Steam that the user has joined successfully
|
||||
/// </summary>
|
||||
public void OnReservationCompleted( SteamId steamid )
|
||||
{
|
||||
Internal.OnReservationCompleted( Id, steamid );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To cancel a reservation (due to timeout or user input), call this.
|
||||
/// Steam will open a new reservation slot.
|
||||
/// Note: The user may already be in-flight to your game, so it's possible they will still connect and try to join your party.
|
||||
/// </summary>
|
||||
public void CancelReservation( SteamId steamid )
|
||||
{
|
||||
Internal.CancelReservation( Id, steamid );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn off the beacon
|
||||
/// </summary>
|
||||
public bool Destroy()
|
||||
{
|
||||
return Internal.DestroyBeacon( Id );
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Facepunch.Steamworks/Structs/RemotePlaySession.cs
Normal file
38
Facepunch.Steamworks/Structs/RemotePlaySession.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a RemotePlaySession from the SteamRemotePlay interface
|
||||
/// </summary>
|
||||
public struct RemotePlaySession
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
|
||||
public override string ToString() => Id.ToString();
|
||||
public static implicit operator RemotePlaySession( uint value ) => new RemotePlaySession() { Id = value };
|
||||
public static implicit operator uint( RemotePlaySession value ) => value.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this session was valid when created. This will stay true even
|
||||
/// after disconnection - so be sure to watch SteamRemotePlay.OnSessionDisconnected
|
||||
/// </summary>
|
||||
public bool IsValid => Id > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Get the SteamID of the connected user
|
||||
/// </summary>
|
||||
public SteamId SteamId => SteamRemotePlay.Internal.GetSessionSteamID( Id );
|
||||
|
||||
/// <summary>
|
||||
/// Get the name of the session client device
|
||||
/// </summary>
|
||||
public string ClientName => SteamRemotePlay.Internal.GetSessionClientName( Id );
|
||||
|
||||
/// <summary>
|
||||
/// Get the name of the session client device
|
||||
/// </summary>
|
||||
public SteamDeviceFormFactor FormFactor => SteamRemotePlay.Internal.GetSessionClientFormFactor( Id );
|
||||
}
|
||||
}
|
||||
37
Facepunch.Steamworks/Structs/Screenshot.cs
Normal file
37
Facepunch.Steamworks/Structs/Screenshot.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct Screenshot
|
||||
{
|
||||
internal ScreenshotHandle Value;
|
||||
|
||||
/// <summary>
|
||||
/// Tags a user as being visible in the screenshot
|
||||
/// </summary>
|
||||
public bool TagUser( SteamId user )
|
||||
{
|
||||
return SteamScreenshots.Internal.TagUser( Value, user );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tags a user as being visible in the screenshot
|
||||
/// </summary>
|
||||
public bool SetLocation( string location )
|
||||
{
|
||||
return SteamScreenshots.Internal.SetLocation( Value, location );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tags a user as being visible in the screenshot
|
||||
/// </summary>
|
||||
public bool TagPublishedFile( PublishedFileId file )
|
||||
{
|
||||
return SteamScreenshots.Internal.TagPublishedFile( Value, file );
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Facepunch.Steamworks/Structs/Server.cs
Normal file
145
Facepunch.Steamworks/Structs/Server.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct ServerInfo : IEquatable<ServerInfo>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Ping { get; set; }
|
||||
public string GameDir { get; set; }
|
||||
public string Map { get; set; }
|
||||
public string Description { get; set; }
|
||||
public uint AppId { get; set; }
|
||||
public int Players { get; set; }
|
||||
public int MaxPlayers { get; set; }
|
||||
public int BotPlayers { get; set; }
|
||||
public bool Passworded { get; set; }
|
||||
public bool Secure { get; set; }
|
||||
public uint LastTimePlayed { get; set; }
|
||||
public int Version { get; set; }
|
||||
public string TagString { get; set; }
|
||||
public ulong SteamId { get; set; }
|
||||
public uint AddressRaw { get; set; }
|
||||
public IPAddress Address { get; set; }
|
||||
public int ConnectionPort { get; set; }
|
||||
public int QueryPort { get; set; }
|
||||
|
||||
string[] _tags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the individual tags for this server
|
||||
/// </summary>
|
||||
public string[] Tags
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( _tags == null )
|
||||
{
|
||||
if ( !string.IsNullOrEmpty( TagString ) )
|
||||
{
|
||||
_tags = TagString.Split( ',' );
|
||||
}
|
||||
}
|
||||
|
||||
return _tags;
|
||||
}
|
||||
}
|
||||
|
||||
internal static ServerInfo From( gameserveritem_t item )
|
||||
{
|
||||
return new ServerInfo()
|
||||
{
|
||||
AddressRaw = item.NetAdr.IP,
|
||||
Address = Utility.Int32ToIp( item.NetAdr.IP ),
|
||||
ConnectionPort = item.NetAdr.ConnectionPort,
|
||||
QueryPort = item.NetAdr.QueryPort,
|
||||
Name = item.ServerNameUTF8(),
|
||||
Ping = item.Ping,
|
||||
GameDir = item.GameDirUTF8(),
|
||||
Map = item.MapUTF8(),
|
||||
Description = item.GameDescriptionUTF8(),
|
||||
AppId = item.AppID,
|
||||
Players = item.Players,
|
||||
MaxPlayers = item.MaxPlayers,
|
||||
BotPlayers = item.BotPlayers,
|
||||
Passworded = item.Password,
|
||||
Secure = item.Secure,
|
||||
LastTimePlayed = item.TimeLastPlayed,
|
||||
Version = item.ServerVersion,
|
||||
TagString = item.GameTagsUTF8(),
|
||||
SteamId = item.SteamID
|
||||
};
|
||||
}
|
||||
|
||||
public ServerInfo( uint ip, ushort cport, ushort qport, uint timeplayed ) : this()
|
||||
{
|
||||
AddressRaw = ip;
|
||||
Address = Utility.Int32ToIp( ip );
|
||||
ConnectionPort = cport;
|
||||
QueryPort = qport;
|
||||
LastTimePlayed = timeplayed;
|
||||
}
|
||||
|
||||
internal const uint k_unFavoriteFlagNone = 0x00;
|
||||
internal const uint k_unFavoriteFlagFavorite = 0x01; // this game favorite entry is for the favorites list
|
||||
internal const uint k_unFavoriteFlagHistory = 0x02; // this game favorite entry is for the history list
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add this server to our history list
|
||||
/// If we're already in the history list, weill set the last played time to now
|
||||
/// </summary>
|
||||
public void AddToHistory()
|
||||
{
|
||||
SteamMatchmaking.Internal.AddFavoriteGame( SteamClient.AppId, AddressRaw, (ushort)ConnectionPort, (ushort)QueryPort, k_unFavoriteFlagHistory, (uint)Epoch.Current );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this server responds to source engine style queries, we'll be able to get a list of rules here
|
||||
/// </summary>
|
||||
public async Task<Dictionary<string, string>> QueryRulesAsync()
|
||||
{
|
||||
return await SourceServerQuery.GetRules( this );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove this server from our history list
|
||||
/// </summary>
|
||||
public void RemoveFromHistory()
|
||||
{
|
||||
SteamMatchmaking.Internal.RemoveFavoriteGame( SteamClient.AppId, AddressRaw, (ushort)ConnectionPort, (ushort)QueryPort, k_unFavoriteFlagHistory );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add this server to our favourite list
|
||||
/// </summary>
|
||||
public void AddToFavourites()
|
||||
{
|
||||
SteamMatchmaking.Internal.AddFavoriteGame( SteamClient.AppId, AddressRaw, (ushort)ConnectionPort, (ushort)QueryPort, k_unFavoriteFlagFavorite, (uint)Epoch.Current );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove this server from our favourite list
|
||||
/// </summary>
|
||||
public void RemoveFromFavourites()
|
||||
{
|
||||
SteamMatchmaking.Internal.RemoveFavoriteGame( SteamClient.AppId, AddressRaw, (ushort)ConnectionPort, (ushort)QueryPort, k_unFavoriteFlagFavorite );
|
||||
}
|
||||
|
||||
public bool Equals( ServerInfo other )
|
||||
{
|
||||
return this.GetHashCode() == other.GetHashCode();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Address.GetHashCode() + SteamId.GetHashCode() + ConnectionPort.GetHashCode() + QueryPort.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Facepunch.Steamworks/Structs/ServerInit.cs
Normal file
80
Facepunch.Steamworks/Structs/ServerInit.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to set up the server.
|
||||
/// The variables in here are all required to be set, and can't be changed once the server is created.
|
||||
/// </summary>
|
||||
public struct SteamServerInit
|
||||
{
|
||||
public IPAddress IpAddress;
|
||||
public ushort SteamPort;
|
||||
public ushort GamePort;
|
||||
public ushort QueryPort;
|
||||
public bool Secure;
|
||||
|
||||
/// <summary>
|
||||
/// The version string is usually in the form x.x.x.x, and is used by the master server to detect when the server is out of date.
|
||||
/// If you go into the dedicated server tab on steamworks you'll be able to server the latest version. If this version number is
|
||||
/// less than that latest version then your server won't show.
|
||||
/// </summary>
|
||||
public string VersionString;
|
||||
|
||||
/// <summary>
|
||||
/// This should be the same directory game where gets installed into. Just the folder name, not the whole path. I.e. "Rust", "Garrysmod".
|
||||
/// </summary>
|
||||
public string ModDir;
|
||||
|
||||
/// <summary>
|
||||
/// The game description. Setting this to the full name of your game is recommended.
|
||||
/// </summary>
|
||||
public string GameDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Is a dedicated server
|
||||
/// </summary>
|
||||
public bool DedicatedServer;
|
||||
|
||||
|
||||
public SteamServerInit( string modDir, string gameDesc )
|
||||
{
|
||||
DedicatedServer = true;
|
||||
ModDir = modDir;
|
||||
GameDescription = gameDesc;
|
||||
GamePort = 27015;
|
||||
QueryPort = 27016;
|
||||
Secure = true;
|
||||
VersionString = "1.0.0.0";
|
||||
IpAddress = null;
|
||||
SteamPort = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Steam quert port
|
||||
/// </summary>
|
||||
public SteamServerInit WithRandomSteamPort()
|
||||
{
|
||||
SteamPort = (ushort)new Random().Next( 10000, 60000 );
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If you pass MASTERSERVERUPDATERPORT_USEGAMESOCKETSHARE into usQueryPort, then it causes the game server API to use
|
||||
/// "GameSocketShare" mode, which means that the game is responsible for sending and receiving UDP packets for the master
|
||||
/// server updater.
|
||||
///
|
||||
/// More info about this here: https://partner.steamgames.com/doc/api/ISteamGameServer#HandleIncomingPacket
|
||||
/// </summary>
|
||||
public SteamServerInit WithQueryShareGamePort()
|
||||
{
|
||||
QueryPort = 0xFFFF;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Facepunch.Steamworks/Structs/Stat.cs
Normal file
150
Facepunch.Steamworks/Structs/Stat.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct Stat
|
||||
{
|
||||
public string Name { get; internal set; }
|
||||
public SteamId UserId { get; internal set; }
|
||||
|
||||
public Stat( string name )
|
||||
{
|
||||
Name = name;
|
||||
UserId = 0;
|
||||
}
|
||||
|
||||
public Stat( string name, SteamId user )
|
||||
{
|
||||
Name = name;
|
||||
UserId = user;
|
||||
}
|
||||
|
||||
internal void LocalUserOnly( [CallerMemberName] string caller = null )
|
||||
{
|
||||
if ( UserId == 0 ) return;
|
||||
throw new System.Exception( $"Stat.{caller} can only be called for the local user" );
|
||||
}
|
||||
|
||||
public double GetGlobalFloat()
|
||||
{
|
||||
double val = 0.0;
|
||||
|
||||
if ( SteamUserStats.Internal.GetGlobalStat( Name, ref val ) )
|
||||
return val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long GetGlobalInt()
|
||||
{
|
||||
long val = 0;
|
||||
SteamUserStats.Internal.GetGlobalStat( Name, ref val );
|
||||
return val;
|
||||
}
|
||||
|
||||
public async Task<long[]> GetGlobalIntDaysAsync( int days )
|
||||
{
|
||||
var result = await SteamUserStats.Internal.RequestGlobalStats( days );
|
||||
if ( result?.Result != Result.OK ) return null;
|
||||
|
||||
var r = new long[days];
|
||||
|
||||
var rows = SteamUserStats.Internal.GetGlobalStatHistory( Name, r, (uint) r.Length * sizeof(long) );
|
||||
|
||||
if ( days != rows )
|
||||
r = r.Take( rows ).ToArray();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public async Task<double[]> GetGlobalFloatDays( int days )
|
||||
{
|
||||
var result = await SteamUserStats.Internal.RequestGlobalStats( days );
|
||||
if ( result?.Result != Result.OK ) return null;
|
||||
|
||||
var r = new double[days];
|
||||
|
||||
var rows = SteamUserStats.Internal.GetGlobalStatHistory( Name, r, (uint)r.Length * sizeof( double ) );
|
||||
|
||||
if ( days != rows )
|
||||
r = r.Take( rows ).ToArray();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public float GetFloat()
|
||||
{
|
||||
float val = 0.0f;
|
||||
|
||||
if ( UserId > 0 )
|
||||
{
|
||||
SteamUserStats.Internal.GetUserStat( UserId, Name, ref val );
|
||||
}
|
||||
else
|
||||
{
|
||||
SteamUserStats.Internal.GetStat( Name, ref val );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int GetInt()
|
||||
{
|
||||
int val = 0;
|
||||
|
||||
if ( UserId > 0 )
|
||||
{
|
||||
SteamUserStats.Internal.GetUserStat( UserId, Name, ref val );
|
||||
}
|
||||
else
|
||||
{
|
||||
SteamUserStats.Internal.GetStat( Name, ref val );
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public bool Set( int val )
|
||||
{
|
||||
LocalUserOnly();
|
||||
return SteamUserStats.Internal.SetStat( Name, val );
|
||||
}
|
||||
|
||||
public bool Set( float val )
|
||||
{
|
||||
LocalUserOnly();
|
||||
return SteamUserStats.Internal.SetStat( Name, val );
|
||||
}
|
||||
|
||||
public bool Add( int val )
|
||||
{
|
||||
LocalUserOnly();
|
||||
return Set( GetInt() + val );
|
||||
}
|
||||
|
||||
public bool Add( float val )
|
||||
{
|
||||
LocalUserOnly();
|
||||
return Set( GetFloat() + val );
|
||||
}
|
||||
|
||||
public bool UpdateAverageRate( float count, float sessionlength )
|
||||
{
|
||||
LocalUserOnly();
|
||||
return SteamUserStats.Internal.UpdateAvgRateStat( Name, count, sessionlength );
|
||||
}
|
||||
|
||||
public bool Store()
|
||||
{
|
||||
LocalUserOnly();
|
||||
return SteamUserStats.Internal.StoreStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Facepunch.Steamworks/Structs/SteamId.cs
Normal file
29
Facepunch.Steamworks/Structs/SteamId.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks
|
||||
{
|
||||
public struct SteamId
|
||||
{
|
||||
public ulong Value;
|
||||
|
||||
public static implicit operator SteamId( ulong value )
|
||||
{
|
||||
return new SteamId { Value = value };
|
||||
}
|
||||
|
||||
public static implicit operator ulong( SteamId value )
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
|
||||
public uint AccountId => (uint) (Value & 0xFFFFFFFFul);
|
||||
|
||||
public bool IsValid => Value != default;
|
||||
}
|
||||
}
|
||||
26
Facepunch.Steamworks/Structs/SteamIpAddress.cs
Normal file
26
Facepunch.Steamworks/Structs/SteamIpAddress.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
[StructLayout( LayoutKind.Explicit, Pack = Platform.StructPlatformPackSize )]
|
||||
internal partial struct SteamIPAddress
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public uint Ip4Address; // Host Order
|
||||
|
||||
[FieldOffset( 16 )]
|
||||
internal SteamIPType Type; // m_eType ESteamIPType
|
||||
|
||||
public static implicit operator System.Net.IPAddress( SteamIPAddress value )
|
||||
{
|
||||
if ( value.Type == SteamIPType.Type4 )
|
||||
return Utility.Int32ToIp( value.Ip4Address );
|
||||
|
||||
throw new System.Exception( $"Oops - can't convert SteamIPAddress to System.Net.IPAddress because no-one coded support for {value.Type} yet" );
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Facepunch.Steamworks/Structs/SteamParamStringArray.cs
Normal file
45
Facepunch.Steamworks/Structs/SteamParamStringArray.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks.Ugc
|
||||
{
|
||||
internal struct SteamParamStringArray : IDisposable
|
||||
{
|
||||
public SteamParamStringArray_t Value;
|
||||
|
||||
IntPtr[] NativeStrings;
|
||||
IntPtr NativeArray;
|
||||
|
||||
public static SteamParamStringArray From( string[] array )
|
||||
{
|
||||
var a = new SteamParamStringArray();
|
||||
|
||||
a.NativeStrings = new IntPtr[array.Length];
|
||||
for ( int i = 0; i < a.NativeStrings.Length; i++ )
|
||||
{
|
||||
a.NativeStrings[i] = Marshal.StringToHGlobalAnsi( array[i] );
|
||||
}
|
||||
|
||||
var size = Marshal.SizeOf( typeof( IntPtr ) ) * a.NativeStrings.Length;
|
||||
a.NativeArray = Marshal.AllocHGlobal( size );
|
||||
Marshal.Copy( a.NativeStrings, 0, a.NativeArray, a.NativeStrings.Length );
|
||||
|
||||
a.Value = new SteamParamStringArray_t
|
||||
{
|
||||
Strings = a.NativeArray,
|
||||
NumStrings = array.Length
|
||||
};
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach ( var x in NativeStrings )
|
||||
Marshal.FreeHGlobal( x );
|
||||
|
||||
Marshal.FreeHGlobal( NativeArray );
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Facepunch.Steamworks/Structs/Ugc.cs
Normal file
13
Facepunch.Steamworks/Structs/Ugc.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Linq;
|
||||
|
||||
#pragma warning disable 649
|
||||
|
||||
namespace Facepunch.Steamworks.Data
|
||||
{
|
||||
public struct Ugc
|
||||
{
|
||||
internal UGCHandle_t Handle;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore 649
|
||||
237
Facepunch.Steamworks/Structs/UgcEditor.cs
Normal file
237
Facepunch.Steamworks/Structs/UgcEditor.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
using QueryType = Facepunch.Steamworks.Ugc.Query;
|
||||
|
||||
namespace Facepunch.Steamworks.Ugc
|
||||
{
|
||||
public struct Editor
|
||||
{
|
||||
PublishedFileId fileId;
|
||||
|
||||
bool creatingNew;
|
||||
WorkshopFileType creatingType;
|
||||
AppId consumerAppId;
|
||||
|
||||
internal Editor( WorkshopFileType filetype ) : this()
|
||||
{
|
||||
this.creatingNew = true;
|
||||
this.creatingType = filetype;
|
||||
}
|
||||
|
||||
public Editor( PublishedFileId fileId ) : this()
|
||||
{
|
||||
this.fileId = fileId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Normal Workshop item that can be subscribed to
|
||||
/// </summary>
|
||||
public static Editor NewCommunityFile => new Editor( WorkshopFileType.Community );
|
||||
|
||||
/// <summary>
|
||||
/// Workshop item that is meant to be voted on for the purpose of selling in-game
|
||||
/// </summary>
|
||||
public static Editor NewMicrotransactionFile => new Editor( WorkshopFileType.Microtransaction );
|
||||
|
||||
public Editor ForAppId( AppId id ) { this.consumerAppId = id; return this; }
|
||||
|
||||
string Title;
|
||||
public Editor WithTitle( string t ) { this.Title = t; return this; }
|
||||
|
||||
string Description;
|
||||
public Editor WithDescription( string t ) { this.Description = t; return this; }
|
||||
|
||||
string MetaData;
|
||||
public Editor WithMetaData( string t ) { this.MetaData = t; return this; }
|
||||
|
||||
string ChangeLog;
|
||||
public Editor WithChangeLog( string t ) { this.ChangeLog = t; return this; }
|
||||
|
||||
string Language;
|
||||
public Editor InLanguage( string t ) { this.Language = t; return this; }
|
||||
|
||||
string PreviewFile;
|
||||
public Editor WithPreviewFile( string t ) { this.PreviewFile = t; return this; }
|
||||
|
||||
System.IO.DirectoryInfo ContentFolder;
|
||||
public Editor WithContent( System.IO.DirectoryInfo t ) { this.ContentFolder = t; return this; }
|
||||
public Editor WithContent( string folderName ) { return WithContent( new System.IO.DirectoryInfo( folderName ) ); }
|
||||
|
||||
RemoteStoragePublishedFileVisibility? Visibility;
|
||||
|
||||
public Editor WithPublicVisibility() { Visibility = RemoteStoragePublishedFileVisibility.Public; return this; }
|
||||
public Editor WithFriendsOnlyVisibility() { Visibility = RemoteStoragePublishedFileVisibility.FriendsOnly; return this; }
|
||||
public Editor WithPrivateVisibility() { Visibility = RemoteStoragePublishedFileVisibility.Private; return this; }
|
||||
|
||||
List<string> Tags;
|
||||
Dictionary<string, string> KeyValueTags;
|
||||
|
||||
public Editor WithTag( string tag )
|
||||
{
|
||||
if ( Tags == null ) Tags = new List<string>();
|
||||
|
||||
Tags.Add( tag );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Editor AddKeyValueTag(string key, string value)
|
||||
{
|
||||
if (KeyValueTags == null) KeyValueTags = new Dictionary<string, string>();
|
||||
KeyValueTags.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<PublishResult> SubmitAsync( IProgress<float> progress = null )
|
||||
{
|
||||
var result = default( PublishResult );
|
||||
|
||||
progress?.Report( 0 );
|
||||
|
||||
if ( consumerAppId == 0 )
|
||||
consumerAppId = SteamClient.AppId;
|
||||
|
||||
//
|
||||
// Item Create
|
||||
//
|
||||
if ( creatingNew )
|
||||
{
|
||||
result.Result = Steamworks.Result.Fail;
|
||||
|
||||
var created = await SteamUGC.Internal.CreateItem( consumerAppId, creatingType );
|
||||
if ( !created.HasValue ) return result;
|
||||
|
||||
result.Result = created.Value.Result;
|
||||
|
||||
if ( result.Result != Steamworks.Result.OK )
|
||||
return result;
|
||||
|
||||
fileId = created.Value.PublishedFileId;
|
||||
result.NeedsWorkshopAgreement = created.Value.UserNeedsToAcceptWorkshopLegalAgreement;
|
||||
result.FileId = fileId;
|
||||
}
|
||||
|
||||
|
||||
result.FileId = fileId;
|
||||
|
||||
//
|
||||
// Item Update
|
||||
//
|
||||
{
|
||||
var handle = SteamUGC.Internal.StartItemUpdate( consumerAppId, fileId );
|
||||
if ( handle == 0xffffffffffffffff )
|
||||
return result;
|
||||
|
||||
if ( Title != null ) SteamUGC.Internal.SetItemTitle( handle, Title );
|
||||
if ( Description != null ) SteamUGC.Internal.SetItemDescription( handle, Description );
|
||||
if ( MetaData != null ) SteamUGC.Internal.SetItemMetadata( handle, MetaData );
|
||||
if ( Language != null ) SteamUGC.Internal.SetItemUpdateLanguage( handle, Language );
|
||||
if ( ContentFolder != null ) SteamUGC.Internal.SetItemContent( handle, ContentFolder.FullName );
|
||||
if ( PreviewFile != null ) SteamUGC.Internal.SetItemPreview( handle, PreviewFile );
|
||||
if ( Visibility.HasValue ) SteamUGC.Internal.SetItemVisibility( handle, Visibility.Value );
|
||||
if ( Tags != null && Tags.Count > 0 )
|
||||
{
|
||||
using ( var a = SteamParamStringArray.From( Tags.ToArray() ) )
|
||||
{
|
||||
var val = a.Value;
|
||||
SteamUGC.Internal.SetItemTags( handle, ref val );
|
||||
}
|
||||
}
|
||||
|
||||
if (KeyValueTags != null && KeyValueTags.Count > 0)
|
||||
{
|
||||
foreach (var keyValueTag in KeyValueTags)
|
||||
{
|
||||
SteamUGC.Internal.AddItemKeyValueTag(handle, keyValueTag.Key, keyValueTag.Value);
|
||||
}
|
||||
}
|
||||
|
||||
result.Result = Steamworks.Result.Fail;
|
||||
|
||||
if ( ChangeLog == null )
|
||||
ChangeLog = "";
|
||||
|
||||
var updating = SteamUGC.Internal.SubmitItemUpdate( handle, ChangeLog );
|
||||
|
||||
while ( !updating.IsCompleted )
|
||||
{
|
||||
if ( progress != null )
|
||||
{
|
||||
ulong total = 0;
|
||||
ulong processed = 0;
|
||||
|
||||
var r = SteamUGC.Internal.GetItemUpdateProgress( handle, ref processed, ref total );
|
||||
|
||||
switch ( r )
|
||||
{
|
||||
case ItemUpdateStatus.PreparingConfig:
|
||||
{
|
||||
progress?.Report( 0.1f );
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemUpdateStatus.PreparingContent:
|
||||
{
|
||||
progress?.Report( 0.2f );
|
||||
break;
|
||||
}
|
||||
case ItemUpdateStatus.UploadingContent:
|
||||
{
|
||||
var uploaded = total > 0 ? ((float)processed / (float)total) : 0.0f;
|
||||
progress?.Report( 0.2f + uploaded * 0.7f );
|
||||
break;
|
||||
}
|
||||
case ItemUpdateStatus.UploadingPreviewFile:
|
||||
{
|
||||
progress?.Report( 0.8f );
|
||||
break;
|
||||
}
|
||||
case ItemUpdateStatus.CommittingChanges:
|
||||
{
|
||||
progress?.Report( 1 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay( 1000 / 60 );
|
||||
}
|
||||
|
||||
progress?.Report( 1 );
|
||||
|
||||
var updated = updating.GetResult();
|
||||
|
||||
if ( !updated.HasValue ) return result;
|
||||
|
||||
result.Result = updated.Value.Result;
|
||||
|
||||
if ( result.Result != Steamworks.Result.OK )
|
||||
return result;
|
||||
|
||||
result.NeedsWorkshopAgreement = updated.Value.UserNeedsToAcceptWorkshopLegalAgreement;
|
||||
result.FileId = fileId;
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public struct PublishResult
|
||||
{
|
||||
public bool Success => Result == Steamworks.Result.OK;
|
||||
|
||||
public Steamworks.Result Result;
|
||||
public PublishedFileId FileId;
|
||||
|
||||
/// <summary>
|
||||
/// https://partner.steamgames.com/doc/features/workshop/implementation#Legal
|
||||
/// </summary>
|
||||
public bool NeedsWorkshopAgreement;
|
||||
}
|
||||
}
|
||||
375
Facepunch.Steamworks/Structs/UgcItem.cs
Normal file
375
Facepunch.Steamworks/Structs/UgcItem.cs
Normal file
@@ -0,0 +1,375 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
using QueryType = Facepunch.Steamworks.Ugc.Query;
|
||||
|
||||
namespace Facepunch.Steamworks.Ugc
|
||||
{
|
||||
public struct Item
|
||||
{
|
||||
internal SteamUGCDetails_t details;
|
||||
internal PublishedFileId _id;
|
||||
|
||||
public Item( PublishedFileId id ) : this()
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The actual ID of this file
|
||||
/// </summary>
|
||||
public PublishedFileId Id => _id;
|
||||
|
||||
/// <summary>
|
||||
/// The given title of this item
|
||||
/// </summary>
|
||||
public string Title { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of this item, in your local language if available
|
||||
/// </summary>
|
||||
public string Description { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of tags for this item, all lowercase
|
||||
/// </summary>
|
||||
public string[] Tags { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// App Id of the app that created this item
|
||||
/// </summary>
|
||||
public AppId CreatorApp => details.CreatorAppID;
|
||||
|
||||
/// <summary>
|
||||
/// App Id of the app that will consume this item.
|
||||
/// </summary>
|
||||
public AppId ConsumerApp => details.ConsumerAppID;
|
||||
|
||||
/// <summary>
|
||||
/// User who created this content
|
||||
/// </summary>
|
||||
public Friend Owner => new Friend( details.SteamIDOwner );
|
||||
|
||||
/// <summary>
|
||||
/// The bayesian average for up votes / total votes, between [0,1]
|
||||
/// </summary>
|
||||
public float Score => details.Score;
|
||||
|
||||
/// <summary>
|
||||
/// Time when the published item was created
|
||||
/// </summary>
|
||||
public DateTime Created => Epoch.ToDateTime( details.TimeCreated );
|
||||
|
||||
/// <summary>
|
||||
/// Time when the published item was last updated
|
||||
/// </summary>
|
||||
public DateTime Updated => Epoch.ToDateTime( details.TimeUpdated );
|
||||
|
||||
/// <summary>
|
||||
/// True if this is publically visible
|
||||
/// </summary>
|
||||
public bool IsPublic => details.Visibility == RemoteStoragePublishedFileVisibility.Public;
|
||||
|
||||
/// <summary>
|
||||
/// True if this item is only visible by friends of the creator
|
||||
/// </summary>
|
||||
public bool IsFriendsOnly => details.Visibility == RemoteStoragePublishedFileVisibility.FriendsOnly;
|
||||
|
||||
/// <summary>
|
||||
/// True if this is only visible to the creator
|
||||
/// </summary>
|
||||
public bool IsPrivate => details.Visibility == RemoteStoragePublishedFileVisibility.Private;
|
||||
|
||||
/// <summary>
|
||||
/// True if this item has been banned
|
||||
/// </summary>
|
||||
public bool IsBanned => details.Banned;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the developer of this app has specifically flagged this item as accepted in the Workshop
|
||||
/// </summary>
|
||||
public bool IsAcceptedForUse => details.AcceptedForUse;
|
||||
|
||||
/// <summary>
|
||||
/// The number of upvotes of this item
|
||||
/// </summary>
|
||||
public uint VotesUp => details.VotesUp;
|
||||
|
||||
/// <summary>
|
||||
/// The number of downvotes of this item
|
||||
/// </summary>
|
||||
public uint VotesDown => details.VotesDown;
|
||||
|
||||
public bool IsInstalled => (State & ItemState.Installed) == ItemState.Installed;
|
||||
public bool IsDownloading => (State & ItemState.Downloading) == ItemState.Downloading;
|
||||
public bool IsDownloadPending => (State & ItemState.DownloadPending) == ItemState.DownloadPending;
|
||||
public bool IsSubscribed => (State & ItemState.Subscribed) == ItemState.Subscribed;
|
||||
public bool NeedsUpdate => (State & ItemState.NeedsUpdate) == ItemState.NeedsUpdate;
|
||||
|
||||
public string Directory
|
||||
{
|
||||
get
|
||||
{
|
||||
ulong size = 0;
|
||||
uint ts = 0;
|
||||
|
||||
if ( !SteamUGC.Internal.GetItemInstallInfo( Id, ref size, out var strVal, ref ts ) )
|
||||
return null;
|
||||
|
||||
return strVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start downloading this item.
|
||||
/// If this returns false the item isn't getting downloaded.
|
||||
/// </summary>
|
||||
public bool Download( bool highPriority = false )
|
||||
{
|
||||
return SteamUGC.Download( Id, highPriority );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we're downloading, how big the total download is
|
||||
/// </summary>
|
||||
public long DownloadBytesTotal
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( !NeedsUpdate )
|
||||
return SizeBytes;
|
||||
|
||||
ulong downloaded = 0;
|
||||
ulong total = 0;
|
||||
if ( SteamUGC.Internal.GetItemDownloadInfo( Id, ref downloaded, ref total ) )
|
||||
return (long) total;
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we're downloading, how much we've downloaded
|
||||
/// </summary>
|
||||
public long DownloadBytesDownloaded
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( !NeedsUpdate )
|
||||
return SizeBytes;
|
||||
|
||||
ulong downloaded = 0;
|
||||
ulong total = 0;
|
||||
if ( SteamUGC.Internal.GetItemDownloadInfo( Id, ref downloaded, ref total ) )
|
||||
return (long)downloaded;
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we're installed, how big is the install
|
||||
/// </summary>
|
||||
public long SizeBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
if ( NeedsUpdate )
|
||||
return DownloadBytesDownloaded;
|
||||
|
||||
ulong size = 0;
|
||||
uint ts = 0;
|
||||
if ( !SteamUGC.Internal.GetItemInstallInfo( Id, ref size, out _, ref ts ) )
|
||||
return 0;
|
||||
|
||||
return (long) size;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we're downloading our current progress as a delta betwen 0-1
|
||||
/// </summary>
|
||||
public float DownloadAmount
|
||||
{
|
||||
get
|
||||
{
|
||||
//changed from NeedsUpdate as it's false when validating and redownloading ugc
|
||||
//possibly similar properties should also be changed
|
||||
if ( !IsDownloading ) return 1;
|
||||
|
||||
ulong downloaded = 0;
|
||||
ulong total = 0;
|
||||
if ( SteamUGC.Internal.GetItemDownloadInfo( Id, ref downloaded, ref total ) && total > 0 )
|
||||
return (float)((double)downloaded / (double)total);
|
||||
|
||||
if ( NeedsUpdate || !IsInstalled || IsDownloading )
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private ItemState State => (ItemState) SteamUGC.Internal.GetItemState( Id );
|
||||
|
||||
public static async Task<Item?> GetAsync( PublishedFileId id, int maxageseconds = 60 * 30 )
|
||||
{
|
||||
var file = await Steamworks.Ugc.Query.All
|
||||
.WithFileId( id )
|
||||
.WithLongDescription( true )
|
||||
.GetPageAsync( 1 );
|
||||
|
||||
if ( !file.HasValue ) return null;
|
||||
if ( file.Value.ResultCount == 0 ) return null;
|
||||
|
||||
return file.Value.Entries.First();
|
||||
}
|
||||
|
||||
internal static Item From( SteamUGCDetails_t details )
|
||||
{
|
||||
var d = new Item
|
||||
{
|
||||
_id = details.PublishedFileId,
|
||||
details = details,
|
||||
Title = details.TitleUTF8(),
|
||||
Description = details.DescriptionUTF8(),
|
||||
Tags = details.TagsUTF8().ToLower().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries )
|
||||
};
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A case insensitive check for tag
|
||||
/// </summary>
|
||||
public bool HasTag( string find )
|
||||
{
|
||||
if ( Tags.Length == 0 ) return false;
|
||||
|
||||
return Tags.Contains( find, StringComparer.OrdinalIgnoreCase );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the user to subscribe to this item
|
||||
/// </summary>
|
||||
public async Task<bool> Subscribe ()
|
||||
{
|
||||
var result = await SteamUGC.Internal.SubscribeItem( _id );
|
||||
return result?.Result == Result.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the user to subscribe to download this item asyncronously
|
||||
/// If CancellationToken is default then there is 60 seconds timeout
|
||||
/// Progress will be set to 0-1
|
||||
/// </summary>
|
||||
public async Task<bool> DownloadAsync( Action<float> progress = null, int milisecondsUpdateDelay = 60, CancellationToken ct = default )
|
||||
{
|
||||
return await SteamUGC.DownloadAsync( Id, progress, milisecondsUpdateDelay, ct );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the user to unsubscribe from this item
|
||||
/// </summary>
|
||||
public async Task<bool> Unsubscribe ()
|
||||
{
|
||||
var result = await SteamUGC.Internal.UnsubscribeItem( _id );
|
||||
return result?.Result == Result.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds item to user favorite list
|
||||
/// </summary>
|
||||
public async Task<bool> AddFavorite()
|
||||
{
|
||||
var result = await SteamUGC.Internal.AddItemToFavorites(details.ConsumerAppID, _id);
|
||||
return result?.Result == Result.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes item from user favorite list
|
||||
/// </summary>
|
||||
public async Task<bool> RemoveFavorite()
|
||||
{
|
||||
var result = await SteamUGC.Internal.RemoveItemFromFavorites(details.ConsumerAppID, _id);
|
||||
return result?.Result == Result.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the user to rate a workshop item up or down.
|
||||
/// </summary>
|
||||
public async Task<Result?> Vote( bool up )
|
||||
{
|
||||
var r = await SteamUGC.Internal.SetUserItemVote( Id, up );
|
||||
return r?.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current users vote on the item
|
||||
/// </summary>
|
||||
public async Task<UserItemVote?> GetUserVote()
|
||||
{
|
||||
var result = await SteamUGC.Internal.GetUserItemVote(_id);
|
||||
if (!result.HasValue)
|
||||
return null;
|
||||
return UserItemVote.From(result.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a URL to view this item online
|
||||
/// </summary>
|
||||
public string Url => $"http://steamcommunity.com/sharedfiles/filedetails/?source=Facepunch.Steamworks&id={Id}";
|
||||
|
||||
/// <summary>
|
||||
/// The URl to view this item's changelog
|
||||
/// </summary>
|
||||
public string ChangelogUrl => $"http://steamcommunity.com/sharedfiles/filedetails/changelog/{Id}";
|
||||
|
||||
/// <summary>
|
||||
/// The URL to view the comments on this item
|
||||
/// </summary>
|
||||
public string CommentsUrl => $"http://steamcommunity.com/sharedfiles/filedetails/comments/{Id}";
|
||||
|
||||
/// <summary>
|
||||
/// The URL to discuss this item
|
||||
/// </summary>
|
||||
public string DiscussUrl => $"http://steamcommunity.com/sharedfiles/filedetails/discussions/{Id}";
|
||||
|
||||
/// <summary>
|
||||
/// The URL to view this items stats online
|
||||
/// </summary>
|
||||
public string StatsUrl => $"http://steamcommunity.com/sharedfiles/filedetails/stats/{Id}";
|
||||
|
||||
public ulong NumSubscriptions { get; internal set; }
|
||||
public ulong NumFavorites { get; internal set; }
|
||||
public ulong NumFollowers { get; internal set; }
|
||||
public ulong NumUniqueSubscriptions { get; internal set; }
|
||||
public ulong NumUniqueFavorites { get; internal set; }
|
||||
public ulong NumUniqueFollowers { get; internal set; }
|
||||
public ulong NumUniqueWebsiteViews { get; internal set; }
|
||||
public ulong ReportScore { get; internal set; }
|
||||
public ulong NumSecondsPlayed { get; internal set; }
|
||||
public ulong NumPlaytimeSessions { get; internal set; }
|
||||
public ulong NumComments { get; internal set; }
|
||||
public ulong NumSecondsPlayedDuringTimePeriod { get; internal set; }
|
||||
public ulong NumPlaytimeSessionsDuringTimePeriod { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL to the preview image for this item
|
||||
/// </summary>
|
||||
public string PreviewImageUrl { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Edit this item
|
||||
/// </summary>
|
||||
public Ugc.Editor Edit()
|
||||
{
|
||||
return new Ugc.Editor( Id );
|
||||
}
|
||||
|
||||
public Result Result => details.Result;
|
||||
}
|
||||
}
|
||||
303
Facepunch.Steamworks/Structs/UgcQuery.cs
Normal file
303
Facepunch.Steamworks/Structs/UgcQuery.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
using QueryType = Facepunch.Steamworks.Ugc.Query;
|
||||
|
||||
namespace Facepunch.Steamworks.Ugc
|
||||
{
|
||||
public struct Query
|
||||
{
|
||||
UgcType matchingType;
|
||||
UGCQuery queryType;
|
||||
AppId consumerApp;
|
||||
AppId creatorApp;
|
||||
string searchText;
|
||||
|
||||
public Query( UgcType type ) : this()
|
||||
{
|
||||
matchingType = type;
|
||||
}
|
||||
|
||||
public static Query All => new Query( UgcType.All );
|
||||
public static Query Items => new Query( UgcType.Items );
|
||||
public static Query ItemsMtx => new Query( UgcType.Items_Mtx );
|
||||
public static Query ItemsReadyToUse => new Query( UgcType.Items_ReadyToUse );
|
||||
public static Query Collections => new Query( UgcType.Collections );
|
||||
public static Query Artwork => new Query( UgcType.Artwork );
|
||||
public static Query Videos => new Query( UgcType.Videos );
|
||||
public static Query Screenshots => new Query( UgcType.Screenshots );
|
||||
public static Query AllGuides => new Query( UgcType.AllGuides );
|
||||
public static Query WebGuides => new Query( UgcType.WebGuides );
|
||||
public static Query IntegratedGuides => new Query( UgcType.IntegratedGuides );
|
||||
public static Query UsableInGame => new Query( UgcType.UsableInGame );
|
||||
public static Query ControllerBindings => new Query( UgcType.ControllerBindings );
|
||||
public static Query GameManagedItems => new Query( UgcType.GameManagedItems );
|
||||
|
||||
|
||||
public Query RankedByVote() { queryType = UGCQuery.RankedByVote; return this; }
|
||||
public Query RankedByPublicationDate() { queryType = UGCQuery.RankedByPublicationDate; return this; }
|
||||
public Query RankedByAcceptanceDate() { queryType = UGCQuery.AcceptedForGameRankedByAcceptanceDate; return this; }
|
||||
public Query RankedByTrend() { queryType = UGCQuery.RankedByTrend; return this; }
|
||||
public Query FavoritedByFriends() { queryType = UGCQuery.FavoritedByFriendsRankedByPublicationDate; return this; }
|
||||
public Query CreatedByFriends() { queryType = UGCQuery.CreatedByFriendsRankedByPublicationDate; return this; }
|
||||
public Query RankedByNumTimesReported() { queryType = UGCQuery.RankedByNumTimesReported; return this; }
|
||||
public Query CreatedByFollowedUsers() { queryType = UGCQuery.CreatedByFollowedUsersRankedByPublicationDate; return this; }
|
||||
public Query NotYetRated() { queryType = UGCQuery.NotYetRated; return this; }
|
||||
public Query RankedByTotalVotesAsc() { queryType = UGCQuery.RankedByTotalVotesAsc; return this; }
|
||||
public Query RankedByVotesUp() { queryType = UGCQuery.RankedByVotesUp; return this; }
|
||||
public Query RankedByTextSearch() { queryType = UGCQuery.RankedByTextSearch; return this; }
|
||||
public Query RankedByTotalUniqueSubscriptions() { queryType = UGCQuery.RankedByTotalUniqueSubscriptions; return this; }
|
||||
public Query RankedByPlaytimeTrend() { queryType = UGCQuery.RankedByPlaytimeTrend; return this; }
|
||||
public Query RankedByTotalPlaytime() { queryType = UGCQuery.RankedByTotalPlaytime; return this; }
|
||||
public Query RankedByAveragePlaytimeTrend() { queryType = UGCQuery.RankedByAveragePlaytimeTrend; return this; }
|
||||
public Query RankedByLifetimeAveragePlaytime() { queryType = UGCQuery.RankedByLifetimeAveragePlaytime; return this; }
|
||||
public Query RankedByPlaytimeSessionsTrend() { queryType = UGCQuery.RankedByPlaytimeSessionsTrend; return this; }
|
||||
public Query RankedByLifetimePlaytimeSessions() { queryType = UGCQuery.RankedByLifetimePlaytimeSessions; return this; }
|
||||
|
||||
#region UserQuery
|
||||
|
||||
SteamId? steamid;
|
||||
|
||||
UserUGCList userType;
|
||||
UserUGCListSortOrder userSort;
|
||||
|
||||
internal Query LimitUser( SteamId steamid )
|
||||
{
|
||||
if ( steamid.Value == 0 )
|
||||
steamid = SteamClient.SteamId;
|
||||
|
||||
this.steamid = steamid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Query WhereUserPublished( SteamId user = default ) { userType = UserUGCList.Published; LimitUser( user ); return this; }
|
||||
public Query WhereUserVotedOn( SteamId user = default ) { userType = UserUGCList.VotedOn; LimitUser( user ); return this; }
|
||||
public Query WhereUserVotedUp( SteamId user = default ) { userType = UserUGCList.VotedUp; LimitUser( user ); return this; }
|
||||
public Query WhereUserVotedDown( SteamId user = default ) { userType = UserUGCList.VotedDown; LimitUser( user ); return this; }
|
||||
public Query WhereUserWillVoteLater( SteamId user = default ) { userType = UserUGCList.WillVoteLater; LimitUser( user ); return this; }
|
||||
public Query WhereUserFavorited( SteamId user = default ) { userType = UserUGCList.Favorited; LimitUser( user ); return this; }
|
||||
public Query WhereUserSubscribed( SteamId user = default ) { userType = UserUGCList.Subscribed; LimitUser( user ); return this; }
|
||||
public Query WhereUserUsedOrPlayed( SteamId user = default ) { userType = UserUGCList.UsedOrPlayed; LimitUser( user ); return this; }
|
||||
public Query WhereUserFollowed( SteamId user = default ) { userType = UserUGCList.Followed; LimitUser( user ); return this; }
|
||||
|
||||
public Query SortByCreationDate() { userSort = UserUGCListSortOrder.CreationOrderDesc; return this; }
|
||||
public Query SortByCreationDateAsc() { userSort = UserUGCListSortOrder.CreationOrderAsc; return this; }
|
||||
public Query SortByTitleAsc() { userSort = UserUGCListSortOrder.TitleAsc; return this; }
|
||||
public Query SortByUpdateDate() { userSort = UserUGCListSortOrder.LastUpdatedDesc; return this; }
|
||||
public Query SortBySubscriptionDate() { userSort = UserUGCListSortOrder.SubscriptionDateDesc; return this; }
|
||||
public Query SortByVoteScore() { userSort = UserUGCListSortOrder.VoteScoreDesc; return this; }
|
||||
public Query SortByModeration() { userSort = UserUGCListSortOrder.ForModeration; return this; }
|
||||
|
||||
public Query WhereSearchText(string searchText) { this.searchText = searchText; return this; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Files
|
||||
PublishedFileId[] Files;
|
||||
|
||||
public Query WithFileId( params PublishedFileId[] files )
|
||||
{
|
||||
Files = files;
|
||||
return this;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<ResultPage?> GetPageAsync( int page )
|
||||
{
|
||||
if ( page <= 0 ) throw new System.Exception( "page should be > 0" );
|
||||
|
||||
if ( consumerApp == 0 ) consumerApp = SteamClient.AppId;
|
||||
if ( creatorApp == 0 ) creatorApp = consumerApp;
|
||||
|
||||
UGCQueryHandle_t handle;
|
||||
|
||||
if ( Files != null )
|
||||
{
|
||||
handle = SteamUGC.Internal.CreateQueryUGCDetailsRequest( Files, (uint)Files.Length );
|
||||
}
|
||||
else if ( steamid.HasValue )
|
||||
{
|
||||
handle = SteamUGC.Internal.CreateQueryUserUGCRequest( steamid.Value.AccountId, userType, matchingType, userSort, creatorApp.Value, consumerApp.Value, (uint)page );
|
||||
}
|
||||
else
|
||||
{
|
||||
handle = SteamUGC.Internal.CreateQueryAllUGCRequest( queryType, matchingType, creatorApp.Value, consumerApp.Value, (uint)page );
|
||||
}
|
||||
|
||||
ApplyReturns(handle);
|
||||
|
||||
if (maxCacheAge.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetAllowCachedResponse(handle, (uint)maxCacheAge.Value);
|
||||
}
|
||||
|
||||
ApplyConstraints( handle );
|
||||
|
||||
var result = await SteamUGC.Internal.SendQueryUGCRequest( handle );
|
||||
if ( !result.HasValue )
|
||||
return null;
|
||||
|
||||
if ( result.Value.Result != Steamworks.Result.OK )
|
||||
return null;
|
||||
|
||||
return new ResultPage
|
||||
{
|
||||
Handle = result.Value.Handle,
|
||||
ResultCount = (int) result.Value.NumResultsReturned,
|
||||
TotalCount = (int)result.Value.TotalMatchingResults,
|
||||
CachedData = result.Value.CachedData
|
||||
};
|
||||
}
|
||||
|
||||
#region SharedConstraints
|
||||
public QueryType WithType( UgcType type ) { matchingType = type; return this; }
|
||||
int? maxCacheAge;
|
||||
public QueryType AllowCachedResponse( int maxSecondsAge ) { maxCacheAge = maxSecondsAge; return this; }
|
||||
string language;
|
||||
public QueryType InLanguage( string lang ) { language = lang; return this; }
|
||||
|
||||
int? trendDays;
|
||||
public QueryType WithTrendDays( int days ) { trendDays = days; return this; }
|
||||
|
||||
List<string> requiredTags;
|
||||
bool? matchAnyTag;
|
||||
List<string> excludedTags;
|
||||
Dictionary<string, string> requiredKv;
|
||||
|
||||
/// <summary>
|
||||
/// Found items must have at least one of the defined tags
|
||||
/// </summary>
|
||||
public QueryType MatchAnyTag() { matchAnyTag = true; return this; }
|
||||
|
||||
/// <summary>
|
||||
/// Found items must have all defined tags
|
||||
/// </summary>
|
||||
public QueryType MatchAllTags() { matchAnyTag = false; return this; }
|
||||
|
||||
public QueryType WithTag( string tag )
|
||||
{
|
||||
if ( requiredTags == null ) requiredTags = new List<string>();
|
||||
requiredTags.Add( tag );
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryType AddRequiredKeyValueTag(string key, string value)
|
||||
{
|
||||
if (requiredKv == null) requiredKv = new Dictionary<string, string>();
|
||||
requiredKv.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryType WithoutTag( string tag )
|
||||
{
|
||||
if ( excludedTags == null ) excludedTags = new List<string>();
|
||||
excludedTags.Add( tag );
|
||||
return this;
|
||||
}
|
||||
|
||||
void ApplyConstraints( UGCQueryHandle_t handle )
|
||||
{
|
||||
if ( requiredTags != null )
|
||||
{
|
||||
foreach ( var tag in requiredTags )
|
||||
SteamUGC.Internal.AddRequiredTag( handle, tag );
|
||||
}
|
||||
|
||||
if ( excludedTags != null )
|
||||
{
|
||||
foreach ( var tag in excludedTags )
|
||||
SteamUGC.Internal.AddExcludedTag( handle, tag );
|
||||
}
|
||||
|
||||
if ( requiredKv != null )
|
||||
{
|
||||
foreach ( var tag in requiredKv )
|
||||
SteamUGC.Internal.AddRequiredKeyValueTag( handle, tag.Key, tag.Value );
|
||||
}
|
||||
|
||||
if ( matchAnyTag.HasValue )
|
||||
{
|
||||
SteamUGC.Internal.SetMatchAnyTag( handle, matchAnyTag.Value );
|
||||
}
|
||||
|
||||
if ( trendDays.HasValue )
|
||||
{
|
||||
SteamUGC.Internal.SetRankedByTrendDays( handle, (uint)trendDays.Value );
|
||||
}
|
||||
|
||||
if ( !string.IsNullOrEmpty( searchText ) )
|
||||
{
|
||||
SteamUGC.Internal.SetSearchText( handle, searchText );
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ReturnValues
|
||||
|
||||
bool? WantsReturnOnlyIDs;
|
||||
public QueryType WithOnlyIDs(bool b) { WantsReturnOnlyIDs = b; return this; }
|
||||
bool? WantsReturnKeyValueTags;
|
||||
public QueryType WithKeyValueTag(bool b) { WantsReturnKeyValueTags = b; return this; }
|
||||
bool? WantsReturnLongDescription;
|
||||
public QueryType WithLongDescription(bool b) { WantsReturnLongDescription = b; return this; }
|
||||
bool? WantsReturnMetadata;
|
||||
public QueryType WithMetadata(bool b) { WantsReturnMetadata = b; return this; }
|
||||
bool? WantsReturnChildren;
|
||||
public QueryType WithChildren(bool b) { WantsReturnChildren = b; return this; }
|
||||
bool? WantsReturnAdditionalPreviews;
|
||||
public QueryType WithAdditionalPreviews(bool b) { WantsReturnAdditionalPreviews = b; return this; }
|
||||
bool? WantsReturnTotalOnly;
|
||||
public QueryType WithTotalOnly(bool b) { WantsReturnTotalOnly = b; return this; }
|
||||
uint? WantsReturnPlaytimeStats;
|
||||
public QueryType WithPlaytimeStats(uint unDays) { WantsReturnPlaytimeStats = unDays; return this; }
|
||||
|
||||
private void ApplyReturns(UGCQueryHandle_t handle)
|
||||
{
|
||||
if (WantsReturnOnlyIDs.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnOnlyIDs(handle, WantsReturnOnlyIDs.Value);
|
||||
}
|
||||
|
||||
if (WantsReturnKeyValueTags.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnKeyValueTags(handle, WantsReturnKeyValueTags.Value);
|
||||
}
|
||||
|
||||
if (WantsReturnLongDescription.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnLongDescription(handle, WantsReturnLongDescription.Value);
|
||||
}
|
||||
|
||||
if (WantsReturnMetadata.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnMetadata(handle, WantsReturnMetadata.Value);
|
||||
}
|
||||
|
||||
if (WantsReturnChildren.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnChildren(handle, WantsReturnChildren.Value);
|
||||
}
|
||||
|
||||
if (WantsReturnAdditionalPreviews.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnAdditionalPreviews(handle, WantsReturnAdditionalPreviews.Value);
|
||||
}
|
||||
|
||||
if (WantsReturnTotalOnly.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnTotalOnly(handle, WantsReturnTotalOnly.Value);
|
||||
}
|
||||
|
||||
if (WantsReturnPlaytimeStats.HasValue)
|
||||
{
|
||||
SteamUGC.Internal.SetReturnPlaytimeStats(handle, WantsReturnPlaytimeStats.Value);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
77
Facepunch.Steamworks/Structs/UgcResultPage.cs
Normal file
77
Facepunch.Steamworks/Structs/UgcResultPage.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks.Ugc
|
||||
{
|
||||
public struct ResultPage : System.IDisposable
|
||||
{
|
||||
internal UGCQueryHandle_t Handle;
|
||||
|
||||
public int ResultCount;
|
||||
public int TotalCount;
|
||||
|
||||
public bool CachedData;
|
||||
|
||||
public IEnumerable<Item> Entries
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
var details = default( SteamUGCDetails_t );
|
||||
for ( uint i=0; i< ResultCount; i++ )
|
||||
{
|
||||
if ( SteamUGC.Internal.GetQueryUGCResult( Handle, i, ref details ) )
|
||||
{
|
||||
var item = Item.From( details );
|
||||
|
||||
item.NumSubscriptions = GetStat( i, ItemStatistic.NumSubscriptions );
|
||||
item.NumFavorites = GetStat( i, ItemStatistic.NumFavorites );
|
||||
item.NumFollowers = GetStat( i, ItemStatistic.NumFollowers );
|
||||
item.NumUniqueSubscriptions = GetStat( i, ItemStatistic.NumUniqueSubscriptions );
|
||||
item.NumUniqueFavorites = GetStat( i, ItemStatistic.NumUniqueFavorites );
|
||||
item.NumUniqueFollowers = GetStat( i, ItemStatistic.NumUniqueFollowers );
|
||||
item.NumUniqueWebsiteViews = GetStat( i, ItemStatistic.NumUniqueWebsiteViews );
|
||||
item.ReportScore = GetStat( i, ItemStatistic.ReportScore );
|
||||
item.NumSecondsPlayed = GetStat( i, ItemStatistic.NumSecondsPlayed );
|
||||
item.NumPlaytimeSessions = GetStat( i, ItemStatistic.NumPlaytimeSessions );
|
||||
item.NumComments = GetStat( i, ItemStatistic.NumComments );
|
||||
item.NumSecondsPlayedDuringTimePeriod = GetStat( i, ItemStatistic.NumSecondsPlayedDuringTimePeriod );
|
||||
item.NumPlaytimeSessionsDuringTimePeriod = GetStat( i, ItemStatistic.NumPlaytimeSessionsDuringTimePeriod );
|
||||
|
||||
if ( SteamUGC.Internal.GetQueryUGCPreviewURL( Handle, i, out string preview ) )
|
||||
{
|
||||
item.PreviewImageUrl = preview;
|
||||
}
|
||||
|
||||
// TODO GetQueryUGCAdditionalPreview
|
||||
// TODO GetQueryUGCChildren
|
||||
// TODO GetQueryUGCKeyValueTag
|
||||
// TODO GetQueryUGCMetadata
|
||||
|
||||
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetStat( uint index, ItemStatistic stat )
|
||||
{
|
||||
ulong val = 0;
|
||||
|
||||
if ( !SteamUGC.Internal.GetQueryUGCStatistic( Handle, index, stat, ref val ) )
|
||||
return 0;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if ( Handle > 0 )
|
||||
{
|
||||
SteamUGC.Internal.ReleaseQueryUGCRequest( Handle );
|
||||
Handle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Facepunch.Steamworks/Structs/UserItemVote.cs
Normal file
21
Facepunch.Steamworks/Structs/UserItemVote.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Facepunch.Steamworks.Data;
|
||||
|
||||
namespace Facepunch.Steamworks.Ugc
|
||||
{
|
||||
public struct UserItemVote
|
||||
{
|
||||
public bool VotedUp;
|
||||
public bool VotedDown;
|
||||
public bool VoteSkipped;
|
||||
|
||||
internal static UserItemVote? From(GetUserItemVoteResult_t result)
|
||||
{
|
||||
return new UserItemVote
|
||||
{
|
||||
VotedUp = result.VotedUp,
|
||||
VotedDown = result.VotedDown,
|
||||
VoteSkipped = result.VoteSkipped
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user