Merge remote-tracking branch 'refs/remotes/Facepunch/master'

This commit is contained in:
Kyle Kukshtel 2017-08-07 09:24:49 -07:00
commit 9516d1ed7a
7 changed files with 166 additions and 34 deletions

View File

@ -19,7 +19,7 @@ namespace Facepunch.Steamworks.Test
bool GotStats = false; bool GotStats = false;
server.Stats.Refresh( MySteamId, success => server.Stats.Refresh( MySteamId, (steamid, success) =>
{ {
GotStats = true; GotStats = true;
Assert.IsTrue( success ); Assert.IsTrue( success );

View File

@ -13,21 +13,57 @@ namespace Facepunch.Steamworks
public Achievement[] All { get; private set; } public Achievement[] All { get; private set; }
public event Action OnUpdated; public event Action OnUpdated;
public event Action<Achievement> OnAchievementStateChanged;
private List<Achievement> unlockedRecently = new List<Achievement>();
internal Achievements( Client c ) internal Achievements( Client c )
{ {
client = c; client = c;
All = new Achievement[0]; All = new Achievement[0];
SteamNative.UserStatsReceived_t.RegisterCallback( c, UserStatsReceived ); SteamNative.UserStatsReceived_t.RegisterCallback( c, UserStatsReceived );
SteamNative.UserStatsStored_t.RegisterCallback( c, UserStatsStored );
Refresh();
} }
public void Refresh() public void Refresh()
{ {
var old = All;
All = Enumerable.Range( 0, (int)client.native.userstats.GetNumAchievements() ) All = Enumerable.Range( 0, (int)client.native.userstats.GetNumAchievements() )
.Select( x => new Achievement( client, x ) ) .Select( x =>
{
if ( old != null )
{
var name = client.native.userstats.GetAchievementName( (uint)x );
var found = old.FirstOrDefault( y => y.Id == name );
if ( found != null )
{
if ( found.Refresh() )
{
unlockedRecently.Add( found );
}
return found;
}
}
return new Achievement( client, x );
} )
.ToArray(); .ToArray();
foreach ( var i in unlockedRecently )
{
OnUnlocked( i );
}
unlockedRecently.Clear();
}
internal void OnUnlocked( Achievement a )
{
OnAchievementStateChanged?.Invoke( a );
} }
public void Dispose() public void Dispose()
@ -50,14 +86,10 @@ namespace Facepunch.Steamworks
/// </summary> /// </summary>
public bool Trigger( string identifier, bool apply = true ) public bool Trigger( string identifier, bool apply = true )
{ {
var r = client.native.userstats.SetAchievement( identifier ); var a = Find( identifier );
if ( a == null ) return false;
if ( apply ) return a.Trigger( apply );
{
client.Stats.StoreStats();
}
return r;
} }
/// <summary> /// <summary>
@ -71,6 +103,17 @@ namespace Facepunch.Steamworks
private void UserStatsReceived( UserStatsReceived_t stats, bool isError ) private void UserStatsReceived( UserStatsReceived_t stats, bool isError )
{ {
if ( isError ) return; if ( isError ) return;
if ( stats.GameID != client.AppId ) return;
Refresh();
OnUpdated?.Invoke();
}
private void UserStatsStored( UserStatsStored_t stats, bool isError )
{
if ( isError ) return;
if ( stats.GameID != client.AppId ) return;
Refresh(); Refresh();
@ -97,6 +140,7 @@ namespace Facepunch.Steamworks
public DateTime UnlockTime { get; private set; } public DateTime UnlockTime { get; private set; }
private int iconId { get; set; } = -1; private int iconId { get; set; } = -1;
private int refreshCount = 0;
/// <summary> /// <summary>
/// If this achievement is linked to a stat this will return the progress. /// If this achievement is linked to a stat this will return the progress.
@ -159,6 +203,9 @@ namespace Facepunch.Steamworks
/// </summary> /// </summary>
public bool Trigger( bool apply = true ) public bool Trigger( bool apply = true )
{ {
if ( State )
return false;
State = true; State = true;
UnlockTime = DateTime.Now; UnlockTime = DateTime.Now;
@ -169,6 +216,8 @@ namespace Facepunch.Steamworks
client.Stats.StoreStats(); client.Stats.StoreStats();
} }
client.Achievements.OnUnlocked( this );
return r; return r;
} }
@ -185,10 +234,12 @@ namespace Facepunch.Steamworks
/// <summary> /// <summary>
/// Refresh the unlock state. You shouldn't need to call this manually /// Refresh the unlock state. You shouldn't need to call this manually
/// but it's here if you have to for some reason. /// but it's here if you have to for some reason. Retuns true if state changed (meaning, probably unlocked)
/// </summary> /// </summary>
public void Refresh() public bool Refresh()
{ {
bool previousState = State;
bool state = false; bool state = false;
uint unlockTime; uint unlockTime;
@ -199,6 +250,15 @@ namespace Facepunch.Steamworks
State = state; State = state;
UnlockTime = Utility.Epoch.ToDateTime( unlockTime ); UnlockTime = Utility.Epoch.ToDateTime( unlockTime );
} }
refreshCount++;
if ( previousState != State && refreshCount > 1 )
{
return true;
}
return false;
} }
} }

