Forum

how to use <insert here> in CSQC

Discuss CSQC related programming.

Moderator: InsideQC Admins

how to use <insert here> in CSQC

Postby gnounc » Fri Jan 23, 2015 12:22 pm

Theres been a fair amount of "how do I USE this function" going on lately, and I thought this thread would be a great way to clear some of that up!

So if you use CSQC and find yourself having to ask how to use a function because you cant find any doxx, run back here and drop off
what you learn!

To start things off shpuld and I will cover the most used basic functions, from there I hope this thread becomes the place to go for a real quick
"how do I?"

NOTE: for anyone posting, I'd love it if you guys threw the functions prototype up in a codeblock up top so that the readers can instantly see what arguments it takes.
thx :)
Last edited by gnounc on Sun Jan 25, 2015 11:21 am, edited 2 times in total.
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby gnounc » Fri Jan 23, 2015 12:23 pm

First things first.

Code: Select all
   noref void(float vwidth, float vheight, float notmenu) CSQC_UpdateView;   /* Called every single video frame. The CSQC is responsible for rendering the entire screen. */



If you're going to be using csqc, you're probably going to be doing a bit of 2d drawing.
For that you will want to set up your screen size, and probably screen center globals.

In fte and dp both, you'll want to do this in CSQC_UpdateView();

In fte its as simple as declaring a global screen_size in your defs,
and feeding it the parameters of CSQC_UpdateView vwidth and vheight

screen_center then of course, would be half of that.
or
Code: Select all
   screen_center = [screen_size_x/2, screen_size_y/2];


Darkplaces is slightly different. The parameters to CSQC_UpdateView in Darkplaces
give you virtual pixels. If you try to position things the way you would in fte using the method above, things
will look wonky.

Instead we use cvars.

Code: Select all
   screen_size_x = cvar("vid_conwidth");
   screen_size_y = cvar("vid_conheight");


now you can
Code: Select all
   drawstring(vector position, string text, vector size, vector rgb, float alpha, float drawflag);
   drawpic(vector position, string pic, vector size, vector rgb, float alpha, optional float drawflag);

and
Code: Select all
   drawfill(vector position, vector size, vector rgb, float alpha, optional float drawflag);


to the topleft corner with position vector '0 0'
or to the bottomleft corner with screen_size. (remembering of course to subtract the size of the image from its position,
or it will be drawn offscreen)
Last edited by gnounc on Sat Jan 24, 2015 7:18 am, edited 1 time in total.
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby Shpuld » Fri Jan 23, 2015 1:14 pm

First I'd like to point out that the stuff I'm gonna write is FTEQW ONLY. Darkplaces doesn't have multicasts or sendevents, in DP you can hack temp entities and sv_parseclientcommand.

Your game/mod is most likely going to need the client and server parts to work together, this requires information exchange between those 2, which in our case are of course CSQC and SSQC.
A lot of this stuff happens automatically in the engine, for example calling 'AddEntities' will make the client know about the entities that are sent by the server in the existing protocol already.

Now let's say you want to do some client<->server communication manually. CSQC provides good ways to do this.
For server -> client I generally like to use a multicast in SSQC and then handle it client-side in CSQC_Parse_Event. You get to decide exactly what you send and how it's interpreted. Very nice.

Example: We want to know client-side when we touch a thing serverside and we also want to get the things classname and origin for some reason.

In SSQC we would have a .touch function for the entity that looks like this:

Code: Select all
const float EVENT_THINGTOUCH = 123; //you probably wanna keep lines like this in defs or something

void() thing_touch =
{
   if(other.classname != "player")
      return;

   msg_entity = other; //make the client who touches this thing to be the receiver
   WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); //when sending a SVC_CGAMEPACKET we can handle it clientside, note: it has to be a MSG_MULTICAST.
   WriteByte(MSG_MULTICAST, EVENT_THINGTOUCH); //it's smart to use the first byte of the actual message as an identifier, and it's also smart to name them.
   WriteString(MSG_MULTICAST, self.classname);
   WriteCoord(MSG_MULTICAST, self.origin_x); //sending vectors unfortunately requires 3 lines instead of a handy WriteVector
   WriteCoord(MSG_MULTICAST, self.origin_y);
   WriteCoord(MSG_MULTICAST, self.origin_z);
   multicast('0 0 0', MULTICAST_ONE); //send the message only to the dude that touched it, there's plenty of other ways to send these messages
}


