Part two of the series handles sending data between CSQC and SSQC.
void () CSQC_Parse_Event;
This function runs when the CSQC receives a message (an SVC_CGAMEPACKET) from the SSQC. The way in which Scout’s Journey sends messages to the CSQC is via multicast(), and I’m afraid this is going to be FTE specific.
When do you want to send data to the CSQC? For example, imagine the player picks up some item (SSQC), and the CSQC should display a pretty icon. CSQC needs to know what kind of item it is, and/or what image file to use for the icon. This can be extended to inventories and the like.
You need two parts to send and receive messages; a server side part (SSQC) and a client side part (CSQC).
Server side part
The server side part consists of a number of WriteByte() calls, which are quite common. You’ll want to send floats rather than strings here to keep the networking to a minimum. And in this case the WriteByte()s don’t use MSG_ALL or MSG_BROADCAST, instead they use MSG_MULTICAST.
The following is SSQC !!
- WriteByte (MSG_MULTICAST, SVC_CGAMEPACKET);
The SVC here basically tells the CSQC to run CSQC_Parse_Event().
- WriteByte (MSG_MULTICAST, 99);
The number 99 (as an example) should be an identifier to communicate *what* is actually being sent. SSQC and CSQC will need to know and agree on what this number means.
- WriteByte (MSG_MULTICAST, item);
Let “item” be the actual information (a float, ideally; if you have to send strings try and define constants for those strings in both SSQC and CSQC so you can refer to them as floats.)
- msg_entity = whom;
Let “whom” be the entity this message is meant for (the player/client, in this case.) “Whom” is a variable of type entity.
- multicast (whom.origin, MULTICAST_ONE_R);
This sends the message. Try to keep these messages as short as possible for better network performance.
Client side part
Let’s see how the CSQC handles the above message. We already know that this is done by CSQC_Parse_Event(). What this function needs to actually do is read the incoming bytes and interpret them, and then call some approproate action. It parses the SVC_CGAMEPACKET automatically (which makes it spring into action.) So the readbyte() stuff has to start with the second byte sent (the 99 in the example.)
This is CSQC !!
- local float ev = readbyte();
Parses the identifier byte into a variable (in this case, ev is now 99.)
- local float item;
- if (ev == 99) item = readbyte();
And we have the actual info – the “item” float. The transfer is complete. You can add more if()s for additional message identifiers.
Warning: If you transfer strings in this way, they will arrive as tempstrings. You will have to strzone() / strunzone() in those cases.
Anything that should be done upon the retrieval of this info can be called next: for example, you could let the code print some text with drawstring()
- drawstring (vector position, string text, vector size, vector RGB, float alpha, float drawflag);
or have it display an image with drawpic()
- drawpic (vector position, string pic, vector size, vector RGB, float alpha, float drawflag);
Those are just two simple possibilities.
void(string evname, string evargs, …) sendevent;
This CSQC function prods the SSQC to call the named function with the given arguments. So it allows you to send a couple of variables over to the server.
This one is easiest to explain with an example, but I’ll explain the arguments first.
The name of the event to be sent. This matters because this forms part of the corresponding SSQC function. It may be any descriptive string you can come up with.
This forms part of the corresponding SSQC function as well. It consists of a sequence of characters, where “f” stands for float or “s” stands for string. So if you want to send five floats (again floats are better to send than strings because strings are bigger), evargs would be “fffff”. This might seem weird at first, but there are reasons for it.
- Other arguments
The rest of the arguments to sendevent are the actual variables that you wish to send. If you specified five floats with “fffff”, you have to give five more arguments to sendevent, each of which is a float.
Client side part
Now for the example. Assume the player interacted with a CSQC GUI to put an item into a chest. Further assume that the SSQC handles all entities’ inventories, so CSQC has to transmit at least which item it is and what chest we’re talking about (there might be several.) This is very simplified of course. We’ll also assume that the game has some sort of item system that assigns a number to everything so we can do it all with floats instead of, say, netname strings. Let’s send an event to notify SSQC about what we just did.
- sendevent(“ItemStashed”, “ff”, which_item, what_chest);
As you can see, this event sends two floats and is named “ItemStashed”. So far, so good – CSQC has done its part.
Server side part
When prodded by a sendevent, the SSQC will look at the event’s name and the arguments sequence. It then attempts to call a function whose name is assembled from both and starts with “Cmd”.
- void (float which_item, float what_chest) Cmd_ItemStashed_ff;
I hope you can see where this SSQC function takes its name: Cmd_(evname)_(evargs). It will expect the two floats as arguments. It must have this form in order to work at all.
In our example, this function would handle the dirty inventory stuff (such as remove the item from the player, add it to the chest etc.) It might also animate the chest model or play a sound, or whatever else you can come up with.
Note: In the server side part of this transfer, “self” is the entity where the sendevent came from, ie. the player whose client did the sending. So to remove the hypothetical item from the player, it would be removed from “self.”
This is a custom function that must have originated in one of the relatively early CSQC sources (Ender’s, Dresk’s or Arkage’s) and was handed down from there. If you came up with it, please comment so I can give you credit. Anyway, it retrieves stats from the SSQC. This is the way to share e.g. health, ammo count, armour, and so on.
updateStats() or any similar function should be called from CSQC_UpdateView() so it runs every frame.
Server side part
SSQC has to expose any stats that should be shared in worldspawn(). Globals are exported via globalstat() and entity fields via clientstat(). This is SSQC:
- globalstat(34, EV_STRING, “surname”);
- clientstat(36, EV_FLOAT, armortype);
A lot of stats are predefined in fteextensions.qc (such as health.) Those don’t need to be listed in worldspawn.
Client side part
CSQC can parse those stats in updateStats().
- surname = getstats(34);
- armortype = getstatf(36);
These are the ones from worldspawn. getstats() handles strings, getstatf() handles floats. Note that “surname” will be a tempstring, so consider strzone().
- health = getstatf(STAT_HEALTH);
This one is predefined in fteextensions.qc, so we can just grab it from CSQC.
In part 3, I will deal with sharing entities.