Cloud Code is one of the most powerful features in ChilliConnect, allowing you to write your own logic in Javascript which runs on the ChilliConnect servers. Cloud Code scripts can be triggered directly from your game, on a schedule, or even by external platforms. Developers on the ChilliConnect platform are finding ever more creative ways to use Cloud Code in their games. Some common use cases are:

  • Implementing specific server authoritative features eg. energy regeneration.
  • Simplifying client code that needs to make a sequence of API calls.
  • An indirection layer that allows additional logic to be added before, or after, a ChilliConnect API call.

If you’ve just started using Cloud Code, we have collected some examples of advanced techniques that will help you get the most from your scripts.

Using Catalog Configuration

One reason Cloud Code is so powerful is its capability to integrate with the rest of the ChilliConnect API. In particular, this allows you to use the Catalog to store configuration variables and settings that drive your scripts. For example, if you have a script that rewards players a random amount of in game currency, rather than hardcode this reward into your script, you can define it as a Metadata item in your Catalog:

Screenshot from ChilliConnect Live Game Management Game Backend Services dashboard showing metadata type in catalog

ChilliConnect Live Game Management Dashboard | Catalog

Your script can then load this item from the Catalog:


var ChilliConnectSDK = ChilliConnect.getSdk(“2.0.0”);

/**
* COIN_CHEST is an Catalog Meta Data definition that is in the following
* format:
* 
* {
*   “Rewards”: [
*       5,
*       10,
*       100,
*       500
*   ]
* }
*/
var chestConfigs = ChilliConnectSDK.Catalog.getMetadataDefinitions(“COIN_CHEST”);
if ( !chestConfigs || chestConfigs.length < 1) {
   return { “Error” : “Could not find meta data definition” };
}

/** 
* Get the Meta Data part from the list of definitions 
*/
var rewards = chestConfigs.Items[0].CustomData.Rewards;

/** 
* Assign the reward randomly based on one of the items 
*/
var rewardAmount = rewards[Math.floor(Math.random() * rewards.length)];

/** 
* Add the reward to the players currency balance 
*/
ChilliConnectSDK.Economy.addCurrencyBalance( “COINS”, rewardAmount )

/** 
* Return the rewarded amount plus the new balance 
*/
return { 
   “Reward” : rewardAmount, 
   “Balances” : ChilliConnectSDK.Economy.getCurrencyBalance( [“COINS”] ) 
};

 

When a script is run, it will automatically use the correct Catalog definitions for the player, meaning that you can use A/B Testing, or Permanent Overrides, to modify any Catalog items that have been referenced.

ES6 Features

ChilliConnect scripts support up to ES6 Javascript standard, allowing you to take advantage of newer Javascript features such as:


//Arrow functions:
const chillify = (what) => { return "Chilli"+ what };
return chillify("Connect");


//Constants:
const THE_BEST_PLATFORM = “ChilliConnect”;

//New data structures eg. Sets:
let s = new Set()
s.add("ChilliConnect")
   .add(" supports")
   .add(" new data structures");


let response = "";
for (let key of s.values())
   response+=key;

return response;

Get more information on the latest ES6 features.

Modules and Offline Testing

You can share code between your scripts using modules. For example, if we wanted to run the code sample above from multiple scripts, we could rewrite it as a module:


var coinChest = function(sdk) {
    this.sdk = sdk;
    this.applyRewards = function() { 

    var chestConfigs = this.sdk.Catalog.getMetadataDefinitions(“COIN_CHEST”);
    if ( !chestConfigs || chestConfigs.Items.length < 1) {
        throw “Could not find meta data definition”;
    }

    var rewards = chestConfigs.Items[0].CustomData.Rewards;
    var rewardAmount = rewards[Math.floor(Math.random() * rewards.length)];

    this.sdk.Economy.addCurrencyBalance( “COINS”, rewardAmount )
    
    return {
        “Reward” : rewardAmount,
        “Balances” :sdk.Economy.getCurrencyBalance( [“COINS”] )
    };
 }
}

module.exports = coinChest;

The module could then be called from an API script:


try {

const coinChest = require('COIN_CHEST');

return new coinChest(ChilliConnect.getSdk("2.1.0")).applyRewards();

}
catch(e) {
   return e.toString();
}

