Game Development Community

Is it possible for client to send command to server in engine?

by Nabarro · in Torque 3D Professional · 01/12/2010 (9:01 am) · 8 replies

As title, in our project, we need client to update it's position data to server, then server will sync the client's position data to other clients. So, we want to know if it's possible for client to implement such function in engine(except commandtoserver() in script). Thanks,

#1
01/12/2010 (9:08 am)
Technically most objects in the game do this constantly, informing the server of their positions and having the server distribute that info to clients who are "in scope" of the object in question.

I get the feeling there's something more specific you're trying to do, like network sync a custom value you added to an object aside from its actual Torque world position, so I'd need more details to help you beyond that.

It's definitely very possible to network any additional values you want to add to your objects, and I'd suggest you take a look at packUpdate/unpackUpdate, a function found in most of the object types (Player, ShapeBase, Vehicle, etc) in T3D, to start to get an idea of how it's done.

There's also writePacketData/readPacketData, which is for control objects (these updates are sent about the object a user is controlling to that user only, and contain data that would be a waste if sent to every other client).
#2
01/12/2010 (10:25 pm)
Henry, thank you very much.
I've looked some functions like packUpdate/unpackUpdate and writePacketData/readPacketData, etc. The problem is, these functions are used for server to send data or update it's status to client. For example, packUpdate and writePacketData is called by server side, unpackUpdate and readPacketData is called by client side. We call it as the server side send data to client side.

While, we need to let client side send data(or update it's status) to server side. So, above functions/mechanism can not be used.

Do you have any suggestions with this? Thank you very much.
#3
01/13/2010 (5:32 am)
sorry, that was pretty dull of me. I saw a mention of sending positions and sort of glossed over what you were saying.

You might find the MoveManager helpful, as this is how the client sends its collection of key presses and mouse movements to the server.

You should take a look at moveManager.cpp, moveManager.h and moveList.cpp.

This is just an example, but it's the easiest way for me to describe the process. I added an aim offset to my move list for controlling vehicle-mounted turrets by clicking on the screen. First, in moveManager.h, I added:
F32 aimX, aimY, aimZ;
to the Move struct.

Then I went to moveManager.cpp, to the Move::pack function, and added this right after "stream->writeInt(proll, 16);":

if(stream->writeFlag(aimX != 0))
	      stream->write(aimX);
	   if(stream->writeFlag(aimY != 0))
	      stream->write(aimY);
	   if(stream->writeFlag(aimZ != 0))
	      stream->write(aimZ);

The != 0 check was just because I didn't want to waste time sending this data if it wasn't being used.

Next, in Move::unpack, right after "proll = stream->readFlag() ? stream->readInt(16) : basemove->proll;" I added:

if(stream->readFlag())
	      stream->read(&aimX);
	   if(stream->readFlag())
	      stream->read(&aimY);
	   if(stream->readFlag())
	      stream->read(&aimZ);

Depending on what you're doing, that may be enough, as you can now add new data to the Move struct at any point on the client and expect it to get to the server. In my case, I wanted this to be accessible from script like the other MoveManager values, so I added:

F32 MoveManager::mAimOffsetX = 0;
F32 MoveManager::mAimOffsetY = 0;
F32 MoveManager::mAimOffsetZ = 0;

to the top of moveManager.cpp, with the other values there (changed to verbose naming just to be consistant with the style of the code there). Then down to MoveManager::init to add the console variables to the list there:

Con::addVariable("mvAimOffsetX", TypeF32, &mAimOffsetX);
   Con::addVariable("mvAimOffsetY", TypeF32, &mAimOffsetY);
   Con::addVariable("mvAimOffsetZ", TypeF32, &mAimOffsetZ);
Those values can now be accessed in client scripts as the global variable $mvAimOffsetX (/Y/Z).

And finally to moveList.cpp, MoveList::getNextMove where I transfer that script-linked value to the Move struct, that does the actual networking. Near the top, with the other lines following this structure:

curMove.aimX = MoveManager::mAimOffsetX;
   curMove.aimY = MoveManager::mAimOffsetY;
   curMove.aimZ = MoveManager::mAimOffsetZ;

Note that getNextMove is a great place to dump data into your Move struct even if it's not coming from script or the MoveManager. You could just as easily get your client Player object here and drop some data from that object into Move. Here's how I did that (kind of ugly, may be some potential issues with all that recasting):

if (mConnection->isConnectionToServer())
   {
	   ShapeBase * control = static_cast<ShapeBase*>(mConnection->getControlObject());
	   if (!control || !(control->getType() & PlayerObjectType))
	   {
		   // ... some error case goes here if you need it
	   }
	   else
	   {   
		   Player *tempPlayer = (Player*)control;
                   // ... take values from tempPlayer and store them
                   // in curMove here
           }
   }

To get these values back into your object on the server, you'll need to go to (for example, in the case of Vehicle): Vehicle::processTick. All of the object types should have a processTick function, and the latest move is always passed to processTick.

In my case, I wanted to take these values out of the Move struct and store them in Vehicle for later use (in this case, for use when the player decides to fire the turret they were aiming), so I added:

if (move != &NullMove) {
      mAimOffset.x = move->aimX;
      mAimOffset.y = move->aimY;
      mAimOffset.z = move->aimZ;
   }

where mAimOffset is a Point3F variable I added to the Vehicle class. Technically I placed this code in Vehicle::updateMove, but only Vehicles and Players have this function, so for the sake of broader application I pointed to processTick (which is where calls to UpdateMove come from in the first place). If you're using processTick, make sure it's a server instance, as this function gets called on client&server.

if (!isGhost())
will tell you if this is a server object. Ghosts are client objects.

Hopefully somewhere in that mess of example code is something that will help you out. There's probably another way to send data from client->server, but as far as I know, the Move struct is how the Player sends basically all of its updates to the server.
#4
01/13/2010 (8:18 am)
To answer your original question: the "commandToServer()" script function is simply one implementation of a NetEvent. So yes, you can use them in C++ to send information through the network.

They are a tad more complicated to use in C++, but are a lot more flexible.

First you create a class that derives from NetEvent. Here's an example that sends a Point3F (this code doesn't need to go in a header: place it in the .cpp that will use, since nobody else needs to know about this class).

