question

Kim Strasser avatar image
Kim Strasser asked

CloudScript: How can I update my achievements?

I created 4 different achievements in Title Data but I don't know how to update the achievements.

I wrote some code to update the achievements in Title Data after the player has finished a level in my game, but I get this error when I want to save the CloudScript revision:

SyntaxError: Unexpected token . at Script Document [3]:20:20 -> args.AchievementKey: NewAchievement.addvalue

What is wrong with my code? How can I change the value of args.AchievementKey?

I create the achievements when the player starts the game the very first time:

handlers.CreateAchievementsReadOnlyData = function (args, context)
{
   var resultdata = server.GetUserReadOnlyData({PlayFabId: currentPlayerId, Keys: "Finish level 1"}); // first search for the current achievement info
   
   if(resultdata.Data.hasOwnProperty("Finish level 1"))
   { // if achievements info already exists in readonly data
       log.info("Already has achievements info."); // notify the client that Achievements info already been set
   }
   else
   {// if achievements info hasn't been set yet, set it for the user
       server.UpdateUserReadOnlyData({
           PlayFabId: currentPlayerId,
           Data: {
               "Finish level 1": "false",
               "Finish level 1 three times": "false",
               "Level1FinishTimes": "0",
               "Kill all enemies in this level": "false",
               "Get 10.000 points": "false", // You get points after you finiseh a level. I want to sum all points and check if the player has reached 10.000 points.
               "PlayerTotalpoints": "0"
           },
           Permission: UserDataPermission.Public
        });
        
      log.info("Achievements info has now been set."); 
   }
   
   return resultdata;
}

I always need to update the achievements after the player finished a level. But I don't know how to do this correctly.

handlers.UpdateAchievementReadOnlyData = function (args, context)
{
   var resultdata = server.GetUserReadOnlyData({PlayFabId: currentPlayerId, Keys: args.AchievementKey});
   var achievementkey = args.AchievementKey;
   var NewAchievement = ChooseanAchievement(args.AchievementKey);

   if ((resultdata.Data.hasOwnProperty(args.AchievementKey)) && (NewAchievement.achievement != ""))
   { // if achievement info already exists in readonly data 
          if ((resultdata.Data.args.AchievementKey.Value != null) && (resultdata.Data.args.AchievementKey.Value != ""))
          { // make sure the value is not null or ""
           server.UpdateUserReadOnlyData({
               PlayFabId: currentPlayerId,
               Data: {
               args.AchievementKey: NewAchievement.addvalue
               },
               Permission: UserDataPermission.Public
               });
               
               log.info("Updating achievement was succuessful.");
          }
          else
          { 
              log.info("This achievement exists but has no value.");
          }
   }
   else
   {
       log.info("This achievement does not exist.");
   }
   
   return server.GetUserReadOnlyData({PlayFabID: currentPlayerId, Keys: args.AchievementKey}).Data.args.AchievementKey.Value;
}

function ChooseanAchievement(achievementkey)
{
    switch (achievementkey)
    {
    case "Finish level 1":
        log.info("Trying to update achievement: Finish level 1");
        return{achievement: "Finish level 1", addvalue: true};
        break;
    case "Finish level 1 three times":
        var resultdatalevel1finish = server.GetUserReadOnlyData({PlayFabId: currentPlayerId, Keys: "Level1FinishTimes"});
        if(resultdatalevel1finish.Data.hasOwnProperty("Level1FinishTimes"))
        {
          if ((resultdatalevel1finish.Data.Level1FinishTimes.Value != null) && (resultdatalevel1finish.Data.Level1FinishTimes.Value != ""))
          { 
            if (resultdatalevel1finish.Data.Level1FinishTimes.Value >= 3)
              return{achievement: "Finish level 1 three times", addvalue: true};
            else
              return{achievement: "Finish level 1 three times", addvalue: false};
          }
          else
           return{achievement: "", addvalue: false};
        }
        else
         return{achievement: "", addvalue: false};
        break;
    case "Get 10.000 points":
        var resultdatapoints = server.GetUserReadOnlyData({PlayFabId: currentPlayerId, Keys: "PlayerTotalpoints"});
        if(resultdatapoints.Data.hasOwnProperty("PlayerTotalpoints"))
        {
          if ((resultdatapoints.Data.PlayerTotalpoints.Value != null) && (resultdatapoints.Data.PlayerTotalpoints.Value != ""))
          { 
            if (resultdatapoints.Data.PlayerTotalpoints.Value >= 10000)
              return{achievement: "Get 10.000 points", addvalue: true};
            else
              return{achievement: "Get 10.000 points", addvalue: false};
          }
          else
           return{achievement: "", addvalue: false};
        }
        else
         return{achievement: "", addvalue: false};
        break;
    case "Kill all enemies in this level":
        return{achievement: "Kill all enemies in this level", addvalue: true};
        break;
    default: // if the achievementkey is not valid
        return{achievement: "", addvalue: 0};
        break;
    }
}

