Grabbing item from the inventory

Hi all,
I’m working on some server side logic and am trying to get the player inventory, I think cloud code is going to be the best way for me to go but I am having some trouble getting the cloud code to work. I might just be looking in the wrong place, so if this does exist somewhere please let me know!

So I have this in my cloud code:

const { InventoryApi } = require("@unity-services/economy-2.3");
module.exports = async ({params, context, logger}) => {
const { projectId, playerId } = context;
const inventory = new InventoryApi(context);
const result = await inventory.getPlayerInventory({ projectId, playerId });
return result.data;
}

which seems like an obvious starting point. So I am guessing I need to take that “result” const and check to see if it has an item. Let’s just say its the sword item from the inventory examples. So if I want to check if the player has “sword” for do I do that here in the cloud code? Even better, if I just want the custom parameters to be sent back (like the “rarity” in the economy example) so I can just call this cloud code script with a playerID and an item name and it would just return the parameters (assuming the player has the item)

Any help at all here would be greatly appreciated!

Also just realized I posted this to Cloud Save and not Cloud Code… oops! If any mods want to delete this one feel free. Gonna make the post on the proper board now.

1 Like

Hi @Corva-Nocta ,

I’ve moved your post over to the Cloud Code forum.

You are on the right track.

One thing to point out though, CustomData refers to parameters that you define and attach to an Inventory Item definition. When a player picks up an inventory item they are holding an Instance of it that does not contain any custom data but does contain an empty InstanceData object. Take a look at the Custom Data & Instance Data documentation.

You would therefore probably want to copy the Custom Data or certain parameters from it, from the item definition to the Instance Data as the player receives the item, so you can access and potentially modify the instance’s configuration over time. e.g. think about decreasing durability as the item is used.

With that in mind, here are some code snippets that might help.

A Cloud Code script to retrieve player inventory

/*
*  -------- Cloud Code Script ----------------
*  Get Player Inventory - iterate over pages
* --------------------------------------------
*/
const { InventoryApi } = require("@unity-services/economy-2.4");

module.exports = async ({ params, context, logger }) => {

  const {
    projectId,
    playerId,
    accessToken
  } = context;
 
  const inventoryApi = new InventoryApi(context);
 
  // Default to pages of 20 inventory items if none specified as input params.pageSize
  const pageSize = params.pageSize != undefined ? params.pageSize : 20 ;
  let pages = 0 ;
  let inv = [];
 
  // get a page worth of inventory items
  let playerInventory = await inventoryApi.getPlayerInventory({projectId, playerId, limit:pageSize});
  
  // When we have results
  while( playerInventory.data.results.length > 0) {
  
      // Add them to the local inv array
      playerInventory.data.results.forEach( i => inv.push(i));
 
      // Grab the playersInventoryItemId of the last one
      lastPlayersInventoryItemId = playerInventory.data.results[playerInventory.data.results.length -1].playersInventoryItemId;
 
      // Then go looking for another page worth
      playerInventory = await inventoryApi.getPlayerInventory({projectId, playerId, limit: pageSize, after: lastPlayersInventoryItemId});
        pages ++;
  }
 
  // Return the JSON result to the client
  return {
    inventory: inv,
    pages: pages
  };
};

Is called from this C# client side script

    public async void GetInventoryFromCloudCodeButtonClicked()
    {
        Dictionary<string, object> requestParams = new Dictionary<string, object>();
        requestParams.Add("pageSize", 15);
   
        ResultType response = await CloudCodeService.Instance.CallEndpointAsync<ResultType>("getInventory", requestParams);   
        InventoryResponse[] playerInventoryArray = response.inventory;

        Debug.Log($"Returned {playerInventoryArray.Count()} items of inventory");
        Debug.Log($"pages {response.pages}");

        foreach(InventoryResponse playerInventoryItem in playerInventoryArray) {
            Debug.Log($"itemId : {playerInventoryItem.inventoryItemId} ");
            Debug.Log($"playersInventoryItemId : {playerInventoryItem.playersInventoryItemId }");

            string instanceData = playerInventoryItem.instanceData != null ? playerInventoryItem.instanceData.ToString() : "NULL";
            Debug.Log($"InstanceData : {instanceData} ");
       
        }
    }

