Game Development Community

Override onDamage for local client only?

by Rob Terrell · in Torque Game Engine · 12/20/2006 (1:54 pm) · 3 replies

Dumb, dumb, dumb. I know this is basic TorqueScript stuff, but I'm stumped. I'm adding rumble support to my Wiimote code. The problem is, I can't figure out where to call it. I mean, it's obvious where to do it in C++ (I would add it around damage flash). But I'd like to do this in script.

If I add it in C++ to ShapeBase::getDamageFlash() I get a rumble for *every* player's damage.

If I add it in TorqueScript to /server/player.cs ::onDamage, I get a rumble for *every* player's damage.

Also in that script, I tried to test each damaged object for player-ness:

function Armor::onDamage(%this, %obj, %delta)
{
// ...snip of the other usual stuff, including the computation of the local %flash

if (%this.client == %obj) {
echo("it's the player!");
wiiRumble(%flash);
}

}

...and I never see "it's the player".


Any suggestions?

#1
12/20/2006 (4:30 pm)
Everything that you are doing is server side, and the effect you want needs to be client side.

However, your server does need to be the one to determine when a particular client should get a rumble, so you need to deliver a NetEvent to the appropriate client at "rumble time".

Fortunately, this is pretty easy. Script provides a set of commands designed specifically for this: commandToClient and clientCmd.

First, the syntax:

On the SERVER:
commandToClient(%clientToSendTo, 'TAG', <optional parameters>);

On the CLIENT:

function clientCmdTag(<optional parameters>)
{
  // do something here
}
Use case example:

SERVER:
commandToClient(%client, 'WiiRumble', %rumbleTime);

CLIENT:
function clientCmdWiiRumble( %rumbleTime)
{
   wiiRumble(%rumbleTime);
}
Now, on to figuring out which client to send to:

In the script method Armor::onDamage(%this, %obj, %delta), we have three parameters sent to us:

--%this : ObjectID of the datablock assigned to the player on the server
--%obj : ObjectID of the player object
--%delta : the damage to be done (from memory).

One more key element we need to know:

In /example/starter.fps/server/scripts/game.cs, we have the following callback that is performed on the server every time a client connects to the server:

function GameConnection::onClientEnterGame(%this)
{
   commandToClient(%this, 'SyncClock', $Sim::Time - $Game::StartTime);

   // Create a new camera object.
   %this.camera = new Camera() {
      dataBlock = Observer;
   };
   MissionCleanup.add( %this.camera );
   %this.camera.scopeToClient(%this);

   // Setup game parameters, the onConnect method currently starts
   // everyone with a 0 score.
   %this.score = 0;

   // Create a player object.
   %this.spawnPlayer();
}

This script does some important things for us, but for this particular need, I'm going to trace through the GameConnection::spawnPlayer() method, down to the GameConnection::createPlayer() method, which is the following:

function GameConnection::createPlayer(%this, %spawnPoint)
{
   if (%this.player > 0)  {
      // The client should not have a player currently
      // assigned.  Assigning a new one could result in 
      // a player ghost.
      error( "Attempting to create an angus ghost!" );
   }

   // Create the player object
   %player = new Player() {
      dataBlock = PlayerBody;
      client = %this;
   };
   MissionCleanup.add(%player);

   // Player setup...
   %player.setTransform(%spawnPoint);
   %player.setShapeName(%this.name);
   
   // Starting equipment
   %player.setInventory(Crossbow,1);
   %player.setInventory(CrossbowAmmo,10);
   %player.mountImage(CrossbowImage,0);

   // Update the camera to start with the player
   %this.camera.setTransform(%player.getEyeTransform());

   // Give the client control of the player
   %this.player = %player;
   %this.setControlObject(%player);
}

Lots of important stuff is done here, but the most critical part for us is the following:

// Create the player object
   %player = new Player() {
      dataBlock = PlayerBody;
      [b][i]client = %this;[/i][/b]
   };

Note that right now, %this is the objectID of the client that is connecting to the server right now.

The line within the new statement (italicized above) does something very important: it creates a dynamic field on the player object that contains the objectID of the client that the player object is assigned to.

Sound familiar? That's exactly what we need!

Given all the above, we need to implement the new function on the client ( clientCmdWiiRumble ), and our Armor::onDamage() should look like this:

function Armor::onDamage(%this, %obj, %delta)
{
   // This method is invoked by the ShapeBase code whenever the 
   // object's damage level changes.
   if (%delta > 0 && %obj.getState() !$= "Dead") {

      // Increment the flash based on the amount.
      %flash = %obj.getDamageFlash() + ((%delta / %this.maxDamage) * 2);
      if (%flash > 0.75)
         %flash = 0.75;
      %obj.setDamageFlash(%flash);

      // If the pain is excessive, let's hear about it.
      if (%delta > 10)
// change here
      {
         %obj.playPain();
         [b][i]commandToClient(%obj.client, 'WiiRumble', 1);[/i][/b]
      }
// end change
   }
}

This will properly deliver the wiiRumble event to the correct client in all cases.
#2
12/20/2006 (8:29 pm)
Stephen, thanks for taking the time to write such a clear explanation. I greatly appreciate it. I guess I was close -- I saw %this.client was the player, but I thought I could directly invoke some method from that object reference. It makes a lot more sense to think of it as integer datablock ID.
#3
12/20/2006 (9:10 pm)
Just a quick correction: %this.client does not exist in Armor::onDamage(). It should be %obj.client! %this is a datablock, NOT a player object.

Hopefully it works out for you!