This client-server stuff is killing me...
by Dumbledore · in Torque Game Engine · 06/15/2008 (1:12 pm) · 12 replies
In code I can understand it pretty well, but scripting is confusing me.
If I make a script datablock method in game.cs which is executed on the server does this method exist on the client as well? My understanding is that it probably does.
#1 can sitOnToilet be called from the client side script?
If I wanted Fred to do something every second on the client side, like for instance, if I wanted to check if anybody was near me before I sat down on the toilet (but some people might be invisible, so invisible that they might not even be ghosted to me for security's sake). So I basically want to check if any ghosts are near me (that is, any objects that have been ghosted to this client) so I'd like to do it client side.
I would schedule sitOnToilet to repeat every second to keep checking if any ghosts are near.
#2 If left like this, is sitOnToilet() a method that is being called on the server?
#3 If the answer to #1 is yes, how would I call sitOnToilet() on the client?
Thanks for reading this lengthy post. And thanks even more for answers!
If I make a script datablock method in game.cs which is executed on the server does this method exist on the client as well? My understanding is that it probably does.
//server/scripts/game.cs
datablock PlayerData(Fred) {};
function Fred::sitOnToilet() { // sits down on toilet }#1 can sitOnToilet be called from the client side script?
If I wanted Fred to do something every second on the client side, like for instance, if I wanted to check if anybody was near me before I sat down on the toilet (but some people might be invisible, so invisible that they might not even be ghosted to me for security's sake). So I basically want to check if any ghosts are near me (that is, any objects that have been ghosted to this client) so I'd like to do it client side.
//server/scripts/fred.cs
function Fred::onAdd(%this, %obj)
{ %obj.sitOnToilet(); } // dont worry about syntaxI would schedule sitOnToilet to repeat every second to keep checking if any ghosts are near.
#2 If left like this, is sitOnToilet() a method that is being called on the server?
#3 If the answer to #1 is yes, how would I call sitOnToilet() on the client?
Thanks for reading this lengthy post. And thanks even more for answers!
#2
First you'd make sitOnToilet a server command:
function serverCmdSitOnToilet(){}
Then from your client you'd call:
commandToServer('SitOnToilet');
Having the player do something client side is going about it the wrong way I think. You'd save yourself a lot of headaches if you just checked for objects server-side. onAdd doesn't happen client-side.
EDIT: Oh, and if you want to make a client do something:
function clientCmdDoSomething(){}
commandToClient(%client, 'DoSomething');
I believe command names should be tagged strings, though I'm not 100% sure.
06/15/2008 (3:24 pm)
CommandToClient and commandToServer is what you're looking for.First you'd make sitOnToilet a server command:
function serverCmdSitOnToilet(){}
Then from your client you'd call:
commandToServer('SitOnToilet');
Having the player do something client side is going about it the wrong way I think. You'd save yourself a lot of headaches if you just checked for objects server-side. onAdd doesn't happen client-side.
EDIT: Oh, and if you want to make a client do something:
function clientCmdDoSomething(){}
commandToClient(%client, 'DoSomething');
I believe command names should be tagged strings, though I'm not 100% sure.
#4
06/15/2008 (4:36 pm)
Really? How so?
#5
06/15/2008 (6:00 pm)
Some objects issue an onAdd() script call when instanciated client-side and some don't. it's a bit inconsistent. if you want it for a particular class, it's easy to add.
#6
06/15/2008 (6:54 pm)
Okay I think it probably would be best if I did it server side. When I use the containerSearchNext() it returns a SimObjectId. I believe that would be the id of the object on the server? How do I attain a reference to the actual object from it's SimObjectId on the server?
#7
I have another question. Is there a difference between a ghost id and a ghost index?
%obj.getGhostID() -> %obj.getGhostIndex()
I've seen stuff like this done before:
resolveObjectFromGhostIndex(%obj.getGhostID());
06/15/2008 (7:22 pm)
It seems the answer to my above question is "Sim::findObject()". I have another question. Is there a difference between a ghost id and a ghost index?
%obj.getGhostID() -> %obj.getGhostIndex()
I've seen stuff like this done before:
resolveObjectFromGhostIndex(%obj.getGhostID());
#8
06/16/2008 (7:09 am)
As long as you're doing this server-side I think you can just use the object ID your container search returns as a reference.
#9
This function is intended to be called server-side in order to get a ghost index to be sent from the server to the client. So, this function gets a Ghost Index for some server-side object, relative to some client connection. This ID can then be sent to the Client, where the client uses the function resolveGhostID to convert the Ghost ID (or Index) into the Sim ID of the object on the client.
06/16/2008 (10:58 am)
Quote:Okay I think it probably would be best if I did it server side. When I use the containerSearchNext() it returns a SimObjectId. I believe that would be the id of the object on the server? How do I attain a reference to the actual object from it's SimObjectId on the server?Are you doing the search from within a C++ function? If you're calling the containerRadiusSearch from script, once you have the SimID of an object found, you don't need to do anything else to "attain a reference." For TorqueScript purposes, the SimID or the object Name is all the reference you need. If you're working inside C++ then yes, Sim::findObject is what you need.
Quote:I have another question. Is there a difference between a ghost id and a ghost index?The short answer is: no, not as far as the script functions are concerned. getGhostId() can be called as a method of either a GameConnection object or a NetObject. In both cases you are actually getting the ghost index.
ConsoleMethod( NetObject, getGhostID, S32, 2, 2, "")
{
return object->get[b]NetIndex();[/b]
}The version that is a method of NetObject is intended to be called client-side to obtain the Ghost Index of that object from this client's perspective. That index can then be sent to the server and the server can use it to get the "real" server-side SimID through the function resolveObjectFromGhostIndex(). Make sure you're not calling getGhostId on the server-side as you may get strange results, or a crash.ConsoleMethod(NetConnection, getGhostID, S32, 3, 3, "( S32 realID ) Convert a real id to the ghost id for this connection.")
{
NetObject * foo;
if(Sim::findObject(argv[2], foo))
{
return object->get[b]GhostIndex[/b](foo);
}
else
{
Con::errorf("NetConnection::serverToGhostID - could not find specified object");
return -1;
}
}This function is intended to be called server-side in order to get a ghost index to be sent from the server to the client. So, this function gets a Ghost Index for some server-side object, relative to some client connection. This ID can then be sent to the Client, where the client uses the function resolveGhostID to convert the Ghost ID (or Index) into the Sim ID of the object on the client.
#10
When the object's properties are done being sent to the client, it will create that object on the client simulation and assign it a different local SimID. This is really important to understand: the SimID of an object on the client is almost never the same as the SimID of the same object on the Server (with the exception of a Datablock object, but ignore that for right now). However, when the Client receives that ghosted object and creates it, it also keeps track if its SimID inside of an array local to that GameConnection: mLocalGhosts. An object that is ghosted is guaranteed to have the same Index in both of these arrays.
So, let's follow and example. We have some object created on the server and has a SimID = 3542. That object is then ghosted to a client in index position 211. Once ghosting is done the object is created on the client and given SimID = 2251. The most common case where this whole ghost index resolution comes in (that I have seen) is in object selection/interaction. So let's say you have a feature where the player can click on some object to interact with it. You handle the mouse click on the Client (like you should), and do a ray cast to find out what object was hit. Your code tells you that it was an object with SimID = 2251. Now you need to tell the server that you want to "interact" with this object, that sort of thing is handled on the Server.
If you were to simply send an "interact" request to the server and pass SimID = 2251 you would get a completely different object. So that's where Ghost ID's (indexes) come in. On the client you call:
Now on the server, you can resolve that ghost index to the the Sim ID of that object on the server:
Now, let's look at this from the flip-side. Let's say you have a function in your game where players in some sort of "party" or "group" can share target information, like that "Attack my target!" thing you see in a lot of online RPG's. When those other party members accept the first guy's target, you'll need to send them the ID of the object that they are now targetting so that their client-side display can update itself (perhaps to show target info or some such). So, let's say that first client has targeting an object and we now have the server-side ID of that target, using a method very similar to what is shown above. Let's also say that the other members of the group have received the "Attack my target?" message and accepted, so now we can handle that in a function as follows:
Alright, I hope that helps everyone who's had a question about that whole GhostID/GhostIndex thing.
EDIT: Fixed a bug in the code, just in case anyone is copying and pasting.
06/16/2008 (12:04 pm)
I realize that last post may seem like it's talking in circles a little bit so I'll back up a moment. There is a fundamental difference between a SimID and a GhostID (or Ghost Index). The Server (obviously) "knows" about all "important" objects that exist in the game world. Through a process called scoping it determines which objects are interesting to a particular client and marks those to be ghosted to that client. When an object gets ghosted, it's ID gets stuffed into an array (mGhostRefs) that is a member of that client's GameConnection object. This array keeps track of which objects have been ghosted to this client. When the object's properties are done being sent to the client, it will create that object on the client simulation and assign it a different local SimID. This is really important to understand: the SimID of an object on the client is almost never the same as the SimID of the same object on the Server (with the exception of a Datablock object, but ignore that for right now). However, when the Client receives that ghosted object and creates it, it also keeps track if its SimID inside of an array local to that GameConnection: mLocalGhosts. An object that is ghosted is guaranteed to have the same Index in both of these arrays.
So, let's follow and example. We have some object created on the server and has a SimID = 3542. That object is then ghosted to a client in index position 211. Once ghosting is done the object is created on the client and given SimID = 2251. The most common case where this whole ghost index resolution comes in (that I have seen) is in object selection/interaction. So let's say you have a feature where the player can click on some object to interact with it. You handle the mouse click on the Client (like you should), and do a ray cast to find out what object was hit. Your code tells you that it was an object with SimID = 2251. Now you need to tell the server that you want to "interact" with this object, that sort of thing is handled on the Server.
If you were to simply send an "interact" request to the server and pass SimID = 2251 you would get a completely different object. So that's where Ghost ID's (indexes) come in. On the client you call:
// %hitObject contains id 2251
%hitGhostId = %hitObject.getGhostId();
// %higGhostId will contain "211" in our example
CommandToServer('InteractWithObject', %hitGhostId);Now on the server, you can resolve that ghost index to the the Sim ID of that object on the server:
function ServerCmdInteractWithObject(%client, %ghostID) {
// %ghostID will contain "211" from our example
%realObject = %client.resolveObjectFromGhostIndex(%ghostID);
// %realObject will now contain 3542, which is a Server-side SimID
%realObject.doSomeInteraction();
}Now, let's look at this from the flip-side. Let's say you have a function in your game where players in some sort of "party" or "group" can share target information, like that "Attack my target!" thing you see in a lot of online RPG's. When those other party members accept the first guy's target, you'll need to send them the ID of the object that they are now targetting so that their client-side display can update itself (perhaps to show target info or some such). So, let's say that first client has targeting an object and we now have the server-side ID of that target, using a method very similar to what is shown above. Let's also say that the other members of the group have received the "Attack my target?" message and accepted, so now we can handle that in a function as follows:
function sendTargetToGroupMembers(%group, %target) {
for(%i = 0; %i < %group.getCount(); %i++) {
%client = %group.getObject(%i);
%clientGhostIndex = %client.getGhostId(%target);
CommandToClient(%client, 'TargetObject', %clientGhostIndex );
}
}See how the above code (running on the server) gets the Ghost ID relative to a particular client? That's really important. On the client-side, we can (in a similar fashion as above) resolve that Ghost ID into a Sim ID that is actually on the client:function clientCmdTargetObject(%targetGhostID) {
%objectID = ServerConnection.resolveGhostID(%targetGhostID);
changeTargetObject(%objectID);
}Note that the naming the client connection to the server ServerConnection is simply a Torque convention and might be different in your code. If you don't remember changing that, you probably haven't. ;)Alright, I hope that helps everyone who's had a question about that whole GhostID/GhostIndex thing.
EDIT: Fixed a bug in the code, just in case anyone is copying and pasting.
#11
In your 3rd code block the 5th line should read:
06/17/2008 (2:56 pm)
Thanks Mark Dynna I'm sure many people will appreciate this. In your 3rd code block the 5th line should read:
CommandToClient(%client, 'TargetObject', %clientGhostIndex);
#12
06/17/2008 (3:25 pm)
Good catch, fixed the example. Also, you're welcome.
Torque Owner Stefan Lundmark
In conclusion, you are not able to execute functions that are on the server, from the client. It might seem like you can when you're running a listening server, since both client and server are on the same simulation, but it's not something you should get used to.