The two keys/value pairs "Level1FinishTimes": "0" and "PlayerTotalpoints": "0" are no achievements, I created these two key/value pairs in order to count how often the player finished level 1 and in order to count how many points the player has already collected.

What is the best way to update my achievements? Could you give me some recommendations or examples?

Client code:

string AchievementName = "Finish level 1";

private async Task UpdateAchievements()
{
    var result = await PlayFabClientAPI.ExecuteCloudScriptAsync(new ExecuteCloudScriptRequest()
    {
        FunctionName = "UpdateAchievementReadOnlyData",
        FunctionParameter = new { AchievementKey = AchievementName },
        GeneratePlayStreamEvent = true
    });

    if (result.Error != null)
        Console.WriteLine(result.Error.Error.ToString());
    else
    {
        if (result.Result.Logs.Count() > 0)
        {
             //...     
        }
    }
}

UPDATE:

I changed the code in handlers.UpdateAchievementReadOnlyData, but I still get a JavascriptException and I don't know how to fix this problem.

What is wrong with my code in handlers.UpdateAchievementReadOnlyData?

Raw event JSON
{
    "EventName": "player_executed_cloudscript",
    "Source": "CloudScript",
    "FunctionName": "UpdateAchievementReadOnlyData",
    "CloudScriptExecutionResult": {
        "FunctionName": "UpdateAchievementReadOnlyData",
        "Revision": 92,
        "FunctionResult": null,
        "FunctionResultTooLarge": null,
        "Logs": [
            {
                "Level": "Info",
                "Message": "Trying to update achievement: Finish level 1",
                "Data": null
            }
        ],
        "LogsTooLarge": null,
        "ExecutionTimeSeconds": 0.016638,
        "ProcessorTimeSeconds": 0,
        "MemoryConsumedBytes": 58768,
        "APIRequestsIssued": 1,
        "HttpRequestsIssued": 0,
        "Error": {
            "Error": "JavascriptException",
            "Message": "JavascriptException",
            "StackTrace": "TypeError: Cannot read property 'AchievementKey' of undefined\n    at handlers.UpdateAchievementReadOnlyData (BFD0A-main.js:15:37)"
        }
    },
handlers.UpdateAchievementReadOnlyData = function (args, context)
{
   var resultdata = server.GetUserReadOnlyData({PlayFabId: currentPlayerId, Keys: args.AchievementKey});
   var achievementkey = args.AchievementKey;
   var NewAchievement = ChooseanAchievement(args.AchievementKey);

   if ((resultdata.Data.hasOwnProperty(args.AchievementKey)) && (NewAchievement.achievement != ""))
   { // if achievement info already exists in readonly data 
          if ((resultdata.Data.args.AchievementKey.Value != null) && (resultdata.Data.args.AchievementKey.Value != ""))
          { // make sure the value is not null or ""
               var MyRequest = {
               PlayFabId: currentPlayerId,
               Data: {    },
               Permission: UserDataPermission.Public
               };    
               
               MyRequest.Data[args.AchievementKey] = NewAchievement.addvalue;         
               server.UpdateUserReadOnlyData(MyRequest);
               
               log.info("Updating achievement " + NewAchievement.achievement.toString() + " was succuessful.");
          }
          else
          { 
              log.info("This achievement exists but has no value.");
          }
   }
   else
   {
       log.info("This achievement does not exist.");
   }
   
   return server.GetUserReadOnlyData({PlayFabID: currentPlayerId, Keys: args.AchievementKey}).Data.args.AchievementKey.Value;
}
CloudScriptTitle Data
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Sarah Zhang avatar image
Sarah Zhang answered

@Kim Strasser

>>> Why is args.AchievementKey undefined?

We need some time to check the whole code you provided, so we didn't give the entire troubleshooting result immediately, just gave some tips before. According to your client code, args.AchievementKey isn't undefined in this case.

Firstly, you can target the line which has the error through the error message. Like ([TitleId]main.js:15:37), it means the JavascriptException happens on line 15 Column 37. So, we can know the error should be in the Data.args.AchievementKey The cause of the error message is we can't use an expression instead of the field name in this part. If we use Data.args.AchievementKey, Js will parse the path incorrectly. But "Finish level 1" contains the space symbol, it can't be used as the field name directly too. (If we change the "Finish level 1" to "Finish_level_1", Data.Finish_level_1 will work). Summary, we can use Data[args.AchievementKey].Value here. Js will use args.AchievementKey as an index string to get the corresponding field.

Besides, your code has another issue. In the request body of GetUserReadOnlyData, Keys should be a sequence. Generally, you can use Array or List to handle such JSON. You can check the API reference to know such data types, it is actually a List<string>. So we can change the CloudScript code to Keys:[args.AchievementKey]. Or we can define an Array or a List in the clients, then use Keys:args.AchievementKeys.

>>> How can I update the achievements in one call?

You can define an object[] as the Data's child object. The object array can contain multiple K/V pairs.

3 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Kim Strasser avatar image Kim Strasser commented ·

Thank you for the explanations. I will try it later with an object[] as the Data's child object.

But I have a question about Result.FunctionResult in the client code. It is always null, but I don't understand why Result.FunctionResult is null. I get no error messages and updating the key/value pair "Finish level 1" is successful, but Result.FunctionResult is always null.

I have this line at the end of my CloudScript code:

return server.GetUserReadOnlyData({PlayFabID: currentPlayerId, Keys: [args.AchievementKey]}).Data.Value;

Why is Result.FunctionResult not returning the value of the key/value pair in the client code?

0 Likes 0 ·
Sarah Zhang avatar image Sarah Zhang Kim Strasser commented ·

You can try the following code.

   return server.GetUserReadOnlyData({PlayFabID: currentPlayerId, Keys: [args.AchievementKey]}).Data[args.AchievementKey].Value;
0 Likes 0 ·
Sarah Zhang avatar image Sarah Zhang Sarah Zhang commented ·

@Kim Strasser Actually, you can test PlayFab API using the external REST API testing tools (like Postman). It will be a convenient way to confirm the body of the API requests or responses. You also can use such tools to test your CloudScript code using ExecuteCloudScript API. PlayFab provides Postman SDK, you can download it in the SDK page.

0 Likes 0 ·
Sarah Zhang avatar image
Sarah Zhang answered

We can’t use the expression, variable name as a field name on the Data block directly when we initialize the request with assigning values. Please target the line the error message reported, and try the following way.

var MyRequest = {
               PlayFabId: currentPlayerId,
               Data: {    },
               Permission: UserDataPermission.Public
               };               
 MyRequest.Data[args.AchievementKey] = NewAchievementValue;         
 server.UpdateUserReadOnlyData(MyRequest);

4 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Kim Strasser avatar image Kim Strasser commented ·

I changed it in my CloudScript but now I get another error message:

"Error": {
            "Error": "JavascriptException",
            "Message": "JavascriptException",
            "StackTrace": "TypeError: Cannot read property 'AchievementKey' of undefined\n    at handlers.UpdateAchievementReadOnlyData (BFD0A-main.js:15:37)"
        }

What is wrong with AchievementKey? How can I find out if args.AchievementKey has a value or not?

handlers.UpdateAchievementReadOnlyData = function (args, context)
{
//...

if ((resultdata.Data.hasOwnProperty(args.AchievementKey)) && (NewAchievement.achievement != ""))
{ 
    if ((resultdata.Data.args.AchievementKey.Value != null) && (resultdata.Data.args.AchievementKey.Value != ""))
    {
        var MyRequest = {
               PlayFabId: currentPlayerId,
               Data: {    },
               Permission: UserDataPermission.Public
               };    
               
        MyRequest.Data[args.AchievementKey] = NewAchievement.addvalue;         
               server.UpdateUserReadOnlyData(MyRequest);
               
        log.info("Updating achievement " + NewAchievement.achievement.toString() + " was succuessful.");
    }
//...

}
0 Likes 0 ·
Sarah Zhang avatar image Sarah Zhang Kim Strasser commented ·

You can try to add "undefined" judgment.

args.AchievementKey == undefined;

or

case undefined: 
// code
break;
0 Likes 0 ·
Kim Strasser avatar image Kim Strasser Sarah Zhang commented ·

I don't understand why args.AchievementKey is undefined. I use this client code to call the CloudScript. I want to change the key/value pair "Finish level 1" in my CloudScript from false to true.

string AchievementName = "Finish level 1";
var result = await PlayFabClientAPI.ExecuteCloudScriptAsync(new ExecuteCloudScriptRequest()
    {
        FunctionName = "UpdateAchievementReadOnlyData",
        FunctionParameter = new { AchievementKey = AchievementName },
        GeneratePlayStreamEvent = true
    });

And the player already has the key/value pair in his account because I call handlers.CreateAchievementsReadOnlyData before handlers.UpdateAchievementReadOnlyData.

Why is args.AchievementKey undefined?

0 Likes 0 ·
Show more comments

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.