diff --git a/Facepunch.Steamworks.Test/Client.cs b/Facepunch.Steamworks.Test/Client.cs index a6ca188..796f2ae 100644 --- a/Facepunch.Steamworks.Test/Client.cs +++ b/Facepunch.Steamworks.Test/Client.cs @@ -131,5 +131,49 @@ public void GetServers() Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() ); } } + + [TestMethod] + public void InventoryDefinitions() + { + using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) + { + Assert.IsNotNull( client.Inventory.Definitions ); + Assert.AreNotEqual( 0, client.Inventory.Definitions.Length ); + } + } + + [TestMethod] + public void InventoryItemList() + { + using ( var client = new Facepunch.Steamworks.Client( 252490 ) ) + { + bool CallbackCalled = false; + + // OnUpdate hsould be called when we receive a list of our items + client.Inventory.OnUpdate = () => { CallbackCalled = true; }; + + // tell steam to download the items + client.Inventory.Refresh(); + + // Wait for the items + while ( client.Inventory.Items == null ) + { + client.Update(); + System.Threading.Thread.Sleep( 10 ); + } + + // make sure callback was called + Assert.IsTrue( CallbackCalled ); + + // Make sure items are valid + foreach ( var item in client.Inventory.Items ) + { + Assert.IsNotNull( item ); + Assert.IsNotNull( item.Definition ); + + Console.WriteLine( item.Definition.Name + " - " + item.Id ); + } + } + } } } diff --git a/Facepunch.Steamworks/Client.cs b/Facepunch.Steamworks/Client.cs index 6404ee3..2cbfac0 100644 --- a/Facepunch.Steamworks/Client.cs +++ b/Facepunch.Steamworks/Client.cs @@ -14,6 +14,7 @@ public partial class Client : IDisposable internal Valve.Steamworks.ISteamUser _user; internal Valve.Steamworks.ISteamFriends _friends; internal Valve.Steamworks.ISteamMatchmakingServers _servers; + internal Valve.Steamworks.ISteamInventory _inventory; /// /// Current running program's AppId @@ -60,6 +61,7 @@ public Client( uint appId ) _friends = _client.GetISteamFriends( _huser, _hpipe, "SteamFriends015" ); _user = _client.GetISteamUser( _huser, _hpipe, "SteamUser019" ); _servers = _client.GetISteamMatchmakingServers( _huser, _hpipe, "SteamMatchMakingServers002" ); + _inventory = _client.GetISteamInventory( _huser, _hpipe, "STEAMINVENTORY_INTERFACE_V001" ); AppId = appId; Username = _friends.GetPersonaName(); @@ -103,11 +105,20 @@ public void Update() { Valve.Steamworks.SteamAPI.RunCallbacks(); Voice.Update(); + Inventory.Update(); } public bool Valid { get { return _client != null; } } + + internal Action InstallCallback( int type, Action action ) + { + var ptr = Marshal.GetFunctionPointerForDelegate( action ); + Valve.Steamworks.SteamAPI.RegisterCallback( ptr, type ); + + return () => Valve.Steamworks.SteamAPI.UnregisterCallback( ptr ); + } } } diff --git a/Facepunch.Steamworks/Client/Inventory.cs b/Facepunch.Steamworks/Client/Inventory.cs new file mode 100644 index 0000000..b097f57 --- /dev/null +++ b/Facepunch.Steamworks/Client/Inventory.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Facepunch.Steamworks +{ + public partial class Client : IDisposable + { + Inventory _inv; + + public Inventory Inventory + { + get + { + if ( _inv == null ) + _inv = new Inventory( this ); + + return _inv; + } + } + } + + public class Inventory + { + /// + /// Called when the items are first retrieved, and when they change + /// + public Action OnUpdate; + + internal Client client; + private int updateRequest = 0; + + internal Inventory( Client c ) + { + client = c; + + LoadItemDefinitions(); + + + } + + /// + /// Call this to retrieve the items. + /// Note that if this has already been called it won't + /// trigger a call to OnUpdate unless the items have changed + /// + public void Refresh() + { + // Pending + if ( updateRequest != 0 ) + return; + + client._inventory.GetAllItems( ref updateRequest ); + } + + internal void LoadItemDefinitions() + { + // + // Make sure item definitions are loaded, because we're going to be using them. + // + client._inventory.LoadItemDefinitions(); + + int[] ids; + if ( !client._inventory.GetItemDefinitionIDs( out ids ) ) + return; + + Definitions = ids.Select( x => + { + var d = new Definition() + { + client = client, + Id = x + }; + + d.SetupCommonProperties(); + return d; + + } ).ToArray(); + } + + internal T DefinitionProperty( int i, string name ) + { + string val = string.Empty; + client._inventory.GetItemDefinitionProperty( i, name, out val ); + return (T) Convert.ChangeType( val, typeof( T) ); + } + + internal void DestroyResult() + { + if ( updateRequest != 0 ) + { + client._inventory.DestroyResult( updateRequest ); + updateRequest = 0; + } + } + + + internal void Update() + { + UpdateRequest(); + } + + private void UpdateRequest() + { + if ( updateRequest == 0 ) + return; + + var status = (Valve.Steamworks.EResult) client._inventory.GetResultStatus( updateRequest ); + + if ( status == Valve.Steamworks.EResult.k_EResultPending ) + return; + + if ( status == Valve.Steamworks.EResult.k_EResultOK || status == Valve.Steamworks.EResult.k_EResultExpired ) + { + RetrieveInventory(); + } + + // Some other error + // Lets just retry. + DestroyResult(); + Refresh(); + } + + /// + /// A list of items owned by this user. You should call Refresh() before trying to access this, + /// and then wait until it's non null or listen to OnUpdate to find out immediately when it's populated. + /// + public Item[] Items; + + private void RetrieveInventory() + { + Valve.Steamworks.SteamItemDetails_t[] items = null; + client._inventory.GetResultItems( updateRequest, out items ); + + Items = items.Select( x => + { + return new Item() + { + client = client, + Quantity = x.m_unQuantity, + Id = x.m_itemId, + DefinitionId = x.m_iDefinition, + TradeLocked = ( (int)x.m_unFlags & (int)Valve.Steamworks.ESteamItemFlags.k_ESteamItemNoTrade ) != 0, + Definition = Definitions.FirstOrDefault( y => y.Id == x.m_iDefinition ) + }; + } ).ToArray(); + + // + // Tell everyone we've got new items! + // + OnUpdate?.Invoke(); + } + + /// + /// An item in your inventory. + /// + public class Item + { + internal Client client; + + public ulong Id; + public int Quantity; + + public int DefinitionId; + public Definition Definition; + + public bool TradeLocked; + } + + /// + /// A list of items defined for this app. + /// This should be immediately populated and available. + /// + public Definition[] Definitions; + + + /// + /// An item definition. This describes an item in your Steam inventory, but is + /// not unique to that item. For example, this might be a tshirt, but you might be able to own + /// multiple tshirts. + /// + public class Definition + { + internal Client client; + + public int Id; + public string Name; + public string Description; + + public DateTime Created; + public DateTime Modified; + + public T GetProperty( string name ) + { + string val = string.Empty; + + if ( !client._inventory.GetItemDefinitionProperty( Id, name, out val ) ) + return default( T ); + + try + { + return (T)Convert.ChangeType( val, typeof( T ) ); + } + catch( System.Exception ) + { + return default( T ); + } + } + + internal void SetupCommonProperties() + { + Name = GetProperty( "name" ); + Description = GetProperty( "description" ); + Created = GetProperty( "timestamp" ); + Modified = GetProperty( "modified" ); + } + } + + } +} diff --git a/Facepunch.Steamworks/Facepunch.Steamworks.csproj b/Facepunch.Steamworks/Facepunch.Steamworks.csproj index bcf343e..510a2a1 100644 --- a/Facepunch.Steamworks/Facepunch.Steamworks.csproj +++ b/Facepunch.Steamworks/Facepunch.Steamworks.csproj @@ -119,6 +119,7 @@ + diff --git a/Facepunch.Steamworks/steam_api_interop.cs b/Facepunch.Steamworks/steam_api_interop.cs index 76d6a83..bbd205b 100644 --- a/Facepunch.Steamworks/steam_api_interop.cs +++ b/Facepunch.Steamworks/steam_api_interop.cs @@ -2558,7 +2558,7 @@ internal override ISteamInventory GetISteamInventory( uint hSteamuser, uint hSte { CheckIfUsable(); IntPtr result = NativeEntrypoints.SteamAPI_ISteamClient_GetISteamInventory(m_pSteamClient,hSteamuser,hSteamPipe,pchVersion); - return (ISteamInventory)Marshal.PtrToStructure( result, typeof( ISteamInventory ) ); + return new CSteamInventory( result ); } internal override ISteamVideo GetISteamVideo( uint hSteamuser, uint hSteamPipe, string pchVersion ) {