Assuming these two classes have been defined to work with the repsonse from the Cloud Code script.

    private class ResultType
    {
        public InventoryResponse[] inventory;
        public int pages;
    }

    private class InventoryResponse
    {
        public JObject created;
        public JObject instanceData;
        public string inventoryItemId;
        public JObject modified;
        public string playersInventoryItemId;
        public string writeLock;
    }

I hope that helps

1 Like

So I am trying to use some of the code you posted, seems fairly straight forward but I am getting a tiny error. I want to combine the cloud code for grabbing the inventory with the code of grabbing another player’s inventory item. Here’s what I’ve got:

const { InventoryApi } = require("@unity-services/economy-2.3");
module.exports = async ({ params, context, logger }) => {
  const { projectId, playerId } = context;
  const { playerID } = params;
  const inventory = new InventoryApi(context);
 
  const playerCheck = await inventory.getPlayerInventory({
    projectId,
    playerId: { playerID },
    inventoryItemIds: "PLAYERDATA" });
 
  if (playerCheck.data.results.length > 0)
  {
    return playerCheck.data;
  }
  else
  {
    const createdInv = await inventory.addInventoryItem({
      projectId,
      playerID,
      addInventoryRequest:
      {
        inventoryItemId: "PLAYERDATA",
        instanceData:
        {
          "Chunk": 0,
          "World": "Main",
          "X": 0,
          "Y": 0,
          "Z": 0
        }
      }
    });
    return playerCheck.data;
  }
}

playerID is a param that is required to run the code.

and using this code in C#:

public async void GetPlayerInfo(string playerName)
    {
        Dictionary<string, object> requestParams = new Dictionary<string, object>();
        requestParams.Add("playerID", playerName);
        PlayerData pData = await CloudCodeService.Instance.CallEndpointAsync<PlayerData>("InitializePlayer", requestParams);
        Debug.Log(pData);
    }

but I get this error in the editor:
ArgumentException: Failed to lookup discriminator value for problems/invocation/axios. Possible values: problems/basic, problems/invocation, BasicErrorResponse, InvocationErrorResponse
Unity.Services.CloudCode.Internal.Models.RunScript422OneOf.GetConcreteType (System.String type) (at Library/PackageCache/com.unity.services.cloudcode@2.1.2/Runtime/com.unity.services.cloudcode.internal/Models/RunScript422OneOf.cs:69)
The rest of the error seems to be pretty much this same thing.

Not sure why this isn’t working. I tried a few different things that the errors were giving me about the “playerId” being null or incorrect, so I changed it to its current state. I think its done correctly, I’m not getting the error about the playerId anymore at least, but I have no idea what this error means. Any ideas?

It is currently not possible to access another player’s inventory (or add items to it). I hope they will support this soon. Adding items from a Game Server as rewards to players is a common use case.

My current workaround is to use CloudSave and add a “pending items” field. When the player retrieves their inventory via CloudCode, it will process the pending items into their actual inventory.

You can probably do something similar for your case by building the list of the player’s items and store it in CloudSave each time they log out. It won’t be realtime, but it may be good enough.

Ugh really? What is the point of the economy system then? lol. Not gonna lie, that’s a bit frustrating.

I started with doing things through client side, which was ok but super inefficient and annoying, and also just for testing before doing server side. Guess I’ll just have to stick to client side (But at least I know how to do cloud code for it now!)

The cloud save option might work though. I am only saving and loading the inventory when a player logs in/out, so its not needed to be done constantly. I’ll see what I can figure out for it.

As long as the player doesn’t have an enormous inventory, it may work just fine for you. All you need to save are the item ids. A hundred items may only use KBs of data.

