Game Development Community

dev|Pro Game Development Curriculum

Sharing Player Data between Server and Client in Torque

by DavidRM · 05/05/2002 (6:58 am) · 4 comments

This was originally posted as part of my 30 April, 2002, plan. There was some positive feedback about posting this as a resource, so here goes.

In adding a "mission ready status" to our current (unannounced) project, I discovered that the client side of Torque is quite ignorant about certain things on the server side. Obviously, in a lot of cases, this is a Good Thing (tm). But even the player's name is unavailable to the client, currently. And that seems a bit odd.

So I came up with a small, flexible system for communicating information from the server back to the client. It also includes a mechanism for the client to request certain changes in this information.

Here's an overview:

Server-to-Client
playerLocalInfo playerID playerName
playerData playerID attributeName attributeValue
playerDataSummary playerID playerName attributeValue1 attributeValue2 ...

Client-to-Server
playerDataChange attributeName attributeValue

playerLocalInfo is sent to each player when they log in, letting the client software know who they are. playerDataSummary is sent to every player when a new player logs in. It contains public information that is visible to all players. After that, if public player data changes, a playerData will be sent to all players with the update.

playerDataChange is sent by the client to request changes in his own data. The server sends a confirmation playerData message in response (with the new data or the old data if the change was rejected).

The flexibility of this system comes from using stub "event handlers", as well as using the script command eval().

Since I have already made some extensive changes to the scripts that come with the Torque demo, there's no real way for me provide drop-in code and modifications. The following shows what I did, and where I did it.

(new file) ~/common/server/playerData.cs:
function SendPlayerLocalInfo(%client)
   {
   commandToClient(%client,'playerLocalInfo',%client,%client.Name);
   }

function SendPlayerDataSummary(%client,%player)
   {
   commandToClient(%client,'playerDataSummary',%player,%player.Name,%player.missionReady);
   }

function SendPlayerData(%client,%player,%attributeName,%attributeValue)
   {
   commandToClient(%client,'playerData',%player,%attributeName,%attributeValue);
   }

function SendPlayerDataAll(%player,%attributeName,%attributeValue)
   {
   commandToClient(%client,'playerData',%player,%attributeName,%attributeValue);
   %count=ClientGroup.getCount();
   for(%cc=0; %cc<%count; %cc++)
      {
      %client = ClientGroup.getObject(%cc);
      SendPlayerData(%client,%player,%attributeName,%attributeValue);
      }
   }

function SendPlayerDataAllExcept(%skipClient,%player,%attributeName,%attributeValue)
   {
   commandToClient(%client,'playerData',%player,%attributeName,%attributeValue);
   %count=ClientGroup.getCount();
   for(%cc=0; %cc<%count; %cc++)
      {
      %client = ClientGroup.getObject(%cc);
      if (%client!=%skipClient)
         SendPlayerData(%client,%player,%attributeName,%attributeValue);
      }
   }

// Message Handlers
function serverCmdPlayerDataChange(%client,%attributeName,%attributeValue)
   {
   onPlayerDataChange(%client,detag(%attributeName),%attributeValue);
   // confirm change (or force reset to existing value)
   eval("%value=%client."@detag(%attributeName)@";");
   SendPlayerData(%client,%client,%attributeName,%value);
   }

// Event handlers
function onPlayerDataChange(%client,%attributeName,%attributeValue)
   {
   // switch/if those attribute names you want to validate before setting
   if (%attributeName$="missionReady")
      %client.missionReady=%attributeValue;
   }

(new file) ~/common/client/playerData.cs:
$localPlayer=0;
$localPlayerID=0;
$localPlayerName="";

// Message handlers
function clientCmdPlayerLocalInfo(%clientID,%clientName)
   {
   $localPlayerID=%clientID;
   $localPlayerName=StripMLControlChars(detag(%clientName));
   if (isObject($localPlayer))
      {
      $localPlayer.Name=$localPlayerName;
      }
   }

function clientCmdPlayerData(%clientID,%attributeName,%attributeValue)
   {
   OnPlayerData(%clientID,detag(%attributeName),%attributeValue);
   }