I'd like to elaborate a bit on the last line before moving on to the client-side part. The first argument is a position where the message comes from, which is not useful when doing a direct MULTICAST_ONE to a single client, but it's crucial when you wanna do stuff like sending the message to all clients in the same Potentially Visible Set (PVS) as our self.origin for example.
Here are all the MULTICAST_* options and short documentation by Spike himself:

Code: Select all
const float MULTICAST_ALL = 0;   /* The multicast message is unreliably sent to all players. MULTICAST_ constants are valid arguments for the multicast builtin, which ignores the specified origin when given this constant. */
const float MULTICAST_PHS = 1;   /* The multicast message is unreliably sent to only players that can potentially hear the specified origin. Its quite loose. */
const float MULTICAST_PVS = 2;   /* The multicast message is unreliably sent to only players that can potentially see the specified origin. */
const float MULTICAST_ONE = 6;   /* The multicast message is unreliably sent to the player specified in the msg_entity global. The specified origin is ignored. */
const float MULTICAST_ALL_R = 3;   /* The multicast message is reliably sent to all players. The specified origin is ignored. */
const float MULTICAST_PHS_R = 4;   /* The multicast message is reliably sent to only players that can potentially hear the specified origin. Players might still not receive it if they are out of range. */
const float MULTICAST_PVS_R = 5;   /* The multicast message is reliably sent to only players that can potentially see the specified origin. Players might still not receive it if they cannot see the event. */
const float MULTICAST_ONE_R = 7;   /* The multicast message is reliably sent to the player specified in the msg_entity global. The specified origin is ignored */



Now let's get to the client-side handling for the event, it happens in CSQC_Parse_Event, a built-in entry point.

Code: Select all
const float EVENT_THINGTOUCH = 123; //make sure this matches between client and server

//function is called whenever the client receives a SVC_CGAMEPACKET
noref void() CSQC_Parse_Event =
{
   local float first = readbyte(); //we read the first byte after the initial SVC_CGAMEPACKET, in our case it's 123, or EVENT_THINGTOUCH
   if(first == EVENT_THINGTOUCH)
   {
      local vector pos;
      local string thing_msg;
      //we proceed to read all the information sent server-side, make sure you do it in the same order as it was sent
      //IMPORTANT: make sure you read EVERYTHING you sent in the packet
      //if you send more stuff than you read here, the engine is going to crash, which is not the preferred outcome usually
      //in case you have a problem with this and need to find it, you can use sv_csqcdebug to help debugging

      thing_msg = readstring();
      pos_x = readcoord();
      pos_y = readcoord();
      pos_z = readcoord();

      print(strcat("You touched a thing that says: ", thing_msg, " and is located at " vtos(pos) ". \n"));
   }
   else if(first == EVENT_SOMETHINGELSE)
   {
      ...
   }
}


Now this example isn't very useful but I hope you get the idea.

In the next post I'll tell how to tell the server to do things from the client using sendevent.
Last edited by Shpuld on Sat Jan 24, 2015 11:23 am, edited 1 time in total.
User avatar
Shpuld
 
Posts: 73
Joined: Sat Feb 13, 2010 1:48 pm

Re: how to use <insert here> in CSQC

Postby Shpuld » Fri Jan 23, 2015 1:43 pm

Let's go through one way of doing client -> server communication.
Like mentioned in the last post I'll go through sendevent:

Code: Select all
void(string evname, string evargs, ...) sendevent = #359; /*
      Invoke Cmd_evname_evargs in ssqc. evargs must be a string of initials refering to the types of the arguments to pass. v=vector, e=entity(.entnum field is sent), f=float, i=int. 6 arguments max - you can get more if you pack your floats into vectors. */


So basically with this we can call our custom functions in SSQC from CSQC.

Example time: let's say we have Deus Ex -style numpads in our game, touching/using them opens a nice CSQC based numpad GUI on the screen, we input the code and want to let the server know what kind of bollocks we typed in.