Sharing code between scripts is only one benefit of modules. Defining the bulk of your Cloud Code as modules also means that you can develop and test them offline, using a full development environment. For example, we could create a unit test using mocha.js for the Coin Chest module, testing this dependency on ChilliConnect:


var assert = require('assert');
var simple = require('simple-mock')

var CoinChest = require('./coin-chest.js');

describe('CoinChest', () => {
   describe('applyRewards', () => {
       it('should return error when no definition found', () => {
     
           let catalogModuleMock = {}
           simple.mock(catalogModuleMock, 'getMetadataDefinitions')
               .returnWith({ "Items" : [] });
               
           let chest = new CoinChest( { Catalog: catalogModuleMock } );
           try {
               chest.applyRewards();
               assert.fail();
           }
           catch(e) {
               assert.equal("Could not find meta data definition", e);    
           }
       });
       
       it('should apply award to player', () => {
     
           let catalogModuleMock = {}
           simple.mock(catalogModuleMock, 'getMetadataDefinitions')
               .returnWith({ "Items" : [{ 
                   "CustomData" : { "Rewards" :[ 10 ] }
               }]
           });
               
           let economyModuleMock = {}
           simple.mock(economyModuleMock, 'addCurrencyBalance');
           simple.mock(economyModuleMock, 'getCurrencyBalance')
               .returnWith( [ {"Key":"COINS", "Balance" : 10 } ]);
           
           let chest = new CoinChest( { Catalog: catalogModuleMock, Economy: economyModuleMock } );        
           chest.applyRewards();
           
           assert(economyModuleMock.addCurrencyBalance.called);
           assert.equal(economyModuleMock.addCurrencyBalance.lastCall.args[0], "COINS");
           assert.equal(economyModuleMock.addCurrencyBalance.lastCall.args[1], 10);
           
           assert(economyModuleMock.getCurrencyBalance.called);
           assert(Array.isArray(economyModuleMock.getCurrencyBalance.lastCall.args[0]));
           assert.equal(economyModuleMock.getCurrencyBalance.lastCall.args[0][0], ["COINS"]);
       });
   });
});

This approach allows you to make use of a fully featured development environment when writing your Cloud Code, as well as gaining the benefit of automated testing. We’ll be taking steps to make it even easier to write your Cloud Code scripts in your own environment in the future so watch this space…

Using Server Dates

Many popular monetisation and retention mechanics in live F2P games involve the use of date and time. For example, generating “energy” cooldown periods, or daily rewards that the player can earn by returning to the game on consecutive days.

In many cases, the simplest and most straightforward way to implement these is to use the time directly from the player’s device, but of course this can be easily manipulated by the player. Alternatively, you can fetch the current server time, in UTC, from a Cloud Code script and base on device calculations from this to prevent cheating:


return {"ServerDate" : new Date().toISOString() }

You can also take this approach one step further and move any date based calculations on to the server itself. For example, the script below demonstrates how you can create a server authoritative energy regeneration system:


try {

const sdk = ChilliConnect.getSdk("2.0.0");

/** Amount of currency generated per second **/
const regenRatePerSecond = 1;
const currentTimestamp = Math.ceil( new Date().getTime() / 1000 );

let lastTimestampGenerated = currentTimestamp;
let writeLock = null;

/** Load from Player Data the last time the currency was regenerated.*/
var playerData = sdk.CloudData.getPlayerData( ["LastEnergyRegen"] );
if ( playerData.Values.length === 1 ) {

   const value = parseFloat(playerData.Values[0].Value);
   
   /** If the value is valid, set it as the last regen time */
   if ( !isNaN(value) ) {
       lastTimestampGenerated = value;
       writeLock = playerData.Values[0].WriteLock;
   }
}

let newCurrency = 0;

/** Calculate number of seconds since last regen */
const secondsSinceRegen = currentTimestamp - lastTimestampGenerated;
if ( secondsSinceRegen > 0 ) {
   newCurrency = Math.ceil(secondsSinceRegen * regenRatePerSecond);
}

/** Pass in write lock if set, so concurrent requests do not reward the generated energy twice */
if ( writeLock !== null ) {
   sdk.CloudData.setPlayerData( "LastEnergyRegen", currentTimestamp, writeLock ) 
} else {
   sdk.CloudData.setPlayerData( "LastEnergyRegen", currentTimestamp )
}

/** Add the new currency to the player balance, if any */
if ( newCurrency > 0 ) {
   sdk.Economy.addCurrencyBalance("ENERGY", newCurrency);
}

return {
   "Success" : true,
   "Awarded" : newCurrency,
   "Total" : sdk.Economy.getCurrencyBalance(["ENERGY"]).Balances[0].Balance
};
}
catch( e ) {
   ChilliConnect.Logger.error(e.toString());
   return { "Success": false }
}

