> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tabelas de drop e saque aleatório no Economy v2

> Crie drops de saque aleatórios ponderados no PlayFab Economy v2 usando Title Data, Azure Functions e ExecuteInventoryOperations para conceder itens de forma atômica.

<Info>
  O Economy v2 agora está em Disponibilidade Geral. Para obter suporte e enviar comentários, acesse o [Fórum do PlayFab](https://community.playfab.com).
</Info>

O Economy v1 (Herdado) tinha um recurso integrado de [Tabelas de drop](/pt-BR/services/playfab/economy-monetization/economy/tutorials/drop-tables) que permitia definir distribuições aleatórias ponderadas de itens diretamente no Game Manager. O Economy v2 não inclui tabelas de drop como recurso nativo, mas você pode implementar o mesmo comportamento, e com mais flexibilidade, usando o [Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview) combinado com a API [ExecuteInventoryOperations](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations).

Este tutorial mostra como criar um sistema completo de saque aleatório que:

* Define tabelas de saque com conjuntos de itens ponderados no [Title Data](/pt-BR/services/playfab/live-service-management/game-configuration/titledata).
* Usa uma Azure Function para sortear itens aleatórios de uma tabela de saque.
* Subtrai atomicamente um token de contêiner e concede os itens selecionados aleatoriamente em uma única chamada.

## Como funciona

1. **Definir tabelas de saque no Title Data**: armazene as configurações das suas tabelas de drop (IDs de itens, pesos, quantidades) como JSON no Title Data para poder atualizá-las sem reimplantar código.
2. **Representar contêineres como moeda virtual**: use o mesmo [padrão de token de contêiner](/pt-BR/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents) dos contêineres com conteúdo fixo: uma moeda virtual que funciona como uma caixa de saque não aberta.
3. **Chamar uma Azure Function para abrir o contêiner**: a função lê a tabela de saque, executa a seleção aleatória ponderada e, em seguida, chama `ExecuteInventoryOperations` para subtrair atomicamente 1 token de contêiner e adicionar os itens selecionados aleatoriamente.

## Pré-requisitos

* Uma [conta de desenvolvedor do PlayFab](https://developer.playfab.com/en-us/sign-up).
* Um título criado no [Game Manager](https://developer.playfab.com/).
* Itens de catálogo publicados que você possa conceder como recompensas de saque.
* Uma moeda virtual que represente o contêiner (consulte [Contêineres com conteúdo fixo](/pt-BR/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-1-create-a-virtual-currency-for-the-container) para obter instruções de configuração).
* Um projeto do [Azure Functions](/pt-BR/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart) conectado ao seu título do PlayFab.

## Etapa 1: Definir tabelas de saque no Title Data

Armazene as definições das suas tabelas de saque no [Title Data](/pt-BR/services/playfab/live-service-management/game-configuration/titledata) para que os designers possam atualizar as taxas de drop sem alterações de código.

### Exemplo de estrutura de tabela de saque

Crie uma chave do Title Data chamada `LootTables` com um valor JSON:

```json theme={null}
{
  "TreasureChest": {
    "Rolls": 1,
    "Items": [
      { "ItemId": "{{CommonSwordID}}", "Weight": 1000, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{UncommonSwordID}}", "Weight": 100, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{RareSwordID}}", "Weight": 10, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{LegendarySwordID}}", "Weight": 1, "MinQuantity": 1, "MaxQuantity": 1 }
    ]
  },
  "SupplyCrate": {
    "Rolls": 3,
    "Items": [
      { "ItemId": "{{HealthPotionID}}", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 5 },
      { "ItemId": "{{ManaPotionID}}", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 3 },
      { "ItemId": "{{GoldCurrencyID}}", "Weight": 80, "MinQuantity": 10, "MaxQuantity": 100 },
      { "ItemId": "{{RareGemID}}", "Weight": 5, "MinQuantity": 1, "MaxQuantity": 1 }
    ]
  }
}
```

Cada tabela de saque inclui as seguintes propriedades:

| Propriedade | Descrição |
| - | - |
| **Rolls** | Quantas vezes sortear na tabela quando o contêiner é aberto. |
| **Items** | O conjunto de recompensas possíveis, cada uma com um peso e um intervalo de quantidade. |
| **Weight** | Probabilidade relativa. Um peso maior significa que o item tem mais chance de cair. |
| **MinQuantity / MaxQuantity** | Intervalo de quantas unidades desse item conceder por sorteio. |

### Definindo o Title Data

#### Game Manager

1. Vá para **Content** > **Title Data**.
2. Crie uma chave chamada `LootTables`.
3. Cole a configuração JSON da sua tabela de saque como valor.
4. Selecione **Save**.

#### API

Use [SetTitleData](https://learn.microsoft.com/en-us/rest/api/playfab/server/title-wide-data-management/set-title-data):

```json theme={null}
{
  "Key": "LootTables",
  "Value": "{\"TreasureChest\":{\"Rolls\":1,\"Items\":[...]}}"
}
```

***

## Etapa 2: Criar a Azure Function

Crie uma Azure Function que o cliente do seu jogo chame quando um jogador quiser abrir um contêiner. A função:

1. Verifica se o jogador tem pelo menos um token de contêiner.
2. Lê a tabela de saque do Title Data.
3. Executa a seleção aleatória ponderada.
4. Chama `ExecuteInventoryOperations` para subtrair atomicamente o token e conceder as recompensas.

### Modelos de dados

```csharp theme={null}
public class OpenContainerRequest
{
    public string LootTableId { get; set; }  // e.g., "TreasureChest"
}

public class LootTable
{
    public int Rolls { get; set; }
    public List<LootTableEntry> Items { get; set; }
}

public class LootTableEntry
{
    public string ItemId { get; set; }
    public int Weight { get; set; }
    public int MinQuantity { get; set; }
    public int MaxQuantity { get; set; }
}
```

### Seleção aleatória ponderada

```csharp theme={null}
public static LootTableEntry RollOnTable(LootTable table, Random rng)
{
    int totalWeight = table.Items.Sum(e => e.Weight);
    int roll = rng.Next(0, totalWeight);

    int cumulative = 0;
    foreach (var entry in table.Items)
    {
        cumulative += entry.Weight;
        if (roll < cumulative)
            return entry;
    }

    // Fallback (shouldn't reach here)
    return table.Items.Last();
}
```

### Azure Function completa

```csharp theme={null}
[FunctionName("OpenLootContainer")]
public static async Task<dynamic> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    // 1. Parse the PlayFab context and request
    var context = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(
        await req.ReadAsStringAsync());
    var args = context.FunctionArgument != null
        ? JsonConvert.DeserializeObject<OpenContainerRequest>(
            context.FunctionArgument.ToString())
        : null;

    if (args == null || string.IsNullOrEmpty(args.LootTableId))
        return new { Error = "LootTableId is required." };

    var playerEntity = context.CallerEntityProfile.Entity;
    var settings = new PlayFabApiSettings
    {
        TitleId = context.TitleAuthenticationContext.Id,
        DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY")
    };
    var authContext = new PlayFabAuthenticationContext
    {
        EntityToken = context.TitleAuthenticationContext.EntityToken
    };

    var serverApi = new PlayFabServerInstanceAPI(settings);
    var economyApi = new PlayFabEconomyInstanceAPI(settings, authContext);

    // 2. Read the loot table from Title Data
    var titleDataResult = await serverApi.GetTitleDataAsync(new GetTitleDataRequest
    {
        Keys = new List<string> { "LootTables" }
    });

    if (!titleDataResult.Result.Data.ContainsKey("LootTables"))
        return new { Error = "LootTables not found in Title Data." };

    var allTables = JsonConvert.DeserializeObject<Dictionary<string, LootTable>>(
        titleDataResult.Result.Data["LootTables"]);

    if (!allTables.ContainsKey(args.LootTableId))
        return new { Error = $"Loot table '{args.LootTableId}' not found." };

    var lootTable = allTables[args.LootTableId];

    // 3. Roll for rewards
    var rng = new Random();
    var rewards = new Dictionary<string, int>(); // ItemId -> total quantity

    for (int i = 0; i < lootTable.Rolls; i++)
    {
        var entry = RollOnTable(lootTable, rng);
        int quantity = rng.Next(entry.MinQuantity, entry.MaxQuantity + 1);

        if (rewards.ContainsKey(entry.ItemId))
            rewards[entry.ItemId] += quantity;
        else
            rewards[entry.ItemId] = quantity;
    }

    // 4. Build the atomic operation: subtract 1 container token + add rewards
    var operations = new List<InventoryOperation>();

    // Subtract the container token
    // The container currency ID must match the LootTableId mapping
    // Store this mapping in Title Data or use a naming convention
    operations.Add(new InventoryOperation
    {
        Subtract = new SubtractInventoryItemsOperation
        {
            Item = new InventoryItemReference
            {
                Id = GetContainerCurrencyId(args.LootTableId) // Your mapping logic
            },
            Amount = 1
        }
    });

    // Add each reward
    foreach (var reward in rewards)
    {
        operations.Add(new InventoryOperation
        {
            Add = new AddInventoryItemsOperation
            {
                Item = new InventoryItemReference { Id = reward.Key },
                Amount = reward.Value
            }
        });
    }

    // 5. Execute atomically
    var executeResult = await economyApi.ExecuteInventoryOperationsAsync(
        new ExecuteInventoryOperationsRequest
        {
            Entity = new EntityKey
            {
                Id = playerEntity.Id,
                Type = playerEntity.Type
            },
            Operations = operations,
            IdempotencyId = Guid.NewGuid().ToString()
        });

    if (executeResult.Error != null)
    {
        log.LogError($"Failed to execute operations: {executeResult.Error.ErrorMessage}");
        return new { Error = executeResult.Error.ErrorMessage };
    }

    log.LogInformation($"Player {playerEntity.Id} opened {args.LootTableId}, received: " +
        string.Join(", ", rewards.Select(r => $"{r.Value}x {r.Key}")));

    return new { Rewards = rewards, TransactionIds = executeResult.Result.TransactionIds };
}
```

<Note>
  A função auxiliar `GetContainerCurrencyId` mapeia o nome de uma tabela de saque para o ID da moeda de contêiner correspondente. Você pode armazenar esse mapeamento no Title Data junto com as tabelas de saque ou usar uma convenção de nomenclatura.
</Note>

## Etapa 3: Conceder tokens de contêiner aos jogadores

Conceda tokens de contêiner aos jogadores por meio da jogabilidade, de compras na loja ou de recompensas. Use a mesma abordagem descrita em [Contêineres com conteúdo fixo](/pt-BR/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-3-grant-container-tokens-to-players).

## Etapa 4: Chamar a função do cliente do jogo

Quando o jogador quiser abrir um contêiner, chame a Azure Function usando [ExecuteFunction](https://learn.microsoft.com/en-us/rest/api/playfab/cloudscript/server-side-cloud-script/execute-function):

```json theme={null}
{
    "FunctionName": "OpenLootContainer",
    "FunctionParameter": {
        "LootTableId": "TreasureChest"
    },
    "GeneratePlayStreamEvent": true
}
```

A resposta contém as recompensas selecionadas aleatoriamente:

```json theme={null}
{
    "FunctionResult": {
        "Rewards": {
            "{{UncommonSwordID}}": 1
        },
        "TransactionIds": ["txn-abc-123"]
    }
}
```

## Padrões de design de tabelas de saque

### Níveis de raridade

O sistema clássico de raridade com pesos que diminuem exponencialmente:

```json theme={null}
{
  "Items": [
    { "ItemId": "CommonItem",     "Weight": 1000, "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "UncommonItem",   "Weight": 100,  "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "RareItem",       "Weight": 10,   "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "LegendaryItem",  "Weight": 1,    "MinQuantity": 1, "MaxQuantity": 1 }
  ]
}
```

Probabilidades de drop:

* Comum: \~90,0%
* Incomum: \~9,0%
* Raro: \~0,9%
* Lendário: \~0,09%

### Vários sorteios com recompensas mistas

Uma caixa de suprimentos que concede três itens aleatórios de um conjunto misto:

```json theme={null}
{
  "Rolls": 3,
  "Items": [
    { "ItemId": "{{HealthPotionID}}", "Weight": 40, "MinQuantity": 1, "MaxQuantity": 3 },
    { "ItemId": "{{ManaPotionID}}",   "Weight": 40, "MinQuantity": 1, "MaxQuantity": 3 },
    { "ItemId": "{{GoldCurrencyID}}", "Weight": 30, "MinQuantity": 10, "MaxQuantity": 50 },
    { "ItemId": "{{RareGemID}}",      "Weight": 5,  "MinQuantity": 1, "MaxQuantity": 1 }
  ]
}
```

### Garantido + aleatório (sistema de piedade)

Para um padrão de "raro ou melhor garantido a cada 10 aberturas", acompanhe a contagem de aberturas do jogador nas [Propriedades de Exibição](/pt-BR/services/playfab/economy-monetization/economy-v2/inventory#display-properties) ou nos [Dados do Jogador](/pt-BR/services/playfab/player-progression/player-data/quickstart) e substitua a seleção aleatória na Azure Function quando o limite for atingido.

### Tabelas de drop aninhadas

Uma tabela de saque pode fazer referência a outra tabela de saque por ID. Sua Azure Function resolve a referência recursivamente:

```json theme={null}
{
  "BossDropTable": {
    "Rolls": 2,
    "Items": [
      { "ItemId": "TABLE:WeaponDropTable", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "TABLE:ArmorDropTable",  "Weight": 30, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{GoldCurrencyID}}",    "Weight": 80, "MinQuantity": 50, "MaxQuantity": 200 }
    ]
  }
}
```

A função verifica se um `ItemId` começa com `TABLE:` e, em caso afirmativo, sorteia na tabela referenciada em vez de conceder o item diretamente.

## Considerações de segurança

* **Sempre execute a lógica de saque no lado do servidor** em uma Azure Function. Nunca confie no cliente para determinar quais itens foram sorteados. Essa abordagem evita manipulação.
* **Use `IdempotencyId`** na chamada `ExecuteInventoryOperations` para evitar concessões duplicadas se a função for repetida. Para obter mais informações, consulte [Transações idempotentes e novas tentativas](/pt-BR/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries).
* **Valide a posse do contêiner** verificando o inventário do jogador antes de subtrair. A operação `Subtract` em `ExecuteInventoryOperations` falha se o jogador não tiver tokens suficientes, o que reverte todo o lote.
* **Registre todos os sorteios** para auditoria. O valor retornado pela função e os eventos do PlayStream fornecem um histórico do que cada jogador recebeu.

## Consulte também

* [Contêineres com conteúdo fixo](/pt-BR/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents)
* [Referência da API ExecuteInventoryOperations](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations)
* [Transações idempotentes e novas tentativas](/pt-BR/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries)
* [Title Data](/pt-BR/services/playfab/live-service-management/game-configuration/titledata)
* [Início rápido do Azure Functions](/pt-BR/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart)
* [Tabelas de drop (Economy herdado)](/pt-BR/services/playfab/economy-monetization/economy/tutorials/drop-tables)
* [Visão geral do Economy v2](/pt-BR/services/playfab/economy-monetization/economy-v2/overview)


## Related topics

- [Tabelas de drop no Economy (Legacy)](/pt-BR/services/playfab/economy-monetization/economy/tutorials/drop-tables.md)
- [Contêineres com conteúdo fixo no Economy v2](/pt-BR/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents.md)
- [Visão geral do Economy v2](/pt-BR/services/playfab/economy-monetization/economy-v2/overview.md)
- [Atualizar tabelas de resultados aleatórios](/pt-BR/services/playfab/api-references/rest/admin/title-wide-data-management/update-random-result-tables.md)
- [Obter tabelas de resultados aleatórios](/pt-BR/services/playfab/api-references/rest/admin/title-wide-data-management/get-random-result-tables.md)