CSQC:

Code: Select all
//let's imagine this function gets called when clicks on the "Enter" button on the UI
void() numpad_send =
{
   local string code; //we somehow set this to what the user has inputted, not the focus of this post
...
   sendevent("checkcode", "s", code);    
   //we're sending only one arg here, if you want to send more and different types it would work like this:
   //sendevent("checkcode", "svf", code, somevector, somefloat);
   //of course this will call a different function in server-side because of the different inputs
}


Now let's get into the SSQC:

Code: Select all
//the name of this function is important, it has to match with the stuff we've done in CSQC, if we had the previous example with more arguments, it would have to look like this
//void(string code, vector somevector, float somefloat) Cmd_checkcode_svf =
//and this of course is a different function already

//self will also be the client that sent the event, so we can easily give him feedback here
void(string code) Cmd_checkcode_s =
{
   if(code != "1337")
      sprint(self, PRINT_HIGH, "Wrong code you dumbo!\n");
   else
   {
      sprint(self, PRINT_HIGH, "You are truly one with the 1337 h4x0rZ\n");
   }
}



Quite simple, isn't it? Now you should have a good knowledge of how to do some basic information exchange between client and server with the UNLIMITED POWER of CSQC.
User avatar
Shpuld
 
Posts: 73
Joined: Sat Feb 13, 2010 1:48 pm

Re: how to use <insert here> in CSQC

Postby gnounc » Sat Jan 24, 2015 11:23 am

Now on to some basic string handling.
strings are important for a variety of reasons, one of the most obvious, is constructing and deconstructing filepaths

most recently i've been using a custom save stuffcmd, adding things like the players name, mapname, kills and secrets right into the save filename
so i can parse it out later.

