> ## 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.

# Tables de butin et butin aléatoire dans Economy v2

> Créez des butins aléatoires pondérés dans PlayFab Economy v2 à l'aide de Title Data, d'Azure Functions et d'ExecuteInventoryOperations pour accorder des objets de façon atomique.

<Info>
  Economy v2 est maintenant en disponibilité générale. Pour obtenir du soutien et transmettre vos commentaires, rendez-vous sur le [forum PlayFab](https://community.playfab.com).
</Info>

Economy v1 (héritée) offrait une fonctionnalité intégrée de [tables de butin](/fr-CA/services/playfab/economy-monetization/economy/tutorials/drop-tables) (Drop Tables) qui vous permettait de définir des distributions aléatoires pondérées d'objets directement dans Game Manager. Economy v2 n'inclut pas les tables de butin comme fonctionnalité native, mais vous pouvez mettre en œuvre le même comportement, avec encore plus de souplesse, en combinant [Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview) et l'API [ExecuteInventoryOperations](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations).

Ce tutoriel vous montre comment créer un système complet de butin aléatoire qui :

* Définit des tables de butin avec des réserves d'objets pondérées dans [Title Data](/fr-CA/services/playfab/live-service-management/game-configuration/titledata).
* Utilise une fonction Azure pour tirer au hasard des objets d'une table de butin.
* Soustrait de façon atomique un jeton de conteneur et accorde les objets sélectionnés au hasard en un seul appel.

## Fonctionnement

1. **Définir les tables de butin dans Title Data** : stockez vos configurations de tables de butin (ID d'objets, poids, quantités) au format JSON dans Title Data afin de pouvoir les mettre à jour sans redéployer le code.
2. **Représenter les conteneurs sous forme de monnaie virtuelle** : utilisez le même [modèle de jeton de conteneur](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents) que pour les conteneurs à contenu fixe, c'est-à-dire une monnaie virtuelle qui fait office de coffre à butin non ouvert.
3. **Appeler une fonction Azure pour ouvrir le conteneur** : la fonction lit la table de butin, effectue une sélection aléatoire pondérée, puis appelle `ExecuteInventoryOperations` pour soustraire de façon atomique 1 jeton de conteneur et ajouter les objets sélectionnés au hasard.

## Prérequis

* Un [compte de développeur PlayFab](https://developer.playfab.com/en-us/sign-up).
* Un titre créé dans [Game Manager](https://developer.playfab.com/).
* Des objets de catalogue publiés que vous pouvez accorder comme récompenses de butin.
* Une monnaie virtuelle représentant le conteneur (consultez [Conteneurs à contenu fixe](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-1-create-a-virtual-currency-for-the-container) pour les instructions de configuration).
* Un projet [Azure Functions](/fr-CA/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart) connecté à votre titre PlayFab.

## Étape 1 : Définir les tables de butin dans Title Data

Stockez vos définitions de tables de butin dans [Title Data](/fr-CA/services/playfab/live-service-management/game-configuration/titledata) afin que les concepteurs puissent mettre à jour les taux de butin sans modifier le code.

### Exemple de structure de table de butin

Créez une clé Title Data appelée `LootTables` avec une valeur 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 }
    ]
  }
}
```

Chaque table de butin comprend les propriétés suivantes :

| Propriété | Description |
| - | - |
| **Rolls** | Nombre de tirages à effectuer dans la table à l'ouverture du conteneur. |
| **Items** | Réserve de récompenses possibles, chacune ayant un poids et une plage de quantités. |
| **Weight** | Probabilité relative. Plus le poids est élevé, plus l'objet a de chances d'être obtenu. |
| **MinQuantity / MaxQuantity** | Plage du nombre d'exemplaires de cet objet à accorder par tirage. |

### Définition de Title Data

#### Game Manager

1. Accédez à **Content** > **Title Data**.
2. Créez une clé appelée `LootTables`.
3. Collez votre configuration de table de butin JSON comme valeur.
4. Sélectionnez **Save**.

#### API

Utilisez [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\":[...]}}"
}
```

***

## Étape 2 : Créer la fonction Azure

Créez une fonction Azure que votre client de jeu appelle lorsqu'un joueur veut ouvrir un conteneur. La fonction :

1. Vérifie que le joueur possède au moins un jeton de conteneur.
2. Lit la table de butin dans Title Data.
3. Effectue une sélection aléatoire pondérée.
4. Appelle `ExecuteInventoryOperations` pour soustraire de façon atomique le jeton et accorder les récompenses.

