How can I grab an Inventory Item Custom Data using Cloud Code?

Hello everyone,

As per the documentation here: Cloud Code Services SDKs (unity3d.com)

I cannot find a method to get a specific item custom data, neither its returned in the value of returned items when running the “getPlayerInventory” method.

The only way I can it possible right now is still using the the C# function "Unity.Services.Economy.Model.PlayersInventoryItem.GetItemDefinition()" as it returns an InventoryItemDefinition which has the custom Data.

In other words, can I get the value from Cloud Code directly or there is no method that exposes this value at the current moment?

Screenshot attached for an explanation of what valuie is needed.

Hi @Malhmoud ,

As the economy inventory API doesn’t return the custom data as part of the response Unity Services Web API docs, I’ve taken a look at the GetItemDefinition method and essentially the method is a helper function to retrieve the configuration of the inventory item.

Unfortunately a similar helper method doesn’t currently exist in Cloud Code but I believe you would be able to fetch what you need via the configuration API (see Cloud Code Services SDKs) by looking up the inventory’s custom data in the response using the inventory item ID.

2 Likes

Thank you @samg-unity , can you please give me an example on how to use the API? I am having a hard time sending creating a ConfigurationApiGetPlayerConfigurationRequest

No problem @Malhmoud

I set up some basic economy configuration with a Sword item and custom data;

Then I’ve put together the following script which I believe gets the result you’re after,

(Note: the configAssignmentHash is entirely optional but I wanted to demonstrate how you can you can pass that value if you want to make use of overrides Economy Game Overrides with Cloud Code)

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

const swordId = "SWORD";

module.exports = async ({ params, context, logger }) => {
 
  const {playerId, projectId} = context;
  const {configAssignmentHash} = params;
 
  const configApi = new ConfigurationApi(context);
  const inventoryApi = new InventoryApi(context);
 
  const getPlayerConfigRequest = {
    configAssignmentHash,
    playerId,
    projectId,
  }

  const getPlayerInventoryRequest = {
    configAssignmentHash,
    playerId,
    projectId,
  }
 
  try {

    // TEST SETUP: Used to create initial player inventory
    // await inventoryApi.addInventoryItem({
    //   addInventoryRequest: {
    //     inventoryItemId : swordId,
    //     instanceData: {
    //       damage: 80,
    //     }
    //   },
    //   playerId,
    //   projectId
    // });
  
    var config = await configApi.getPlayerConfiguration(getPlayerConfigRequest);  
 
    const inventoryConfig = config.data.results.filter((configItem) => {
        return configItem.type === "INVENTORY_ITEM";
    })

    const result = await inventoryApi.getPlayerInventory(getPlayerInventoryRequest);
  
    const itemsWithCustomData = result.data.results.map((inventoryItem) => {
      const inventoryItemConfig = inventoryConfig.find((configItem) => {
        return configItem.id === inventoryItem.inventoryItemId;
      })
      inventoryItem.customData = inventoryItemConfig.customData;
      return inventoryItem;
    });
  
    return itemsWithCustomData;
    // [{
    //   "created": {
    //     "date": "2024-03-18T10:05:07Z"
    //   },
    //   "customData": {
    //     "unique": true
    //   },
    //   "instanceData": {
    //     "damage": 80
    //   },
    //   "inventoryItemId": "SWORD",
    //   "modified": {
    //     "date": "2024-03-18T10:05:07Z"
    //   },
    //   "playersInventoryItemId": "d4339b75-8168-4529-60ad-d343607e0d6b",
    //   "writeLock": "1"
    // }]
  
  } catch (err) {
    logger.error("Error while retrieving inventory items", {"error.message": err.message});
    throw err;
  }
};
1 Like

Hey!

I’m using this very same code to solve a similar issue I have. I am making a card game where each player has only 5 cards. For that reason, players can save premade hands and then select them before a game, and the backend (cloud code) has to filter which hands are available for choosing at the moment of the game, depending on whether those hands can be used with the current ruleset, if they are currently in the players collection AND the player has not put those cards for sell (there ar eplayer owned shops).