to concatenate strings together, we use strcat(); strcat takes up to 8 arguments, but all it does is smoosh the strings together.
on the ssqc side, ive been doing something like
Code: Select all
   path = strcat(name, "\", mapname, "\", kills, "-", secrets);

to construct the save paths in the folders gnounc\e1m1 for example
with a file name of 5-4.sav

then I use tokenizebystring(); to get the information back out in csqc like so
Code: Select all
   idx = tokenizebystring(path, "\", "-");


I believe tokenize does the same job, with all the obvious delimiters assumed, so use tokenize by string if you DONT want to split by \ or - or .
tokenize and tokenizebystring both return the number of items the string was split into
and populate argv();

Code: Select all
   name = argv(0);
   mapname = argv(1);
   etc...


or of course, you could just use a for loop, and the idx that was returned by tokenize
Code: Select all
   for(x = 0; x < idx - 1; x++)
   {
      print(argv(idx);
   }


really quickly, a few other useful string functions are
Code: Select all
   strlen(string s); which returns the number of characters in the string,

really useful for positioning string elements on screen.

Code: Select all
   float stringwidth(string text, float allowColorCodes, vector size) = #327; // get a width of string with given font and char size

returns the size the string will appear on screen..horizontally obv

and lastly, if your temporary strings are getting garbled, being blank, or anything like that try strzone()
it returns a string that is protected from being freed.
which of course means you'll need to free it yourself when you're finished with ti

Code: Select all
   stringVar = strzone("i'd like to preserve this string so i can use it in five minutes please");

and
Code: Select all
   strunzone(stringVar);



addendum: apparently tokenizebystring is called
Code: Select all
   float(string s, string separator1, ...) tokenizebyseparator = #479;

in darkplaces.

..more to come
Last edited by gnounc on Sun Jan 25, 2015 9:46 am, edited 1 time in total.
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby Shpuld » Sat Jan 24, 2015 12:15 pm

If you want to do manipulation in CSQC for certain entities that share a model, for example players, you can use deltalisten().

Code: Select all
float(string modelname, float(float isnew) updatecallback, float flags) deltalisten = #371; /*
      Specifies a per-modelindex callback to listen for engine-networking entity updates. Such entities are automatically interpolated by the engine (unless flags specifies not to).
      The various standard entity fields will be overwritten each frame before the updatecallback function is called. */


I'll be using an example where we scale players up and down in CSQC.
EDIT: originally I said call deltalisten every frame, which is not required.
We call the deltalisten() in CSQC_Init, and for every entity with the model "progs/player.mdl" the specified listen function will be called every frame.

Code: Select all
noref void(float apiver, string enginename, float enginever) CSQC_Init =
{
   ...
   deltalisten("progs/player.mdl", our_custom_listen, 0);
   ...
}


Now we need to define our_custom_listen. I just put it above the CSQC_UpdateView function.

Code: Select all
float(float isnew) our_custom_listen =
{
   //this prints when you "see" an ent for the first time, or when it leaves PVS and enters it again.
   if(isnew)
      print("I am new\n");

   self.scale = 1 + (0.5 * sin(time * 4));
   return 1;
};


Note, you want to make sure you have CSQC_Ent_Remove defined, you're gonna get null function calls otherwise whenever the object is removed from client, like when leaving PVS.
You probably want it to look like this for most cases:

Code: Select all
void() CSQC_Ent_Remove =
{
   remove(self);
};
User avatar
Shpuld
 
Posts: 73
Joined: Sat Feb 13, 2010 1:48 pm

Re: how to use <insert here> in CSQC

Postby gnounc » Sun Jan 25, 2015 9:37 am

Now it's time to talk about one of the two functions that inspired this thread.
I found
Code: Select all
   void() calltimeofday = #231; /* Part of FTE_CALLTIMEOFDAY*/

after speaking with spike and finding a page on the linux system call, I had it all sorted out.

calltimeofday fills a global (I believe) and calling

Code: Select all
   string(float uselocaltime, string format, ...) strftime = #478; /* Part of DP_QC_STRFTIME*/


checks it, and returns the string in the format given as an argument

the applicable string formats can be found in the linux manpages, as strftime is a unix system call
http://linux.die.net/man/3/strftime

below is the call I've been using, remember you may need to strzone it, depending on how you use the string.
Code: Select all
   calltimeofday();
   tod = strftime(1, "%Y-%m-%d %H-%M-%S");
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby gnounc » Sun Jan 25, 2015 9:45 am

quick followup, as I believe calltimeofday is in fte and not darkplaces, you may desire to use
Code: Select all
   float(float tmr) gettime = #519;

instead, which will work in either engine.

the list of available arguments to gettime are
(taken straight from darkplaces copy of csprogsdef.qc

Code: Select all
   float GETTIME_FRAMESTART = 0; // time of start of frame
   float GETTIME_REALTIME = 1; // current time (may be OS specific)
   float GETTIME_HIRES = 2; // like REALTIME, but may reset between QC invocations and thus can be higher precision
   float GETTIME_UPTIME = 3; // time since start of the engine


gettime fills the same constants as calltimeofday, so simply stftime it the same as before
Code: Select all
   gettime(GETTIME_REALTIME);
   s_timeofday = strftime(1, "%Y-%m-%d_%H-%M-%S");


and done. remember to check your linux manpage for strftime use!
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby Spike » Sun Jan 25, 2015 10:12 am

Code: Select all
void(float sec, float min, float hour, float day, float mon, float year, string datestring) timeofday = //called by engine
{
//you're expected to write the stuff to some global and then just return so the caller can do stuff. this gets around the fact that qc cannot return values at once.
print(sprintf("sec=%g, min=%g, hour=%g\nday=%g,month=%g,year=%g\n\"%s\"\n", sec, min, hour, day, mon, year, datestring);
//note that the date string will be something like "Sun Jan 26, 10:07:21 2015". Why it uses that weird format I have no idea.
};
void() somefunction = //called from qc from elsewhere or whatever
{
calltimeofday(); //get the engine to invoke the timeofday function with the current date+time info.
}

obviously its kinda clumsy and not supported in dp, so just use strftime multiple times and hope the clock doesn't tick weirdly in the race condition between calls, or use substring to make it consistant or something.
Spike
 
Posts: 2859
Joined: Fri Nov 05, 2004 3:12 am
Location: UK

Re: how to use <insert here> in CSQC

Postby gnounc » Mon Jan 26, 2015 8:26 am

Ok, now for some basic file handling.
Today we'll be working with
Code: Select all
   float(string pattern, float caseinsensitive, float quiet) search_begin = #444; /* Part of DP_QC_FS_SEARCH*/
   float(float handle) search_getsize = #446; /* Part of DP_QC_FS_SEARCH*/
   string(float handle, float num) search_getfilename = #447; /* Part of DP_QC_FS_SEARCH*/
   void(float handle) search_end = #445; /* Part of DP_QC_FS_SEARCH*/


This is the last group of functions I have in my back pocket to do a showcase on. Its the other one I had to look up recently to figure out how to use.
I googled xonotic and nexuiz and search_begin to find a reference use.

search_begin's argument list is pretty self explainatory, with the exception that its first argument "pattern" is just filepath, except that it accepts wildcards */* and the like
search_begin returns a handle to the file list

search_getsize returns how many files were found and added to that list

search_getfilename takes the handle, and the index number of the list as arguments, and tells you the filename of that item

and lastly search_end takes the handle, and closes the file, freeing the memory.

put together it looks something like this. (note i'm using a nonstandard filepath.)
Code: Select all
void printSaves()
{
   string path;
   path = "saves/*/*.fsv";

   float globhandle, idx, fileCount;
   globhandle = search_begin(path, TRUE, FALSE);      //get the file handle

   //return so we dont try to access garbage data
   if (globhandle <= 0)
      return;

   fileCount = search_getsize(globhandle);      //get number of files
   for (idx = 0; idx < fileCount; ++idx)
      print(search_getfilename(globhandle, idx));   //print filenames

   search_end(globhandle);               //close the search
}
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby gnounc » Mon Jan 26, 2015 9:17 am

It dawned on me, that I cant very well show how to get a list of files
and not show how to use the most common reason for wanting a list of files.
Namely reading and writing to the files.
for this we will be using
Code: Select all
   float(string filename, float mode, optional float mmapminsize) fopen = #110; /* Part of FRIK_FILE*/
   string(float fhandle) fgets = #112; /* Part of FRIK_FILE*/
   void(float fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) fputs = #113; /* Part of FRIK_FILE*/
   void(float fhandle) fclose = #111; /* Part of FRIK_FILE*/


first up, fopen();
The filename argument is obvious. so we'll move on to the mode.
Mode is read, write, append, that sort of thing, for the opened file.
fopen() returns a filehandle, as you might've guessed
Code: Select all
   const float FILE_READ = 0;
   const float FILE_APPEND = 1;
   const float FILE_WRITE = 2;
   const float FILE_READNL = 4;
   const float FILE_MMAP_READ = 5;
   const float FILE_MMAP_RW = 6;


Write overwrites the file, append tacks your additions to the end of it.
not sure what memory map minimum size argument is for, but its optional, so I elect to ignore it.

fgets(), accepts the filehandle, and returns the first line of the file the first time you use it,
and the second line of the file the next, returning an empty string for a newline, and null for end of file.

fputs(), accepts the filehandle, and strings,
then shoves the strings specified in your argument into the file, remember that write overwrites, and append puts it at the bottom.

and fclose(), closes the file freeing the memory.

used altogether it would look something like this

Code: Select all
   float idx;
   filehandle = fopen(myFile, FILE_READ);

   string line;
   while(line = fgets(filehandle))
   {
      print(line);
   }

   fclose(filehandle);


or..

Code: Select all
   filehandle = fopen("playerList", FILE_WRITE);

   entity e;
   string line;
   for(e = find(world, classname, "player"); e ;e = find(e, classname, "player"))
   {
      fputs(filehandle, line);
   }

   fclose(filehandle);
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby gnounc » Sun Feb 08, 2015 12:55 am

Today we'll be learning to use draw rotated pic.
Code: Select all
   void(vector pivot, vector mins, vector maxs, string pic, vector rgb, float alpha, float angle) drawrotpic = #0;
      //Draws an image rotating at the pivot. To rotate in the center, use mins+maxs of half the size with mins negated. Angle is in degrees. */



drawrotpic is a brand new function in fteqw, as such, you'll need the latest version (NOT STABLE VERSION..thats insanely outdated)
the latest versions of fte eschew traditional builtin numbers, any builtin you see registered as #0 requires the latest fte, and wont
work in darkplaces.

drawrotpic does not use position and size arguments like drawpic does,
instead the mins is the origin position, and the maxs is the bottom right corner
a good way to do this is to use a variable named img_pos or something along those lines for the mins,
and for maxs, use img_pos + img_size.

the pivot vector is also in screenspace, and not relative to the image.
As noted in the builtin description, find the center vector for the image,
and use that as the pivot if you want the image to rotate around its middle like a turntable


if you'd rather it rotate out like a pocketknife, use the origin (or any corner really) of the image as the pivot instead.

thus we end up with some code like this

Code: Select all

      vector img_pos, img_size, img_color;
      string img_path;
      float img_alpha, img_angles;

      img_pos = '-32 -32';
      img_size = '64 64';
      img_color = '1 1 1';
      img_path = "gfx/icon";
      img_alpha = 1;
      img_angles = time*100;

      drawrotpic('0 0', img_pos, img_pos + img_size, img_path, img_color, img_alpha, img_angles);


and the effect, is an image mostly offscreen on the topleft (only the bottom right quadrant is visible),
that rotates like a turntable.
User avatar
gnounc
 
Posts: 424
Joined: Mon Apr 06, 2009 6:26 am

Re: how to use <insert here> in CSQC

Postby PrimalLove » Sat Feb 28, 2015 2:23 am

First want to start out by saying this is similar to Shplud's post about CSQC_Parse_Event. As far as I know this is what you would use for FTE instead of CSQC_Parse_TempEntity(). But with DP it seems we are stuck with this.. :( So I suppose think of this as a continuation of his post but more DP specific.

I recently had some misunderstandings about CSQC_Parse_TempEntity that was cleared up by Spike (Thanks again!) and I noticed little to no reference to this on the forums. Gnounc has a small explanation of it in his very helpful clean csqc but otherwise I rarely see it. Spike mentioned how evil it is but in any event I wanted to give hopefully a more thorough explanation of it its use with some examples. This will apply to both NQ or QW with slight differences in usage that will be noted. This will also assume you have all the appropriate and relevant builtin extensions defined.
Code: Select all
// CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer.
// You must ALWAYS first acquire the temporary ID, which is sent as a byte.
// Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event.


This does exactly what it sounds like. It takes write events from the server and handles them in CSQC.
So on the SSQC side we will look at one example in W_FireAxe. At the bottom of the function you will see something like this:

Code: Select all
NetQuake
   {   // hit wall
      sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
      WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
      WriteByte (MSG_BROADCAST, TE_GUNSHOT);
      WriteCoord (MSG_BROADCAST, org_x);
      WriteCoord (MSG_BROADCAST, org_y);
      WriteCoord (MSG_BROADCAST, org_z);
   }

QuakeWorld

   {       // hit wall
      sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);

      WriteByte (MSG_MULTICAST, SVC_TEMPENTITY);
      WriteByte (MSG_MULTICAST, TE_GUNSHOT);
      WriteByte (MSG_MULTICAST, 3);
      WriteCoord (MSG_MULTICAST, org_x);
      WriteCoord (MSG_MULTICAST, org_y);
      WriteCoord (MSG_MULTICAST, org_z);
      multicast (org, MULTICAST_PVS);
   }


You'll notice that QW has some differences. NQ does not support MSG_MULTICAST (Unless you use it in FTE : Credit Spike). MSG_BROADCAST is unreliable. In QW you can use MSG_MULTICAST which is better but still not perfect and its still an unreliable data stream(because it has to be). I should also note that you can still use MSG_BROADCAST in qw its just avoided. (Corrections made here thanks to Spike's input! :) )

The other thing to note is that the latter arugments in QW have two extra calls. The first is a count argument. In some cases (TE_BLOOD) the count argument will only accept 1 as a value. The last call is to let the engine know that this should only be sent to those that meet the requirements. In this case, only to players that can actually see the origin. This will additionally lower bandwidth usage and network traffic. Other than these differences, the code is essentially used the same way on both QW and NQ. (Corrections made here thanks to Spike's input! :) )
In QW particle() is not used so blood is also handled as a temp entity writebyte event. It looks like this:

Code: Select all
/*
================
SpawnBlood
================
*/
void(vector org, float damage) SpawnBlood =
{
   WriteByte (MSG_MULTICAST, SVC_TEMPENTITY);
   WriteByte (MSG_MULTICAST, TE_BLOOD);
   WriteByte (MSG_MULTICAST, 1);
   WriteCoord (MSG_MULTICAST, org_x);
   WriteCoord (MSG_MULTICAST, org_y);
   WriteCoord (MSG_MULTICAST, org_z);
   multicast (org, MULTICAST_PVS);
};


You can do this same thing in NQ source so that all of the blood calls will also be sent as temp entities. Here is an example:

Code: Select all
void(vector org, vector vel, float damage) SpawnBlood =
{
   damage = damage*2;
   vel = vel*0.1;
   WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
   WriteByte (MSG_BROADCAST, TE_BLOOD);
   WriteCoord (MSG_BROADCAST, org_x);
   WriteCoord (MSG_BROADCAST, org_y);
   WriteCoord (MSG_BROADCAST, org_z);
   WriteCoord (MSG_BROADCAST, vel_x);
   WriteCoord (MSG_BROADCAST, vel_y);
   WriteCoord (MSG_BROADCAST, vel_z);
   WriteByte (MSG_BROADCAST, damage);
   //particle (org, vel*0.1, 73, damage*2); // reference....
};


This is how many if not most of the particle effects are handled in standard NQ and QW game source. For the most part nothing to change on the SSQC side of things. Here are the constant float values you will need in both SSQC and CSQC to use this builtin CSQC Temp Entity Parse function. (Note: this could be different depending on the source the important thing is they match on both sides)

Code: Select all
NQ:
float   TE_SPIKE      = 0;
float   TE_SUPERSPIKE   = 1;
float   TE_GUNSHOT      = 2;
float   TE_EXPLOSION   = 3;
float   TE_TAREXPLOSION   = 4;
float   TE_LIGHTNING1   = 5;
float   TE_LIGHTNING2   = 6;
float   TE_WIZSPIKE      = 7;
float   TE_KNIGHTSPIKE   = 8;
float   TE_LIGHTNING3   = 9;
float   TE_LAVASPLASH   = 10;
float   TE_TELEPORT      = 11;
float TE_BLOOD = 50; // IF using Dpextensions this is already defined as 50 so I used it here for the example below.

QW:
float   TE_SPIKE                = 0;
float   TE_SUPERSPIKE   = 1;
float   TE_GUNSHOT              = 2;
float   TE_EXPLOSION    = 3;
float   TE_TAREXPLOSION = 4;
float   TE_LIGHTNING1   = 5;
float   TE_LIGHTNING2   = 6;
float   TE_WIZSPIKE             = 7;
float   TE_KNIGHTSPIKE  = 8;
float   TE_LIGHTNING3   = 9;
float   TE_LAVASPLASH   = 10;
float   TE_TELEPORT             = 11;
float   TE_BLOOD                = 12;
float   TE_LIGHTNINGBLOOD = 13;


It may be helpful to place these in a shared.qc file so you can use one file for both ssqc and csqc defs. That is up to you as long as they are defined in both. Now in your main.qc or a new qc file you will want to place this to start:
(NOTE: Please note ReadCoord is the same as readcoord in FTE. The builtin numbers are what is important so look to them for references if needed the rest that follows assumes DP. The method used by Shplud above is as far as I know standard practice for FTE using Use CSQC_Parse_Event and Multicasts.)
Code: Select all
float CSQC_Parse_TempEntity()
{
   float Handled, damage;
   vector pos, vel;
   Handled  = TRUE;
   float TEID = ReadByte();   
   switch(TEID)
   {
      case TE_SPIKE:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_SPIKE"), pos, '100 0 1', 1);
         Handled = TRUE;
         break;
      case TE_GUNSHOT:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_GUNSHOT"), pos, '0 0 1', 1);
         Handled = TRUE;
         break;
      case TE_SUPERSPIKE:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_SUPERSPIKE"), pos, '300 0 1', 3);
         Handled = TRUE;
         break;
      case TE_EXPLOSION:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_EXPLOSION"), pos, '0 0 1', 1);
         Handled = TRUE;
         break;
      case TE_TAREXPLOSION:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_TAREXPLOSION"), pos, '0 0 1', 1);
         Handled = TRUE;
         break;   
      case TE_LIGHTNING1:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_LIGHTNING1"), pos, '0 0 1', 1);
         Handled = TRUE;
         break;   
      case TE_LIGHTNING2   :
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_LIGHTNING2"), pos, '0 0 0', 1);
         Handled = TRUE;
         break;   
      case TE_WIZSPIKE:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_WIZSPIKE"), pos, '0 0 0', 1);
         Handled = TRUE;
         break;
                case TE_KNIGHTSPIKE:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_KNIGHTSPIKE"), pos, '0 0 0', 1);
         Handled = TRUE;
         break;
      case TE_LIGHTNING3   :
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_LIGHTNING3"), pos, '0 0 0', 1);
         Handled = TRUE;
         break;   
      case TE_LAVASPLASH:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_LAVASPLASH"), pos, '0 0 0', 1);
         Handled = TRUE;
         break;
      case TE_TELEPORT:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_TELEPORT"), pos, '0 0 0', 1);
         Handled = TRUE;
         break;   
      case TE_BLOOD: // An additional example showing the usage of additional arguments
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         vel_x = ReadCoord();
         vel_y = ReadCoord();
         vel_z = ReadCoord();
         damage = ReadByte();
         pointparticles(particleeffectnum("TE_BLOOD"), pos, vel, damage);
         Handled = TRUE;
         break;   
      default:
         Handled = FALSE;
         break;
   }   
   return Handled;
}


The important thing to remember here is, if this function returns TRUE you must read all values sent by the writebyte event otherwise a crash will ensue. You also need to read them in the order they are sent. Lets take TE_GUNSHOT for an example:

Code: Select all
               WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); // This tells the server to send a tempentity event (This is what the function looks for)
      WriteByte (MSG_BROADCAST, TE_GUNSHOT); // This tells the server the name of the event to sent (This is read first by the function at the top (TEID))
      WriteCoord (MSG_BROADCAST, org_x); // The coords need to be read in this same order from x to z.  The same goes for any additional writebytes, writeshorts, or writecoords you do additionally.
      WriteCoord (MSG_BROADCAST, org_y);
      WriteCoord (MSG_BROADCAST, org_z);


You'll notice this only takes care of the particle event itself. If you also want it to replace the sound event as well you can also do this using pointsound(). So:

Code: Select all
case TE_GUNSHOT:
         pos_x = ReadCoord();
         pos_y = ReadCoord();
         pos_z = ReadCoord();
         pointparticles(particleeffectnum("TE_GUNSHOT"), pos, '0 0 1', 1);
            pointsound(pos, "player/axhit2.wav", 1.0, ATTN_NORM);
         Handled = TRUE;
         break;


And on the ssqc side just comment out the sound() call. Pointsound does not rely on Channels but its attenuates differently from what I can tell from other types of sounds. You can adjust this in DP with snd_soundradius to get better results. You can use any custom effect you have made for any of these particles and put them as your particleeffectnum, etc. Anyway, hope this helps explain its usage a bit. If any errors please PM me or find me on qc# so I can correct any mistakes. Will credit you in EDIT. Thanks. :)

EDIT: Spike also wanted me to mention to use sv_csqcdebug cvar in FTE.
Here is a brief explanation of the cvar:
This cvar changes fte's protocol in a subtle way. It adds size counts on a per-entity basis. This makes it easier to find which entity sizes are read/written incorrectly. This is highly FTE specific, and this cvar should only ever be set by csqc modders (or their testers). This will allow the csqc to remain running and also print which entities were not read correctly.
PrimalLove
 
Posts: 14
Joined: Sun Oct 19, 2014 2:37 am


Return to CSQC Programming

Who is online

Users browsing this forum: No registered users and 2 guests