From 45024c57f31a52c8bfd767b26ce812262f218f75 Mon Sep 17 00:00:00 2001 From: Garry Newman Date: Wed, 8 Feb 2017 13:33:37 +0000 Subject: [PATCH] Added leaderboards --- .../Client/Leaderboard.cs | 93 ++++++++++ .../Facepunch.Steamworks.Test.csproj | 1 + Facepunch.Steamworks/Client.cs | 23 +++ Facepunch.Steamworks/Client/Leaderboard.cs | 175 ++++++++++++++++++ .../Facepunch.Steamworks.csproj | 1 + 5 files changed, 293 insertions(+) create mode 100644 Facepunch.Steamworks.Test/Client/Leaderboard.cs create mode 100644 Facepunch.Steamworks/Client/Leaderboard.cs diff --git a/Facepunch.Steamworks.Test/Client/Leaderboard.cs b/Facepunch.Steamworks.Test/Client/Leaderboard.cs new file mode 100644 index 0000000..5244edb --- /dev/null +++ b/Facepunch.Steamworks.Test/Client/Leaderboard.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Facepunch.Steamworks.Test +{ + [TestClass] + [DeploymentItem( "steam_api.dll" )] + [DeploymentItem( "steam_api64.dll" )] + [DeploymentItem( "steam_appid.txt" )] + public class Leaderboard + { + [TestMethod] + public void GetLeaderboard() + { + using ( var client = new Steamworks.Client( 252490 ) ) + { + var board = client.GetLeaderboard( "TestLeaderboard", Steamworks.Client.LeaderboardSortMethod.Ascending, Steamworks.Client.LeaderboardDisplayType.Numeric ); + + while ( !board.IsValid ) + { + Thread.Sleep( 10 ); + client.Update(); + } + + Assert.IsTrue( board.IsValid ); + Assert.IsFalse( board.IsError ); + Assert.IsNotNull( board.Name ); + + Console.WriteLine( $"Board name is \"{board.Name}\"" ); + Console.WriteLine( $"Board has \"{board.TotalEntries}\" entries" ); + + board.AddScore( true, false, 86275309, 7, 8, 9 ); + + board.FetchScores( Steamworks.Leaderboard.RequestType.Global, 0, 20 ); + + while ( board.IsQuerying ) + { + Thread.Sleep( 10 ); + client.Update(); + } + + foreach ( var entry in board.Results ) + { + Console.WriteLine( $"{entry.GlobalRank}: {entry.SteamId} ({entry.Name}) with {entry.Score}" ); + + if ( entry.SubScores != null ) + Console.WriteLine( " - " + string.Join( ";", entry.SubScores.Select( x => x.ToString() ).ToArray() ) ); + } + } + } + + [TestMethod] + public void AddScores() + { + using ( var client = new Steamworks.Client( 252490 ) ) + { + var board = client.GetLeaderboard( "TestLeaderboard", Steamworks.Client.LeaderboardSortMethod.Ascending, Steamworks.Client.LeaderboardDisplayType.Numeric ); + + while ( !board.IsValid ) + { + Thread.Sleep( 10 ); + client.Update(); + } + + Assert.IsTrue( board.IsValid ); + Assert.IsFalse( board.IsError ); + + + board.AddScore( true, false, 1234 ); + + Thread.Sleep( 10 ); + client.Update(); + + board.AddScore( true, true, 34566 ); + + Thread.Sleep( 10 ); + client.Update(); + + board.AddScore( true, false, 86275309, 7, 8, 9, 7, 4, 7, 98, 24, 5, 76, 124, 6 ); + + Thread.Sleep( 10 ); + client.Update(); + + board.AddScore( false, true, 86275309, 7, 8, 9, 7, 4, 7, 98, 24, 5, 76, 124, 6 ); + + Thread.Sleep( 10 ); + client.Update(); + } + } + } +} \ No newline at end of file diff --git a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj index f38be94..4eeecf3 100644 --- a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj +++ b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj @@ -93,6 +93,7 @@ + diff --git a/Facepunch.Steamworks/Client.cs b/Facepunch.Steamworks/Client.cs index 4f8531d..3733bea 100644 --- a/Facepunch.Steamworks/Client.cs +++ b/Facepunch.Steamworks/Client.cs @@ -106,5 +106,28 @@ namespace Facepunch.Steamworks base.Dispose(); } + + public enum LeaderboardSortMethod + { + None = 0, + Ascending = 1, // top-score is lowest number + Descending = 2, // top-score is highest number + }; + + // the display type (used by the Steam Community web site) for a leaderboard + public enum LeaderboardDisplayType + { + None = 0, + Numeric = 1, // simple numerical score + TimeSeconds = 2, // the score represents a time, in seconds + TimeMilliSeconds = 3, // the score represents a time, in milliseconds + }; + + public Leaderboard GetLeaderboard( string name, LeaderboardSortMethod sortMethod = LeaderboardSortMethod.None, LeaderboardDisplayType displayType = LeaderboardDisplayType.None ) + { + var board = new Leaderboard( this ); + native.userstats.FindOrCreateLeaderboard( name, (SteamNative.LeaderboardSortMethod)sortMethod, (SteamNative.LeaderboardDisplayType)displayType, board.OnBoardCreated ); + return board; + } } } diff --git a/Facepunch.Steamworks/Client/Leaderboard.cs b/Facepunch.Steamworks/Client/Leaderboard.cs new file mode 100644 index 0000000..f2c2551 --- /dev/null +++ b/Facepunch.Steamworks/Client/Leaderboard.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SteamNative; + +namespace Facepunch.Steamworks +{ + public class Leaderboard : IDisposable + { + /// + /// Type of leaderboard request + /// + public enum RequestType + { + /// + /// Query everyone and everything + /// + Global = LeaderboardDataRequest.Global, + + /// + /// Query only users that are close to you geographically + /// + GlobalAroundUser = LeaderboardDataRequest.GlobalAroundUser, + + /// + /// Only show friends of this user + /// + Friends = LeaderboardDataRequest.Friends + } + + private static readonly int[] subEntriesBuffer = new int[512]; + + internal ulong BoardId; + internal Client client; + + /// + /// The results from the last query. Can be null. + /// + public Entry[] Results; + + internal Leaderboard( Client c ) + { + client = c; + } + + /// + /// The name of this board, as retrieved from Steam + /// + public string Name { get; private set; } + + /// + /// The total number of entries on this board + /// + public int TotalEntries { get; private set; } + + /// + /// Returns true if this board is valid, ie, we've received + /// a positive response from Steam about it. + /// + public bool IsValid => BoardId != 0; + + /// + /// Returns true if we asked steam about this board but it returned + /// an error. + /// + public bool IsError { get; private set; } + + /// + /// Returns true if we're querying scores + /// + public bool IsQuerying { get; private set; } + + public void Dispose() + { + client = null; + } + + internal void OnBoardCreated( LeaderboardFindResult_t result, bool error ) + { + if ( error || ( result.LeaderboardFound == 0 ) ) + { + IsError = true; + return; + } + + BoardId = result.SteamLeaderboard; + + if ( IsValid ) + { + Name = client.native.userstats.GetLeaderboardName( BoardId ); + TotalEntries = client.native.userstats.GetLeaderboardEntryCount( BoardId ); + } + } + + /// + /// Add a score to this leaderboard. + /// Subscores are totally optional, and can be used for other game defined data such as laps etc.. although + /// they have no bearing on sorting at all. + /// + public void AddScore( bool replaceOldScore, bool onlyIfBeatsOldScore, int score, params int[] subscores ) + { + if ( !IsValid ) return; + + var flags = LeaderboardUploadScoreMethod.None; + + if ( replaceOldScore ) flags |= LeaderboardUploadScoreMethod.ForceUpdate; + if ( onlyIfBeatsOldScore ) flags |= LeaderboardUploadScoreMethod.KeepBest; + + client.native.userstats.UploadLeaderboardScore( BoardId, flags, score, subscores, subscores.Length ); + } + + /// + /// Fetch a subset of scores. The scores end up in Results. + /// + /// Returns true if we have started the query + public bool FetchScores( RequestType RequestType, int start, int end ) + { + if ( !IsValid ) return false; + if ( IsQuerying ) return false; + + client.native.userstats.DownloadLeaderboardEntries( BoardId, (LeaderboardDataRequest) RequestType, start, end, OnScores ); + + Results = null; + IsQuerying = true; + return true; + } + + private unsafe void OnScores( LeaderboardScoresDownloaded_t result, bool error ) + { + IsQuerying = false; + + if ( client == null ) return; + + if ( error ) + return; + + var list = new List(); + + for ( var i = 0; i < result.CEntryCount; i++ ) + fixed ( int* ptr = subEntriesBuffer ) + { + var entry = new LeaderboardEntry_t(); + if ( client.native.userstats.GetDownloadedLeaderboardEntry( result.SteamLeaderboardEntries, i, ref entry, (IntPtr) ptr, subEntriesBuffer.Length ) ) + list.Add( new Entry + { + GlobalRank = entry.GlobalRank, + Score = entry.Score, + SteamId = entry.SteamIDUser, + SubScores = entry.CDetails == 0 ? null : subEntriesBuffer.Take( entry.CDetails ).ToArray(), + Name = client.Friends.GetName( entry.SteamIDUser ) + } ); + } + + Results = list.ToArray(); + } + + /// + /// A single entry in a leaderboard + /// + public struct Entry + { + public ulong SteamId; + public int Score; + public int[] SubScores; + public int GlobalRank; + + + /// + /// Note that the player's name might not be immediately available. + /// If that's the case you'll have to use Friends.GetName to find the name + /// + public string Name; + } + } +} \ No newline at end of file diff --git a/Facepunch.Steamworks/Facepunch.Steamworks.csproj b/Facepunch.Steamworks/Facepunch.Steamworks.csproj index 21d3f90..b9e8c4e 100644 --- a/Facepunch.Steamworks/Facepunch.Steamworks.csproj +++ b/Facepunch.Steamworks/Facepunch.Steamworks.csproj @@ -115,6 +115,7 @@ +