function clientCmdPlayerDataSummary(%clientID,%clientName,%missionReady)
   {
   OnPlayerDataSummary(%clientID,%clientName,%missionReady);
   }
   
// Server messages
function SendPlayerDataChange(%attributeName,%attributeValue)
   {
   commandToServer('playerDataChange',%attributeName,%attributeValue);
   }

// Event handlers
function OnPlayerData(%clientID,%attributeName,%attributeValue)
   {
   // by default, only the local player information is tracked
   if (isObject($localPlayer) && (%clientID==$localPlayerID))
      {
      eval("$localPlayer."@%attributeName@"="@%attributeValue@";");
      }
   }

function OnPlayerDataSummary(%clientID,%clientName,%missionReady)
   {
   // by default, only the local player information is tracked
   if (isObject($localPlayer) && (%clientID==$localPlayerID))
      {
      $localPlayer.missionReady=%missionReady;
      }
   }

(modified) ~/common/server/clientConnection.cs:
function GameConnection::onConnect( %client, %name )

...

   // Save client preferences on the connection object for later use.
   %client.gender = "Male";
   %client.armor = "Light";
   %client.race = "Human";
   %client.skin = addTaggedString( "base" );
   %client.setPlayerName(%name);
   %client.score = 0;
   %client.missionReady = true; // drm
   
   SendPlayerLocalInfo(%client); // drm
   SendPlayerDataSummary(%client,%client); // drm

...

   // Inform the client of all the other clients
   %count = ClientGroup.getCount();
   for (%cl = 0; %cl < %count; %cl++) {

...

         SendPlayerDataSummary(%client,%other); // drm
      }

...

}

Finally, on the client side, it's necessary to modify the actual game mod ("fps" in the demo) since there is no "~/common/client/" file that hooks GameConnection.

(modified) ~/fps/client/scripts/serverConnection.cs:
function GameConnection::onConnectionAccepted(%this)
{
   // Called on the new connection object after connect() succeeds.
   $localPlayer=%this;

...

}

To show the system in action (sorta):
function OnPlayerDataSummary(%clientID,%clientName,%missionReady)
   {
   Parent::OnPlayerDataSummary(%clientID,%clientName,%missionReady);
   if (isObject($localPlayer) && (%clientID==$localPlayerID))
      {
      readyRoomGui.UpdatePlayerName();
      }
   }

function OnPlayerData(%clientID,%attributeName,%attributeValue)
   {
   Parent::OnPlayerData(%clientID,%attributeName,%attributeValue);
   if (isObject($localPlayer) && (%clientID==$localPlayerID))
      {
      if (%attributeName$="missionReady")
         readyRoomGui.UpdatePlayerName();
      }
   }

...

function readyRoomGui::ToggleReady()
   {
   SendPlayerDataChange('missionReady',!$localPlayer.missionReady);
   }

Maybe this is useful to someone. Best of luck.

-David
Samu Games

#1
05/05/2002 (12:29 pm)
Having read this through a few times, I can safely say that this is an excellent resource. Thanks!
#2
03/03/2005 (2:18 pm)
I dont understand what your doing . Maybe you could add comments to the code to at least partly explain what is going on to the reader?
#3
07/05/2005 (10:01 pm)
is this useable code with the current SDK download?
#4
09/20/2006 (8:12 pm)
Quote:function serverCmdPlayerDataChange(%client,%attributeName,%attributeValue)
{
onPlayerDataChange(%client,detag(%attributeName),%attributeValue);
// confirm change (or force reset to existing value)
eval("%value=%client."@detag(%attributeName)@";");
SendPlayerData(%client,%client,%attributeName,%value);
}

I'm sorry to rate this down, but anyone who can edit your game scripts, can compromise your server. They just need to send you a script string that calls some function of their choosing (including "eval" !) and it'll be executed on the server (and possibly on the other clients, too!)

Do not use this mechanism in any game you intend to distribute! You will open up yourself to possibly severe vulnerabilities and liability.