question

Larry Dietz avatar image
Larry Dietz asked

Cloudscript issue from client

I am writing a secondary client that will not be distributed, for testing, and need to be able to update some stats from the client. I don't want to enable clients being able to submit stats, so I created a cloudscript to do the update, with this client sending in the stats. The actual released game wont make any calls to this script, and once the game is going, and people are playing it, I will most likely be removing it, but until then, I need it to work.

When I attempt to call the cloudscript from the client, it fails with the following error in Visual Studio...

Error = {PlayFab.ClientModels.ScriptExecutionError}

Error = "CloudScriptAPIRequestError"

Message = "The script called a PlayFab API, which returned an error. See the Error logs for details."

StackTrace = "Error\n at handlers.UpdatePlayerStats (CFB2-main.js:400:23)"

The logs show the following...

 "apiError": {
                        "code": 400,
                        "status": "BadRequest",
                        "error": "InvalidParams",
                        "errorCode": 1000,
                        "errorMessage": "Invalid input parameters",
                        "errorHash": null,
                        "errorDetails": {
                            "Statistics": [

However, copying the data being sent from the client, and pasting it into a cloudscript call from the player screen in the dashboard works. The update goes through as expected.

The TitleID I am working on is CFB2 and I am currently trying with Revision 141.

Any idea what I might be doing wrong?

Any help is appreciated.

-Larry

CloudScript
10 |1200

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

brendan avatar image
brendan answered

Could you provide the rest of the error message? Specifically, the errorDetails has the complete info on which parameter was incorrect in the call.

Also, it's worth noting that if this version of your executable will never be released to anyone outside your studio, you could always use the Server or Admin API from that code. You'd just need to make very sure that you have a clean separation between that code and your release project, so that you don't accidentally expose your Secret Key to users.

Finally, from a quick look at your script, it appears you're attempting to update statistics on a different player than the one who triggered the script. Please be aware that this could cause collision issues in the data, if two players attempt to update the same stat at the same time. That could cause the stat update API call to fail, but in any case, only the last successful call would be authoritative about the data written. Rather than attempting to update another player's statistics, I would recommend having an "inbox" shared group data per player, and there you can write to a Key/Value pair where the Key is the attacking player's PlayFab ID, and the Value is the change. In an OnLogin operation, you could then have the player whose account was attacked process the inputs from all other players and delete that data from his shared group data.

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

Larry Dietz avatar image Larry Dietz commented ·

That particular routine only modifies the stats on currentPlayerId, but there are a couple of others handlers that are modifies another users stats. I will have to look into the shared data "Inbox" idea. I like that, and it would definitely take care of any collision issues. I also didn't think about using the server or admin api in the private client. That is probably the best way to deal with this particular issue. However, here is the errorDetails. Wasn't a lot of help :(

"errorDetails": {
                            "Statistics": [
                                ""
                            ]
                        }

Thanks for your help.
-Larry

0 Likes 0 ·
brendan avatar image brendan Larry Dietz commented ·

That would indicate that an empty array was passed in as the value for the Statistics parameter. If you're trying to get all stats, leaving the Statistics parameter out of the call will get you that, but the service will have a problem with an empty array.

0 Likes 0 ·
Larry Dietz avatar image
Larry Dietz answered

That is what was not making any sense. The data passed in wasn't empty. I at one point, did a log.info with the value being passed in, and the data was there. I copied that data and pasted it into a call from the dashboard, and it worked. I had to have something wrong, but after you pointed out that I could use the server calls from the client, since it won't be distributed, I removed that handler, and took care of it from the client side, and all is fine now.

I do have another question for you though, concerning your idea of using the shared data, and updating the attacked player when they logged in next, but while trying to think how best to implement it, I ran into a snag.

Assume player 1 had 1,000,000 gold, Player 2 steals half of that and an item queues to deduct this. Player 1 would still have 1,000,000 gold until he logged in next to update it. Before player 1 logs in again, Player 3 steals 75% of his money, and another item queues to deduct this when player 1 logs in.

Now, player 1 logs in, and processes these, first it deducts 500,000 for the first attack, then it deducts 750,000 from the 2nd attack. How he is at -250,000. Even if I clamp it to 0 in the script, an extra 250,000 gold just entered the economy.

What would you think would be the best way to deal with this scenario?

My first though was to iterate through any items queued for the player being attacked, and do the match to determine how much is remaining to steal, but am concerned that if a player takes a while to log in, it may take a while to scan through all the queued items, just to find out the player doesn't have anything left to steal, then move on to another player and start the process again.

Any idea on how you would handle this?

I really appreciate your help.

-Larry

10 |1200

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

brendan avatar image
brendan answered

The problem with this is that you run the risk of having multiple attackers attempting to change the value at the same time. So the scenario you describe would still occur in that case - just less often, since it would only happen when two players attack the same person at the same time (or more accurately, resolve their attacks at the same time). You would have two processes checking the attacked players gold, and seeing the 1 million balance to start, then each subtracting from that.

Since you want to be able to change another player's gold balance even if he's offline, and have that new balance be what the next player attacking that player uses, the safest (though most heavyweight) way to do this would be by having a custom game server acting as the processing queue for those changes. But there is a lighter-weight approach you can use, via Cloud Script and PlayStream.

In PlayStream, events for each entity (a player is an entity, for example) are processed serially. So what you want is to use the event stream for the player being attacked to drive the changes. The logic would then be:

  • Call a Cloud Script from the attacking client, to start the attack resolution.
  • Call WritePlayerEvent from the Cloud Script, with the PlayFab ID of the player being attacked as the PlayFabId in the call. Pass in any data you need (apart from the Gold, since the point is to serialize changes to that) in order to do your calculation as part of this.
  • Define a Rule in PlayStream that runs another Cloud Script handler which performs the necessary calculations - PlayStream triggered Cloud Scripts have a much shorter runtime limit (specifically because event processing for the entity is paused while waiting on it to complete), so the only things you'll do in it are get the gold for the attacked player, calculate the change, and then use WritePlayerEvent to send another event, this time using the attacking player's PlayFab ID, in order to pass back the information on the change.
  • Define a second Rule in PlayStream that runs the Cloud Script handler which "catches" that change info, so that you can set it in the attacking player's data somewhere (or just change his gold balance).

Now, without a custom game server, you'll need to have the attacking player poll after a couple of seconds for that result info. Be sure to use an exponential retry backoff for that, so that you don't have players hammering away at the server for their results (as that could cause them to be throttled). But that would allow you to safely have that cross-player add/subtract operation.

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

Larry Dietz avatar image Larry Dietz commented ·

Ok, I follow you. I will see what I can do to implement that. One of these days I will get more comfortable with everything PlayFab has to offer. :)

Keep up the great work. So far, I am really liking what I am seeing, as well as your responsiveness to help when people are stuck.

Thanks,

-Larry

0 Likes 0 ·
Larry Dietz avatar image Larry Dietz Larry Dietz commented ·

I think I have things right so far, but am stuck on one point.

What I have so far, the starting the stealing process which writes an event.

I have a rule that picks up that event and runs a script which calculates the amount to steal, removes that amount from the victim, then writes another event.

I have another rule that picks up the 2nd event, and adds the amount to the original attacker.

There is where I am stuck. Do you have an example of how to check for the results of that 2nd event? So far I can't seem to figure it out. My only thought so far is to set a key in the player data and check for that key. Is this a viable way to get the results? Or is there some better way to accomplish this?

So far I haven't tested any of this, as I wanted to get the last part in place, and test the whole process.

Thanks,

-Larry

0 Likes 0 ·
brendan avatar image brendan Larry Dietz commented ·

That's what I was saying about needing to poll for the information for the attacker after a couple of seconds. We will be providing a way to subscribe to events later on, but for right now, there's no active connection between the service and the client, so there's no way for us to "push" a notification to the client that this has been completed.

0 Likes 0 ·
Show more comments
Larry Dietz avatar image Larry Dietz commented ·

Understood.

Thanks again for all your help!

-Larry

0 Likes 0 ·
Larry Dietz avatar image Larry Dietz commented ·

For some reason, it would not let me comment on your post. too many layers deep maybe?

In any case. Once again, thank you Brendan. You have been an immense help.

-Larry

0 Likes 0 ·

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.