Loot Drop and Gacha Systems

Introduction

Loot-Drop and Gacha systems are used widely in games to provide players with an incentive to return to the game regularly and to boost player retention. They can be used as daily bonuses/crates or during certain events, or they can be used in-game to generate random goods as part of a crate or chest, thereby letting the server deliver goods and prevent hacking or cheating client-side.

In this tutorial, we'll look at how GameSparks can be used to create these systems and how items in these loot drops can be added and managed by game-designers so that the drops can be balanced within the game.

This tutorial will involve two parts:

  1. Management Screen. In order to set up the loot-drop table we’ll need some sort of back-office management screen so that designers can add items into a loot-table and set the chances of those items being dropped. We'll use GameSparks’ dynamic forms to create these screens.
  2. Loot Drop System. This will involve some simple cloud-code and custom events so that the player can call the loot-drop from the client and the system can deliver goods based on the loot-drop tables created above.

How it Works

In general, loot-drop systems are very simple. They involve a table/list of in-game items that the player can obtain from the loot-drop. Each of these items is usually given a weight/probability/rarity value in that table also. When the player calls the loot-drop, we essentially generate a random number and the result will determine the rarity of the item we are delivering. We can then pick a random item of that rarity from the table and deliver that item to the player.

There are a lot of other configurations that are commonly used for loot-drops systems like paid loot-drops or timed loot-drops - for example, one free chest per day. However, in this tutorial we'll just deal with the basic idea and the manage screens that are needed to set it up.

Manage Screens Introduction

There's a lot of information on the GameSparks Learn site on how to create your own manage screens using our dynamic forms API. In this tutorial we are go to go step-by-step through setting these manage screens up, but if you want to know more about our dynamic forms, see the documentation here and here.

The first thing we need to do is create a new screen.

1. To do this, click on the Manage tab on the left-hand side of the portal. Select Admin Screens and then the Add button:

We're going to give the new screen a name (something like ‘Loot Table Manager’) and a shortCode.

2. Inside the GSML window (see image below), we are going to add some code. This code is going to set up a placeholder for our screen to be drawn in. We are then we are going to call a snippet. It is important to remember the name of this placeholder because when we update or re-draw the screen after any change, we'll reference this placeholder so it re-draws in the same place:

Here's the code we've added:

<gs-placeholder id="loot_tables_ph">
    <gs-snippet snippet="loot_tables_view?action=view"></gs-snippet>
</gs-placeholder>

You’ll notice in the code above that we also pass an action ‘view’ in this snippet too. This action field will be used later to we can save or delete items in the loot-table and the code knows what to do from which action we pass in. But when we want to draw the screen first, we just want to pass in the ‘view’ action.

Now, this snippet we are calling from the screen doesn’t exist yet, so we’ll need to save this screen and create the snippet.

3. Click to Save and Close the new screen.

4. We now need to create a new snippet.

To create a new snippet, click the Snippets tab of the Admin Screens page, and click on the Add button:

In here, we need to give the snippet a Name and a shortCode. The Name is not very important, but for this tutorial the shortCode must be loot_tables_view because we'll reference this shortCode anytime we want our screen to perform an action.

5. Click to Save and Close the new snippet.

Dynamic Forms Tour

This is a good point in the tutorial to explore what we are looking at here in the dynamics form's snippet. You can see that it is divided into five windows, where each window has a small text label which shows what the window is used for. This is similar to any other web-dev IDE you may have used, only in this case it is condensed into one screen:

1. ScriptData - This is an example of the data we want to pass into this screen. When the screen is up and running, you will be passing data from snippets. So, this is only used for testing. In this example, we are going to pass the following in:

{ "action" : "view" }

2. JavaScript - In this window we add some JavaScript we want to run on the data being passed in through ScriptData. At the end of this script we'll return some data, which will be passed along for the HTML to render. We’ll see this later. We'll put some code in here now, so we can catch the data coming in and use it. We are also going to log the data coming into the snippet. This is very useful for debugging, because you can check out if your data are correct by looking at the script.log collection in the NoSQL Explorer:

Spark.setScriptData("form", SnippetProcessor(Spark.getData().scriptData))
function SnippetProcessor(data){

    var form = {};
    Spark.getLog().debug(data)
    var action = data.action;

    switch(action)
    {
        case "view":
            // we'll put in some code here later
            break;
    }

    return form;
}


3. Response JSON - The data we return from our function the JavaScript window will be visible here. We don’t edit this information so there is no need to add any code here.

4. Handlebars - In this window we can use some HTML and handlebars to display information from the response JSON window. You can also add some JQuery to this window to have code run on the HTML displayed here, but we won’t need that for this tutorial. For the moment, we are just going to add some simple HTML to create a title-bar for this screen:

<gs-title-block title="Loot Table" padding="10" margin="0">
    <gs-row>

    </gs-row>
</gs-title-block>

5. Rendered HTML - In this window, we can see what the output of the snippet will be. We can render the HTML by clicking on the play-button of the JavaScript window. You can see this in the image below:

Adding Items to the Table

We're going to start by adding items into this loot-table. Later, we'll create a list so we can see what we have and remove items if we want to. But before we can do that we need to be able to add items.

For this tutorial, we're going to use the out-of-the-box GameSparks Virtual Goods for the items we want to add to the table. So, in our JavaScript window, we're going to get a list of items so we can create a drop-down menu where we can select items to add. We're also going to add a new action to the switch-case called addItem where we will insert a new item into the database.

Note: You'll need to add Virtual Goods to your own game in order for them to show up after we’ve put the code in here.

So, in the JavaScript window we add the following code:

Spark.setScriptData("form", SnippetProcessor(Spark.getData().scriptData))
function SnippetProcessor(data){

    Spark.getLog().debug(data)
    var form = { };
    var action = data.action;
    // get a list of all virtual goods currently configured in the game //
    form.vgList = Spark.getConfig().getVirtualGoods();


    switch(action)
    {
        case "view":
            // we'll put in some code here later
            break;
        case "addItem":

            Spark.metaCollection("lootTable").insert({
                "shortCode" : data.newItem_shortCode,
                "amount" : parseInt(data.newItem_amount),
                "rarity" : parseInt(data.newItem_rarity),
                "type" : "item"
            });            
break;
    }

    return form;
}


What's happening in this code above is that we're adding the Virtual Goods list to the form (JSON object) we created. You can see that this form is being returned from the function (at the bottom of the script). So, anything we add to this form can be drawn in HTML.

We also insert a new item into the collection using the insert function. This is straightforward - we just give the function the fields we want to store, which in this case are contained in the ‘data’ object passed into the function.

Rendering the 'Add Item' Form

In the HandleBars window, we're going to create a simple form with a few fields for the item’s rarity and amount, and also a drop-down selector, which will set the item’s shortCode from the Virtual Good’s name:

<gs-title-block title="Loot Table" padding="10" margin="0">
    <gs-row>
        <gs-row>
            <gs-col width = "3"><h5>Item Name</h5></gs-col>
            <gs-col width = "3"><h5>Amount</h5></gs-col>
            <gs-col width = "3"><h5>Rarity</h5></gs-col>
            <gs-col width = "2"></gs-col>
        </gs-row>
        <gs-row>
            <gs-form snippet="loot_tables_view?action=addItem" target="loot_tables_ph">
                <gs-col width = "3">
                    <select name="newItem_shortCode" class="input-block-level" style="margin-bottom:5px">
                        {{#each form.vgList}}
                            <option value="{{shortCode}}" selected>{{name}}</option>
                        {{/each}}
                    </select>
                </gs-col>
                <gs-col width = "3"><input type='number'required min="1" step="1" value=1 name='newItem_amount' placeholder=1 /></gs-col>
                <gs-col width = "3"><input type='number'required min="1" step="1"  value=1 name='newItem_rarity' placeholder=1 /></gs-col>
                <gs-col width = "3"><gs-submit>Add Item</gs-submit></gs-col>
            </gs-form>
        </gs-row>
    </gs-row>
</gs-title-block>


If you save this snippet and go back to the manage screen, you'll be able to see this form and add new items to the table:

So, now, back in the snippet, we'll draw our list of items in the table so we can see what we’ve just added.

Loot-Table List

As well as drawing the loot-table list, we also want to add some extra functionality:

We'll start by drawing this list first and breaking down what each part of the HTML is doing:

  1. First, we are going to draw a title row, similar to how we did for adding new items. This is just a new row with columns set up for each piece of information we want to display: item-name, amount, rarity, update, and delete.
  2. Under this, we're going to add a little section where we'll place a notification when the user clicks on an item to update, confirming the item has been updated.
  3. We'll use the {{#each}} function in Handlebars to loop over a list of items. We'll draw the details of each items on a separate row, so that they appear one after the other.
  4. We need a form, so we can update items individually. This will be the first line we put inside these rows.
  5. Then we'll draw input fields, as we did when we created the form for adding an item. The HTML will be identical with the exception of the drop-down selector, which will contain a {{#compare}} function. This compare function will allow us to check the shortCode of the item we are drawing but, instead of showing the shortCode, we'll be able to select items by name.
  6. Finally, we'll add a delete icon to each item in the table. This will call the snippet we are in, but we’ll pass the action deleteItem along with the item’s ID, so we can run some code on the DB to remove that item.

Most of the HTML and handlebars are easy to figure out by looking at the code. The only part that is worth close attention is the drop-down selector:

<select name="itemShortCode" class="input-block-level" style="margin-bottom:5px">
                        {{#each ../form.vgList}}
                            <option value="{{shortCode}}" {{#compare shortCode "===" ../shortCode}}selected{{/compare}}>{{name}} </option>
                        {{/each}}
                    </select>

What this code is doing is comparing an item’s shortCode in the vgList with the shortCode of the item in the table, and getting the associated name of that item.

We're already iterating through the table, so in order to reference the vgList we need to go back one level in the form. This is why you’ll see ../ in the code. This is going back up one level in the form (out of the array of items in the table) in order to get the vgList array.

So, the code in the Handlebars window now looks as follows:

<gs-title-block title="Loot Table" padding="10" margin="0">
    <gs-row>
        <gs-row>
            <gs-col width = "3"><h5>Item Name</h5></gs-col>
            <gs-col width = "3"><h5>Amount</h5></gs-col>
            <gs-col width = "3"><h5>Rarity</h5></gs-col>
            <gs-col width = "2"></gs-col>
        </gs-row>
        <gs-row>
            <gs-form snippet="loot_tables_view?action=addItem" target="loot_tables_ph">
                <gs-col width = "3">
                    <select name="newItem_shortCode" class="input-block-level" style="margin-bottom:5px">
                        {{#each form.vgList}}
                            <option value="{{shortCode}}" selected>{{name}}</option>
                        {{/each}}
                    </select>
                </gs-col>
                <gs-col width = "3"><input type='number'required min="1" step="1" value=1 name='newItem_amount' placeholder=1 /></gs-col>
                <gs-col width = "3"><input type='number'required min="1" step="1"  value=1 name='newItem_rarity' placeholder=1 /></gs-col>
                <gs-col width = "3"><gs-submit>Add Item</gs-submit></gs-col>
            </gs-form>
        </gs-row>
    </gs-row>
        <gs-title-block title="Table" padding="10" margin="0">
          <!-- SECTION 1 - DRAW TITLE -->
            <gs-row>
                <gs-col width="3"><h5>Item Name</h5></gs-col>
                <gs-col width="3"><h5>Amount</h5></gs-col>
                <gs-col width="3"><h5>Rarity</h5></gs-col>
                <gs-col width="1"><h5>Update</h5></gs-col>
                <gs-col width="2"><h5>Delete</h5></gs-col>
            </gs-row><hr><br>
          <!-- SECTION 2 - DRAW POPUP FOR SAVE NOTIFICATIONS -->
            <gs-row>
                {{#if form.saved}}
                <gs-col width="12">
                    <gs-alert type="success" message="Saved!"></gs-alert>
                </gs-col>
                {{/if}}
            </gs-row>    
          <!-- SECTION 3 - DRAW TABLE LIST -->
            {{#each form.table}}
            <gs-row>
          <!-- SECTION 4 - CREATE FORM FOR UPDATING INDIVIDUAL ITEMS -->
                <gs-form snippet="loot_tables_view?action=updateItem&id={{_id.$oid}}" target="loot_tables_ph">
                <gs-col width = "3">
                    <select name="itemShortCode" class="input-block-level" style="margin-bottom:5px">
                        {{#each ../form.vgList}}
                            <option value="{{shortCode}}" {{#compare shortCode "===" ../shortCode}}selected{{/compare}}>{{name}} </option>
                        {{/each}}
                    </select>
                </gs-col>
                <gs-col width = "3"><input type='number' min="1" step="1" value="{{amount}}" name='itemAmount'/></gs-col>
                <gs-col width = "3"><input type='number' min="1" step="1" value="{{rarity}}" name='itemRarity'/></gs-col>
                <gs-col width = "1"><gs-submit><i  style="font-size: 2em;" data-toggle="tooltip" data-placement="top" title="Update Item" class="icon-edit"/></gs-submit></gs-col>
        <!-- SECTION 5 - ICON WILL CALL THE DELETE ACTION ON THIS ITEM -->
                <gs-col width = "2">
                    <gs-link snippet="loot_tables_view?action=deleteItem&id={{_id.$oid}}" target="loot_tables_ph"><i  style="font-size: 2em;" data-toggle="tooltip" data-placement="top" title="Delete Loot Item" class="icon-trash"/></gs-link>
                </gs-col>
                </gs-form>
            </gs-row>
            {{/each}}
</gs-title-block>


Now we need to add some corresponding code to the JavaScript window so we can update and delete items.

This code has the following features:

  1. We need to add a function to draw the table-list. This needs to be done even when we are saving or deleting and item because we want to re-draw the table after we’ve done one of those operations.
  2. Updating an item will use the MongoDB function update, which allows us to find a document by its OID, and then update specific fields.
  3. Deleting an item will just find a document by its OID and then delete it.

In the cases mentioned here where we need an OID, you can see in the HTML text above:

At SECTION 4:

<gs-form snippet="loot_tables_view?action=updateItem&id={{_id.$oid}}" target="loot_tables_ph">

And at SECTION 5:

<gs-link snippet="loot_tables_view?action=deleteItem&id={{_id.$oid}}" target="loot_tables_ph">

In both cases, the OIDs (in red) are passed into the snippet when we update and delete an item.

Spark.setScriptData("form", SnippetProcessor(Spark.getData().scriptData))
function SnippetProcessor(data){

    Spark.getLog().debug(data)
    var form = { };
    var action = data.action;
    // get a list of all virtual goods currently configured in the game //
    form.vgList = Spark.getConfig().getVirtualGoods();


    switch(action)
    {
        case "view":
            // we'll put in some code here later
            viewTable();
            break;
        case "addItem":
            Spark.metaCollection("lootTable").insert({
                "shortCode" : data.newItem_shortCode,
                "amount" : parseInt(data.newItem_amount),
                "rarity" : parseInt(data.newItem_rarity),
                "type" : "item"
            });
            viewTable();
            break;
        case "updateItem":
            // find and update the item based on its OID //
            Spark.metaCollection("lootTable").update({ "_id" : { "$oid" : data.id } },{
                $set : {
                    "shortCode" : data.itemShortCode,
                    "amount" : parseInt(data.itemAmount),
                    "rarity" : parseInt(data.itemRarity)
                }
            })

            form.saved = true;
            viewTable();
            break;
        case "deleteItem":
            // remove the table entry with the ID //
            Spark.metaCollection("lootTable").findAndRemove({ "_id" : { "$oid" : data.id } })
            viewTable();
            break;
    }

    function viewTable()
    {
        var table = Spark.metaCollection("lootTable").find({ "type" : "item" });
        form.table = table;
    }   



    return form;
}

Setting Rarity Values

Now there is only one more thing we need to add to this management screen, but it's important because we cannot do an accurate loot-drop without it. When we set the rarity value of each item in the table, we need to know what probability (% chance drop) that rarity value represents.

Players will want to know that your loot-table drops accurately, especially if you are running events or live-ops telling players there is a certain chance of an item being dropped.

We need to add another form which will list the corresponding percentage drop chance for each item’s rarity. For example, rarity 1 might mean that you are guaranteed those items drop 70% of the time (rarity 1 = 70), while rarity 2 has a 20% chance to drop (rarity 2 = 20), and rarity 3 has only a 10% chance to drop (rarity 3 = 10). These should all add up to 100% so that when the player preforms a drop, we can get a random number between 1 and 100, and from that we can check what rarity item the player got, and the probability of the drop is the same each time.

The form for this will be pretty simple. We’ll start by drawing the form we can use for adding new rarity values. We can put this at the very bottom of the screen because it won’t be used too often:

<gs-title-block title="Rarity Values" padding="10" margin="0">
        <gs-row>
            <gs-row>
                <gs-col width = "3"><h5>Rarity Value</h5></gs-col>
                <gs-col width = "3"><h5>Percent (1-100)</h5></gs-col>
                <gs-col width = "2"></gs-col>
            </gs-row>
            <gs-row>
                <gs-form snippet="loot_tables_view?action=addRarityValue" target="loot_tables_ph">
                    <gs-col width = "3"><input type='number'required min="1" step="1" value=1 name='newRar_value' placeholder=1 /></gs-col>
                    <gs-col width = "3"><input type='number'required min="1" step="1"  value=1 name='newRar_percent' placeholder=1 /></gs-col>
                    <gs-col width = "3"><gs-submit>Add Rarity Value</gs-submit></gs-col>
                </gs-form>
            </gs-row>
        </gs-row>
    </gs-title-block>


While we are working on this HTML we can also add the list of current rarity values in (we’ll add the code later to create them). This HTML will be very similar to what we did for the loot-table values where we created an update and delete button for them. The idea is the same here:

  <!-- SECTION 1 - DRAW TITLE -->
  <gs-row>
    <gs-col width="3"><h5>Rarity</h5></gs-col>
    <gs-col width="3"><h5>Percent</h5></gs-col>
    <gs-col width="3"><h5>Update</h5></gs-col>
    <gs-col width="3"><h5>Delete</h5></gs-col>
  </gs-row><hr><br>
  <!-- SECTION 2 - DRAW POPUP FOR SAVE NOTIFICATIONS -->
  <gs-row>
    {{#if form.rarSaved}}
    <gs-col width="12">
        <gs-alert type="success" message="Rarity Value Saved!"></gs-alert>
    </gs-col>
    {{/if}}
  </gs-row>
  <!-- SECTION 3 - DRAW TABLE LIST -->
  {{#each form.rarityValues}}
  <gs-row>
  <!-- SECTION 4 - CREATE FORM FOR UPDATING INDIVIDUAL ITEMS -->
    <gs-form snippet="loot_tables_view?action=updateRar&id={{_id.$oid}}" target="loot_tables_ph">
    <gs-col width = "3"><input type='number' min="1" step="1" value="{{rarityValue}}" name='rarityValue'/></gs-col>
    <gs-col width = "3"><input type='number' min="1" step="1" value="{{percentage}}" name='percentage'/></gs-col>
    <gs-col width = "3"><gs-submit><i  style="font-size: 2em;" data-toggle="tooltip" data-placement="top" title="Update Rarity Value" class="icon-edit"/></gs-submit></gs-col>
  <!-- SECTION 5 - ICON WILL CALL THE DELETE ACTION ON THIS ITEM -->
  <gs-col width = "3">
    <gs-link snippet="loot_tables_view?action=deleteRar&id={{_id.$oid}}" target="loot_tables_ph"><i  style="font-size: 2em;" data-toggle="tooltip" data-placement="top" title="Delete Rarity Value" class="icon-trash"/></gs-link>
    </gs-col>
    </gs-form>
  </gs-row>
  {{/each}}

Your manage screen should now look something like this:

Now we're going to add a few things to the JavaScript so that we can draw, add, update, and delete these rarity values.

They'll work the same as the table items above, except these will stored as type = “rarity”. This allows us to use the same collection for our items and our rarities.

function viewTable()
{
    var table = Spark.metaCollection("lootTable").find({ "type" : "item" });
    form.table = table;
    var rarities = Spark.metaCollection("lootTable").find({ "type" : "rarityValue" });
    form.rarityValues = rarities;
}


case "addRarityValue":
            Spark.metaCollection("lootTable").insert({
                "percentage" : parseInt(data.newRar_percent),
                "rarityValue" : parseInt(data.newRar_value),
                "type" : "rarityValue"
            });
            viewTable();
            break;
        case "updateRar":
            // find and update the item based on its OID //
            Spark.metaCollection("lootTable").update({ "_id" : { "$oid" : data.id } },{
                $set : {
                    "rarityValue" : parseInt(data.rarityValue),
                    "percentage" : parseInt(data.percentage)
                }
            })

            form.rarSaved = true;
            viewTable();
            break;
        case "deleteRar":
             // remove the table entry with the ID //
            Spark.metaCollection("lootTable").findAndRemove({ "_id" : { "$oid" : data.id } })
            viewTable();
            break;


Now you should be able to add, update, and delete rarity values:

Note: In order for this loot system to work correctly, you must have rarity values or it won’t be possible to perform a loot-drop. So, make sure you have a few saved before we move onto the next part of the tutorial.

Loot-Drop Request

Now that we have the manage screen ready, we need to create the request which allows the player to perform the loot-drop.

1. To do this go to the Configurator tab of the portal and select Events. Click on the Add button to create a new Event:

2. We're going to call this lootDrop and add an attribute called amount, which we'll use so we can choose drops with multiple items at once instead of calling the request multiple times for multiple drops.

So, this request will perform the following steps:

  1. We need to roll a random number from 0-99 so we can get the rarity of the item that will drop.
  2. Once we have the rarity-roll, we'll check which rarity value is closest to that random number.
  3. We'll then get the total number of items in the loot-table which have that rarity value and generate a random number between zero and that total (minus 1 so we are in range of the array).
  4. If the total number of items equals zero, then there's a problem with the loot-table and the script will crash in the next step. So, if this happens, we stop the script and return an error.
  5. We then query the loot-table using that random number to select an item with the right rarity value from the loot table.
  6. We deliver the player that Virtual Good for the amount specified in the table.
  7. We add the drop-details (Virtual Good shortCode, amount, and some details of the loot-drop for logging purposes) to an array.
  8. We send this array back to the client so they know what items have been added.

Most of this is straightforward. There are only two aspects to note:

We then check if our rolled percentage is less than the percentage for the current rarity:

Another important thing to note here is that we'll return an error and stop the script if we ever get to a point where we can’t find a rarity value, because otherwise the script will crash in the next step.

3. The following code goes into the lootDrop cloud-code script:

var amount = Spark.getData().amount;
var lootDrop = [];

// if there is more than one item we want to drop, we use this while loop to keep //
// dropping items until we have reached the amount //
while(lootDrop.length < amount)
{
    var rarityPercent = getRandomInt(0, 99); // the rarity roll is between 1 and 100
    // now that we have the rarity-percentage, we need to get the rarityValue from the DB //
    var rarity = getClosestRarity(rarityPercent);
    // Now we know which rarity we are getting we are going to check the number of items with that rarity in the table //
    var totalItems = Spark.metaCollection("lootTable").count({ "rarity" : rarity.rarityValue });
    // it's really important to check that there are actually items with this rarity value in the table, otherwise we will crash the script //
    // so we are going to check this here and return an error if there were no items found.//
    if(totalItems === 0)
    {
        Spark.setScriptError("error", "Couldn't Find Items With Rarity ["+rarity.rarityValue+"] In Table....");
        Spark.exit();
    }
    // now we'll get a random item from the loot-table matching the rarity, and get that item //
    var randomItem = getRandomInt(0, totalItems-1);
    var dropDetails =  Spark.metaCollection("lootTable").find({ "rarity" : rarity.rarityValue }).skip(randomItem).next();
    // Now we deliver that number of items to the player //
    Spark.getPlayer().addVGood(dropDetails.shortCode, dropDetails.amount, "Loot-Drop");
    // now we add this to the array of items we want to return to the client //
    lootDrop.push({
        "report" : { // The report is only used to check that the drop is 100% accurate
            "percent" : rarity.percentage,
            "rarityValue" : rarity.rarityValue,
        },
        "shortCode" : dropDetails.shortCode,
        "amount" : dropDetails.amount
    });
}
// return the loot drop details to the client //
Spark.setScriptData("lootDrop", lootDrop);

4. You'll also need to add the following functions to this script at the bottom. Feel free to create a module to call these if you need to:

/**
 * Gets the rarity value closest to the percentage given and returns the rarityValue
 * @param {Number} percentage - the percentage we want to find a range for
 * @returns {Number} rarityValue - The rarityValue for the items we want to search for
 */
function getClosestRarity(percentage)
{
    // first we get all the rarities in the DB, sorted by the highest percentage first //
    var rarityList = Spark.metaCollection("lootTable").find({ "type" : "rarityValue" }, { "_id" : 0 }).sort({ "percentage" : -1 });
    var currPercentage = 0;
    // We iterate over the rarity list, checking if the precentage is less than the current rarity //
    // if it is, then we use that rarity, if not, then we keep iterating and adding the percentage onto the original value //
    // as the percentage increases we keep checking to make sure that all rarity values are checked until we get to 100 //
    while (rarityList.hasNext())
    {
        var currRar = rarityList.next();
        currPercentage += currRar.percentage;
        if(percentage < currPercentage){
            return currRar;
        }
    }
    // if we still have nothing, then we have a problem with the table and we should raise an error and exit the script //
    Spark.setScriptError("error", "Couldn't Find Rarity Values In Table....");
    Spark.exit();
}



function getRandomInt(min, max)
{
  return Math.floor(Math.random() * (max - min + 1)) + min;
}


Testing

So long as your loot-tables are set up correctly, you should be able to test this out immediately. But don’t worry, we’ve put in some checks to make sure the code can’t run if the loot-table isn’t configured properly, so these checks will tell you if you have any problems.

We can test this from the Test Harness. All you have to do is put in an amount and call the request:

In the example above, you can see that we entered 10 in for the loot-drop amount. Not all of the drops can be seen because of the screen-size, but you can see from the results that the percentages are approximately correct.

Now, because the drop is random, you may get instances where rarity 3 get dropped more than 10% of the time, or otherwise the table seems off. This is not because of the table-logic, but because the random element will create cases like that. On average though, this loot-drop system will drop items of rarity 1 70% of the time, and so on. Test it out and you should see this trend!

Conclusion

This tutorial provided the basics of how to get a loot-drop/gacha system up and running, but this is a very simple example. There are a lot of extra features you could add that would add extra gameplay elements to your loot-drop systems. Common use-cases include:

On the manage screen side, there is also room for improvements. You might want to insert checks to make sure your rarities add up to 100 or that duplicate items cannot be inserted into the table. Elements like this are useful to ensure that designers do not introduce errors or bugs that might become difficult to track later in development. You could also expand the example manage screen we've built in this tutorial to give you multiple loot-tables, each of which could be configured for specific levels, events, bosses, and so on.

Did this page help you? Please enter your feedback below. For questions about using this part of the platform, please contact support here