class MyPositionEvent : public NetEvent
{   
   typedef NetEvent Parent;  
   
   Point3F position;
public:
   MyPositionEvent(Point3F pos)
   { 
      position = pos;
   }   
   void pack(NetConnection* conn, BitStream *bstream)
   {
      bstream->write(&position.x);
      bstream->write(&position.y);
      bstream->write(&position.z);
   }
   void write(NetConnection* conn, BitStream *bstream)
   {
      pack(conn, bstream);
   }
   void unpack(NetConnection* conn, BitStream *bstream)
   {
      bstream->read(&position.x);
      bstream->read(&position.y);
      bstream->read(&position.z);
   }
   void process(NetConnection *conn)
   {
      //Call a script method on the connection and pass the position
      String posString = String::ToString("%g %g %g", position.x, position.y, position.z);
      Con::executef(conn, "onPositionEvent", posString.c_str());
   }

   DECLARE_CONOBJECT(MyPositionEvent);
};

IMPLEMENT_CO_NETEVENT_V1(MyPositionEvent); //This line must go in a .cpp!

Now, to send the event, you call this:
NetConnection *conn = NetConnection::getConnectionToServer();
if (conn)
   conn->postNetEvent(new MyPositionEvent(position));
Where "position" is the position you want to send. "pack" is called on the sender's side while "unpack" and "execute" are called on the receiver's side.
#5
01/13/2010 (2:40 pm)
That's very useful, and much cleaner than what I've been doing. Good to know.
#6
01/13/2010 (9:46 pm)
Thank you all very much. Very appreciated.
#7
04/19/2010 (12:35 pm)
Great explanation. I have a question about a use-case though:

I have an external library that calls a method on a client object. Within this method, I need to affect a call of a method on the server version of this object. It sounds like I should pass a NetEvent, but what would I do in NetEvent::process to get a pointer to the object that I'm targeting?

Thanks
#8
04/21/2010 (11:54 am)
fyi, there are different macros for unidirectional events, and they are cleverly (ha!) named...
http://www.torquepowered.com/community/forums/viewthread/2701/1#comment-742867