AsPlayer

Scripts run from your game using RunScript are always executed under the context of the logged in player. However, it’s also possible switch to another player context using the asPlayer method. This allows you to access and modify other player accounts using the full ChilliConnect API. This can be used to implement lots of interesting multiplayer use cases very easily. For example, you might want to allow players to “steal” a random amount of currency from another player:


const sdk = ChilliConnect.getSdk("2.0.0"); 

try {

    let coinsStolen = sdk.PlayerAccounts.asPlayer( ChilliConnect.Request.FromPlayer, () => {
        const balances = sdk.Economy.getCurrencyBalance( ["COINS"] );
        if ( balances.Balances.length === 0 ) {
            return 0;
        }

        const coinsBalance = balances.Balances[0].Balance;
        if ( coinsBalance === 0 ) {
            return 0;
        }


        let coinsToSteal = Math.floor((Math.random() * coinsBalance) + 1);

        ChilliConnect.Logger.info("Stealing " + coinsToSteal + " coins");
        sdk.Economy.setCurrencyBalance("COINS", coinsBalance - coinsToSteal);

        return coinsToSteal;
    });

    sdk.Economy.addCurrencyBalance("COINS", coinsStolen );

    return { "Stolen" : coinsStolen };
}
catch(e)
{
   ChilliConnect.Logger.error("Error:" + e );
   return { "Error" : e }

}

It’s also possible to create new players from a Cloud Code script using the CreatePlayer method. This means you can create a “proxy” player that represents a team, allowing you to store elements like currency, data and inventory just as you would with a normal player. This capability can also be used to allow players to have multiple game “accounts”, with progression and data tracked separately against each.

API Wrappers

One very useful pattern for Cloud Code scripts is as a wrapper to existing API methods where you can plug in your own code to run before, and after, the main call. A good example of this is in validating leaderboard scores. Rather than your game calling AddScore directly, you can instead call an API Cloud Code script using RunScript. This enables you to check the score, adding your own validation logic to ensure the score meets acceptable criteria and applying any other checks you might need. Again, as with the first example, you can use Catalog Metadata to configure the script, making it easy to tweak values without having to update your code:


const sdk = ChilliConnect.getSdk("2.0.0");

try {

    var configs = sdk.Catalog.getMetadataDefinitions("SCORE_RULES");
    if ( configs.Items.length < 1) {
        return { "Error" : "Could not find score rules definition" };
    }

    var scoreRules = configs.Items[0];
    var maxScore = scoreRules.CustomData.Max;
    var minScore = scoreRules.CustomData.Min;

    var score = ChilliConnect.Request.Score;
    if (score < minScore || score > maxScore ) {
        return { "Error" : "Invalid Score" };
    }

    return sdk.Leaderboards.addScore(score, "SCORES");
}
catch(e)
{
   return { "Error" : e.toString() }

}

This type of script also allows you to update any score checking logic without requiring build updates. For example, you might want to limit the number of score updates a player can submit in a certain time period. You could also keep track of suspicious scores submitted by players and then exclude their submissions entirely, all without having to modify the game client. By using a Cloud Code Script to to decouple the game code from the API call, you gain a layer of indirection that can be really useful when you want to make changes “on the fly”.

We love to share examples of creative Cloud Code with our community, so if you’d like us to use your game as case study please get in touch!

 

Further Reading:

New Support Scripts Feature | ChilliConnect

 

Live Game Management Best Practice

Choosing the right Client/Server Relationship
Preparing for Soft Launch
Updating your Game in Realtime
Gifting