The problem I’m having with the code, is that I seem to be getting only a page of the items in the player inventory. So I tried setting the request limit to 0 to see if it would just not page the thing, and it didn’t work. I tried 111 (all inventory items currently in game. This would still NOT be practical as a player can have multiple copies of a card, thus having more than the 111 cards currently in game, but still, fit for testing purposes), and this gave me a 400 Bad Request. IS there any way to avoid the result from being paged altogether? Or at least any way for me to go through the pages? This is really hindering my progress.

Here’s my script:

const { DataApi } = require('@unity-services/cloud-save-1.3');
const { InventoryApi, ConfigurationApi } = require("@unity-services/economy-2.4");

module.exports = async ({ params, context, logger }) => {
  try {
    const { projectId, playerId, accessToken } = context;
    // Initialize the necessary Unity API clients
    const cloudSaveAPI = new DataApi(context);
    const inventoryApi = new InventoryApi(context);
    const configurationApi = new ConfigurationApi(context);
    //fetch parameters
    var acceptedDecks = params.acceptedDecks;
    var acceptedLevels = params.acceptedLevels;
    //Get player saved hands and filter those whose cards are uneligible or have unavailable cards
    var responseRaw = await cloudSaveAPI.getItems(projectId, playerId, "Hands");
    var hands = responseRaw.data.results[0]?.value;
    if (hands === undefined) {
      hands = [];
    }
    var inventoryValidHandsWithMetada = await verifyAndMapHandCardsAgainstInventory(hands, configurationApi, inventoryApi, projectId, playerId, logger);
    var eligibleHands = [];

    if (acceptedDecks === undefined || acceptedDecks.length === 0 || acceptedLevels === undefined || acceptedLevels.length === 0) {
      eligibleHands = inventoryValidHandsWithMetada;
    } else {

    }

    return eligibleHands;

  } catch (e) {
    logger.error("failed to fetch cards: " + e);
  }
};

async function verifyAndMapHandCardsAgainstInventory(hands, configurationApi, inventoryApi, projectId, playerId, logger) {
  const inventoryApiGetPlayerInventoryRequest = { playerId: playerId, projectId: projectId, limit: 110 }
  const getPlayerConfigRequest = { playerId: playerId, projectId: projectId }
  //Get player inventory
  const playerInventory = await inventoryApi.getPlayerInventory(inventoryApiGetPlayerInventoryRequest);
  //Get player config and filter only for inventory items
  var config = await configurationApi.getPlayerConfiguration(getPlayerConfigRequest);
  const inventoryConfig = config.data.results.filter((configItem) => {
    return configItem.type === "INVENTORY_ITEM";
  });
  //map inventory items with their configuraion
  const itemsWithCustomData = playerInventory.data.results.map((inventoryItem) => {
    const inventoryItemConfig = inventoryConfig.find((configItem) => {
      return configItem.id === inventoryItem.inventoryItemId;
    })
    inventoryItem.customData = inventoryItemConfig.customData;
    return inventoryItem.customData;
  });
  //Filter hands that have cards that are not in the collection or are currently being sold
  logger.debug("Hands to verify: " + JSON.stringify(hands));
  verifiedHands = hands.filter((hand) => {
    var allCardsAreContained = true;
    logger.debug("filtering hand: " + JSON.stringify(hand.handCardsId));
    hand.handCardsId.forEach(cardId => {
      const foundItem = itemsWithCustomData.find((item) => {
        return item.id == cardId && !item.isBeingSold;
      })
      logger.debug("found item: " + JSON.stringify(foundItem));
      if (foundItem === undefined) {
        allCardsAreContained = false;
      }
    });
    return allCardsAreContained;
  });

  logger.debug("found cards: " + JSON.stringify(verifiedHands));
  return verifiedHands;
}

Hi @ANLevant Pagination is enforced; according to the Economy API specification the maximum no. of items your can retrieve in a single request is 100.

If you need to retrieve the next page, you will need to set the “after” value which is an optional property on the request Cloud Code Services SDKs. The value should be the last playersInventoryItemId within the list of results.

But if you’re looking up inventory IDs then I suggest using the inventoryItemIds property on the request as then the API will only return the inventory items if the player has them.

1 Like