I think I actually do have it working now! Economy 2.3 doesn’t let it work, but Economy 2.4 appears to have it working. Well sort of. Its working when I run the test code, but when I look back in the inventory its not there, which might just be a timing bug. When I run the code again the item is definitely there, so I think going through the Find Player option just doesn’t update very fast. But everything appear to be working for dealing with other playerIDs

Just tested in the editor, and it does work! We can do server side saving and altering now!

Hmm…I’ll give it a try. Not sure when 2.4 was released.

I just gave it a try with 2.4 and was not successful. Are you certain you’re retrieving/modifying the items of another player’s account and not your own?

I got the following error which states that I’m not permitted to access this user’s data:
“response”: {
“code”: 53,
“detail”: “you are not permitted to access this user’s data”,
“instance”: null,
“status”: 403,
“title”: “Error”,
“type”: “problems/basic”
}

Hm, odd. That was definitely the same error I was getting. I now don’t get the error, and it seems to be working, but I’ll do some more testing. As far as I can tell it’s not doing anything to the server’s profile, it seems to be working with the other player ID. The odd part that I do see though is that the new player doesn’t appear when I go to find a player. So I am really not sure what is going on.

I’ll try to get some tests this weekend to see what’s going on. I’m traveling a bunch for work but I’ll try to get what I can

Oh, it seems to be working now. This opens the door to a lot of things.

I have to have this line:
const economy = new InventoryApi(context);

instead of:
const economy = new InventoryApi({ accessToken });

1 Like

If you want the code I am currently running:

const { InventoryApi } = require("@unity-services/economy-2.4");

module.exports = async ({ params, context, logger }) => {
  const { projectId } = context;
  const { playerName } = params;
  const inventory = new InventoryApi(context);
 
  const playerCheck = await inventory.getPlayerInventory({
    projectId,
    playerId: { playerName },
    inventoryItemIds: "PLAYERDATA" });
 
  if (playerCheck.data.results.length > 0)
  {
    return playerCheck.data;
  }
  else
  {
    const createdInv = await inventory.addInventoryItem({
      projectId,
      playerId: { playerName },
      addInventoryRequest:
      {
        inventoryItemId: "PLAYERDATA",
        instanceData:
        {
          "Chunk": 0,
          "World": "Main",
          "X": 0,
          "Y": 0,
          "Z": 0
        }
      }
    });
    return playerCheck.data;
  }
}

no major changes, but I changed playerID to playerName to make sure I wasn’t confusing myself. When I run it things seem to work just fine, but I am not 100% positive I’m just adjusting the server’s data and not the other player.

1 Like

It’s working now. The difference here is that you can now pass in your service token to make changes to other player accounts which wasn’t possible in 2.3.

Awesome!!! Glad it’s working!

Can you see your other players in the Find Player tab? I still can’t and it worries me haha. I’m thinking it is probably because I’m not passing a service token, just the name of the user.

Do I need to create a new player profile when a new player connects to the server to be able to get the service token? Or is that something that works automatically?

I’m not sure what you mean by “Find Player tab”. The service token is part of the context object which is automatically included when you pass it like this “const inventory = new InventoryApi(context)”.

Oh right! On the UGS dashboard under the Economy section, the tab for Find Player. When I run the cloud code, I don’t see players added through the cloud code when I run it from Unity. It does create player profiles when I run through the cloud code test though

I think when running from the website, the accounts are temporary. You should still be able to search for the user id.

Yeah the accounts only last for an hour, but the data that gets created for them lasts forever. If I create an inventory for example, after that hour time limit the inventory will persist.

So the weird part I’m having is even when I try searching with the user ID accounts created through Unity don’t show up. It’s a bit odd. I’m thinking that I need to involve passing the user token like you do to make that work. Currently I’m not using any information involving the user token. Though if I remember correctly, aren’t user tokens also a temporary id?

I’m not passing in the token at all. All you should need is the user id (or player id). The user ids are usually much shorter than the token. Tokens are not the same as user ids.

Tokens will expire and gives you temporary access to a resource. The user id identifies an account and are permanent.