### Modèles de données

```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; }
}
```

### Sélection aléatoire pondérée

```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();
}
```

### Fonction Azure complète

```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>
  La fonction d'assistance `GetContainerCurrencyId` associe un nom de table de butin à l'ID de monnaie de conteneur correspondant. Vous pouvez stocker cette correspondance dans Title Data avec les tables de butin, ou utiliser une convention d'affectation de noms.
</Note>

## Étape 3 : Accorder des jetons de conteneur aux joueurs

Accordez des jetons de conteneur aux joueurs au moyen du jeu, d'achats en magasin ou de récompenses. Utilisez la même approche que celle décrite dans [Conteneurs à contenu fixe](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-3-grant-container-tokens-to-players).

## Étape 4 : Appeler la fonction à partir de votre client de jeu

Lorsque le joueur veut ouvrir un conteneur, appelez la fonction Azure au moyen d'[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
}
```

La réponse contient les récompenses sélectionnées au hasard :

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

## Modèles de conception de tables de butin

### Niveaux de rareté

Le système de rareté classique avec des poids qui diminuent de façon exponentielle :

```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 }
  ]
}
```

Probabilités d'obtention :

* Commun : \~90,0 %
* Peu commun : \~9,0 %
* Rare : \~0,9 %
* Légendaire : \~0,09 %

### Tirages multiples avec récompenses mixtes

Une caisse de ravitaillement qui donne trois objets aléatoires tirés d'une réserve mixte :

```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 }
  ]
}
```

### Garanti + aléatoire (système de compensation)

Pour un modèle du type « objet rare ou mieux garanti toutes les 10 ouvertures », faites le suivi du nombre d'ouvertures du joueur dans les [propriétés d'affichage](/fr-CA/services/playfab/economy-monetization/economy-v2/inventory#display-properties) (Display Properties) ou les [données du joueur](/fr-CA/services/playfab/player-progression/player-data/quickstart) (Player Data), et remplacez la sélection aléatoire dans la fonction Azure lorsque le seuil est atteint.

### Tables de butin imbriquées

Une table de butin peut faire référence à une autre table de butin par son ID. Votre fonction Azure résout la référence de façon récursive :

```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 }
    ]
  }
}
```

La fonction vérifie si un `ItemId` commence par `TABLE:` et, le cas échéant, effectue un tirage dans la table référencée au lieu d'accorder l'objet directement.

## Considérations de sécurité

* **Exécutez toujours la logique de butin côté serveur** dans une fonction Azure. Ne faites jamais confiance au client pour déterminer quels objets ont été tirés. Cette approche empêche la manipulation.
* **Utilisez `IdempotencyId`** dans l'appel `ExecuteInventoryOperations` pour éviter les octrois en double si la fonction est relancée. Pour plus d'information, consultez [Transactions idempotentes et nouvelles tentatives](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries).
* **Validez la possession du conteneur** en vérifiant l'inventaire du joueur avant la soustraction. L'opération `Subtract` dans `ExecuteInventoryOperations` échoue si le joueur n'a pas assez de jetons, ce qui annule l'ensemble du lot.
* **Journalisez tous les tirages** à des fins d'audit. La valeur de retour de la fonction et les événements PlayStream fournissent une trace de ce que chaque joueur a reçu.

## Voir aussi

* [Conteneurs à contenu fixe](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents)
* [Référence de l'API ExecuteInventoryOperations](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations)
* [Transactions idempotentes et nouvelles tentatives](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries)
* [Title Data](/fr-CA/services/playfab/live-service-management/game-configuration/titledata)
* [Démarrage rapide d'Azure Functions](/fr-CA/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart)
* [Tables de butin (Economy héritée)](/fr-CA/services/playfab/economy-monetization/economy/tutorials/drop-tables)
* [Vue d'ensemble d'Economy v2](/fr-CA/services/playfab/economy-monetization/economy-v2/overview)


## Related topics

- [Tables de butin dans Economy (hérité)](/fr-CA/services/playfab/economy-monetization/economy/tutorials/drop-tables.md)
- [ETags et contrôle de la concurrence dans Economy v2](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/etags-and-concurrency-control.md)
- [Transactions idempotentes et modèles de nouvelle tentative dans Economy v2](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries.md)
- [Conteneurs à contenu fixe dans Economy v2](/fr-CA/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents.md)
- [Vue d'ensemble d'Economy v2](/fr-CA/services/playfab/economy-monetization/economy-v2/overview.md)