View File

@ -35,6 +35,8 @@ namespace Facepunch.Steamworks
internal ulong BoardId; internal ulong BoardId;
internal Client client; internal Client client;
private readonly Queue<Action> _onCreated = new Queue<Action>();
/// <summary> /// <summary>
/// The results from the last query. Can be null. /// The results from the last query. Can be null.
/// </summary> /// </summary>
@ -77,14 +79,35 @@ namespace Facepunch.Steamworks
client = null; client = null;
} }
private void DispatchOnCreatedCallbacks()
{
while ( _onCreated.Count > 0 )
{
_onCreated.Dequeue()();
}
}
private bool DeferOnCreated( Action onValid, FailureCallback onFailure = null )
{
if ( IsValid || IsError ) return false;
_onCreated.Enqueue( () =>
{
if ( IsValid ) onValid();
else onFailure?.Invoke( Callbacks.Result.Fail );
} );
return true;
}
internal void OnBoardCreated( LeaderboardFindResult_t result, bool error ) internal void OnBoardCreated( LeaderboardFindResult_t result, bool error )
{ {
if ( error || ( result.LeaderboardFound == 0 ) ) if ( error || ( result.LeaderboardFound == 0 ) )
{ {
IsError = true; IsError = true;
return;
} }
else
{
BoardId = result.SteamLeaderboard; BoardId = result.SteamLeaderboard;
if ( IsValid ) if ( IsValid )
@ -94,6 +117,9 @@ namespace Facepunch.Steamworks
} }
} }
DispatchOnCreatedCallbacks();
}
/// <summary> /// <summary>
/// Add a score to this leaderboard. /// Add a score to this leaderboard.
/// Subscores are totally optional, and can be used for other game defined data such as laps etc.. although /// Subscores are totally optional, and can be used for other game defined data such as laps etc.. although
@ -103,7 +129,8 @@ namespace Facepunch.Steamworks
/// </summary> /// </summary>
public bool AddScore( bool onlyIfBeatsOldScore, int score, params int[] subscores ) public bool AddScore( bool onlyIfBeatsOldScore, int score, params int[] subscores )
{ {
if ( !IsValid ) return false; if ( IsError ) return false;
if ( !IsValid ) return DeferOnCreated( () => AddScore( onlyIfBeatsOldScore, score, subscores ) );
var flags = LeaderboardUploadScoreMethod.ForceUpdate; var flags = LeaderboardUploadScoreMethod.ForceUpdate;
if ( onlyIfBeatsOldScore ) flags = LeaderboardUploadScoreMethod.KeepBest; if ( onlyIfBeatsOldScore ) flags = LeaderboardUploadScoreMethod.KeepBest;
@ -142,7 +169,8 @@ namespace Facepunch.Steamworks
/// </summary> /// </summary>
public bool AddScore( bool onlyIfBeatsOldScore, int score, int[] subscores = null, AddScoreCallback onSuccess = null, FailureCallback onFailure = null ) public bool AddScore( bool onlyIfBeatsOldScore, int score, int[] subscores = null, AddScoreCallback onSuccess = null, FailureCallback onFailure = null )
{ {
if ( !IsValid ) return false; if ( IsError ) return false;
if ( !IsValid ) return DeferOnCreated( () => AddScore( onlyIfBeatsOldScore, score, subscores, onSuccess, onFailure ), onFailure );
if ( subscores == null ) subscores = new int[0]; if ( subscores == null ) subscores = new int[0];
@ -182,7 +210,8 @@ namespace Facepunch.Steamworks
/// <returns>True if the file attachment process has started</returns> /// <returns>True if the file attachment process has started</returns>
public bool AttachRemoteFile( RemoteFile file, AttachRemoteFileCallback onSuccess = null, FailureCallback onFailure = null ) public bool AttachRemoteFile( RemoteFile file, AttachRemoteFileCallback onSuccess = null, FailureCallback onFailure = null )
{ {
if ( !IsValid ) return false; if ( IsError ) return false;
if ( !IsValid ) return DeferOnCreated( () => AttachRemoteFile( file, onSuccess, onFailure ), onFailure );
if ( file.IsShared ) if ( file.IsShared )
{ {
@ -260,7 +289,8 @@ namespace Facepunch.Steamworks
/// <returns>Returns true if we have started the query</returns> /// <returns>Returns true if we have started the query</returns>
public bool FetchScores( RequestType RequestType, int start, int end, FetchScoresCallback onSuccess, FailureCallback onFailure = null ) public bool FetchScores( RequestType RequestType, int start, int end, FetchScoresCallback onSuccess, FailureCallback onFailure = null )
{ {
if ( !IsValid ) return false; if ( IsError ) return false;
if ( !IsValid ) return DeferOnCreated( () => FetchScores( RequestType, start, end, onSuccess, onFailure ), onFailure );
client.native.userstats.DownloadLeaderboardEntries( BoardId, (LeaderboardDataRequest) RequestType, start, end, ( result, error ) => client.native.userstats.DownloadLeaderboardEntries( BoardId, (LeaderboardDataRequest) RequestType, start, end, ( result, error ) =>
{ {

View File

@ -35,5 +35,36 @@ namespace Facepunch.Steamworks
client.native.screenshots.TriggerScreenshot(); client.native.screenshots.TriggerScreenshot();
} }
public unsafe void Write( byte[] rgbData, int width, int height )
{
if ( rgbData == null )
{
throw new ArgumentNullException( nameof(rgbData) );
}
if ( width < 1 )
{
throw new ArgumentOutOfRangeException( nameof(width), width,
$"Expected {nameof(width)} to be at least 1." );
}
if ( height < 1 )
{
throw new ArgumentOutOfRangeException( nameof(height), height,
$"Expected {nameof(height)} to be at least 1." );
}
var size = width * height * 3;
if ( rgbData.Length < size )
{
throw new ArgumentException( nameof(rgbData),
$"Expected {nameof(rgbData)} to contain at least {size} elements (actual size: {rgbData.Length})." );
}
fixed ( byte* ptr = rgbData )
{
client.native.screenshots.WriteScreenshot( (IntPtr) ptr, (uint) rgbData.Length, width, height );
}
}
} }
} }

View File

@ -63,6 +63,19 @@ namespace Facepunch.Steamworks
workshop.OnItemInstalled += OnItemInstalled; workshop.OnItemInstalled += OnItemInstalled;
} }
public void Subscribe()
{
workshop.ugc.SubscribeItem(Id);
SubscriptionCount++;
}
public void UnSubscribe()
{
workshop.ugc.UnsubscribeItem(Id);
SubscriptionCount--;
}
private void OnFileDownloaded( ulong fileid, Callbacks.Result result ) private void OnFileDownloaded( ulong fileid, Callbacks.Result result )
{ {
if ( fileid != Id ) return; if ( fileid != Id ) return;

View File

@ -29,8 +29,7 @@ namespace Facepunch.Steamworks
/// <summary> /// <summary>
/// Called when an item has been downloaded. This could have been /// Called when an item has been downloaded. This could have been
/// because of a call to Download or because of a subscription triggered /// because of a call to Download.
/// via the browser/app.
/// </summary> /// </summary>
public event Action<ulong, Callbacks.Result> OnFileDownloaded; public event Action<ulong, Callbacks.Result> OnFileDownloaded;
@ -39,7 +38,7 @@ namespace Facepunch.Steamworks
/// because of a call to Download or because of a subscription triggered /// because of a call to Download or because of a subscription triggered
/// via the browser/app. /// via the browser/app.
/// </summary> /// </summary>
internal event Action<ulong> OnItemInstalled; public event Action<ulong> OnItemInstalled;
internal Workshop( BaseSteamworks steamworks, SteamNative.SteamUGC ugc, SteamNative.SteamRemoteStorage remoteStorage ) internal Workshop( BaseSteamworks steamworks, SteamNative.SteamUGC ugc, SteamNative.SteamRemoteStorage remoteStorage )
{ {
@ -67,7 +66,7 @@ namespace Facepunch.Steamworks
private void onItemInstalled( SteamNative.ItemInstalled_t obj, bool failed ) private void onItemInstalled( SteamNative.ItemInstalled_t obj, bool failed )
{ {
if ( OnItemInstalled != null ) if ( OnItemInstalled != null && obj.AppID == Client.Instance.AppId )
OnItemInstalled( obj.PublishedFileId ); OnItemInstalled( obj.PublishedFileId );
} }
@ -123,7 +122,6 @@ namespace Facepunch.Steamworks
return new Item( itemid, this ); return new Item( itemid, this );
} }
/// <summary> /// <summary>
/// How a query should be ordered. /// How a query should be ordered.
/// </summary> /// </summary>

View File

@ -31,7 +31,7 @@ namespace Facepunch.Steamworks
/// this will be called when the stats are recieved, the bool will signify whether /// this will be called when the stats are recieved, the bool will signify whether
/// it was successful or not. /// it was successful or not.
/// </summary> /// </summary>
public void Refresh( ulong steamid, Action<bool> Callback = null ) public void Refresh( ulong steamid, Action<ulong, bool> Callback = null )
{ {
if ( Callback == null ) if ( Callback == null )
{ {
@ -41,7 +41,7 @@ namespace Facepunch.Steamworks
server.native.gameServerStats.RequestUserStats( steamid, ( o, failed ) => server.native.gameServerStats.RequestUserStats( steamid, ( o, failed ) =>
{ {
Callback( o.Result == SteamNative.Result.OK && !failed ); Callback( steamid, o.Result == SteamNative.Result.OK && !failed );
} ); } );
} }
@ -50,7 +50,7 @@ namespace Facepunch.Steamworks
/// You can do that using this function. The callback will let you know if /// You can do that using this function. The callback will let you know if
/// your action succeeded, but most of the time you can fire and forget. /// your action succeeded, but most of the time you can fire and forget.
/// </summary> /// </summary>
public void Commit( ulong steamid, Action<bool> Callback = null ) public void Commit( ulong steamid, Action<ulong, bool> Callback = null )
{ {
if ( Callback == null ) if ( Callback == null )
{ {
@ -60,7 +60,7 @@ namespace Facepunch.Steamworks
server.native.gameServerStats.StoreUserStats( steamid, ( o, failed ) => server.native.gameServerStats.StoreUserStats( steamid, ( o, failed ) =>
{ {
Callback( o.Result == SteamNative.Result.OK && !failed ); Callback( steamid, o.Result == SteamNative.Result.OK && !failed );
} ); } );
} }
@ -68,7 +68,7 @@ namespace Facepunch.Steamworks
/// Set the named stat for this user. Setting stats should follow the rules /// Set the named stat for this user. Setting stats should follow the rules
/// you defined in Steamworks. /// you defined in Steamworks.
/// </summary> /// </summary>
public bool Set( ulong steamid, string name, int stat ) public bool SetInt( ulong steamid, string name, int stat )
{ {
return server.native.gameServerStats.SetUserStat( steamid, name, stat ); return server.native.gameServerStats.SetUserStat( steamid, name, stat );
} }
@ -77,7 +77,7 @@ namespace Facepunch.Steamworks
/// Set the named stat for this user. Setting stats should follow the rules /// Set the named stat for this user. Setting stats should follow the rules
/// you defined in Steamworks. /// you defined in Steamworks.
/// </summary> /// </summary>
public bool Set( ulong steamid, string name, float stat ) public bool SetFloat( ulong steamid, string name, float stat )
{ {
return server.native.gameServerStats.SetUserStat0( steamid, name, stat ); return server.native.gameServerStats.SetUserStat0( steamid, name, stat );
} }