how to use <insert here> in CSQC
how to use <insert here> in CSQC
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
Re: how to use <insert here> in CSQC
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];
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");
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);
Code: Select all
drawfill(vector position, vector size, vector rgb, float alpha, optional float drawflag);
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)
Re: how to use <insert here> in CSQC
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
}
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)
{
...
}
}
In the next post I'll tell how to tell the server to do things from the client using sendevent.
Re: how to use <insert here> in CSQC
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. */
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
}
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.
Re: how to use <insert here> in CSQC
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);
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, "\", "-");
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...
Code: Select all
for(x = 0; x < idx - 1; x++)
{
print(argv(idx);
}
Code: Select all
strlen(string s); which returns the number of characters in the string,
Code: Select all
float stringwidth(string text, float allowColorCodes, vector size) = #327; // get a width of string with given font and char size
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");
Code: Select all
strunzone(stringVar);
addendum: apparently tokenizebystring is called
Code: Select all
float(string s, string separator1, ...) tokenizebyseparator = #479;
..more to come
Re: how to use <insert here> in CSQC
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. */
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);
...
}
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;
};
You probably want it to look like this for most cases:
Code: Select all
void() CSQC_Ent_Remove =
{
remove(self);
};
Re: how to use <insert here> in CSQC
I found
Code: Select all
void() calltimeofday = #231; /* Part of FTE_CALLTIMEOFDAY*/
calltimeofday fills a global (I believe) and calling
Code: Select all
string(float uselocaltime, string format, ...) strftime = #478; /* Part of DP_QC_STRFTIME*/
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");
Re: how to use <insert here> in CSQC
Code: Select all
float(float tmr) gettime = #519;
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
Code: Select all
gettime(GETTIME_REALTIME);
s_timeofday = strftime(1, "%Y-%m-%d_%H-%M-%S");
Re: how to use <insert here> in CSQC
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.
}
Re: how to use <insert here> in CSQC
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*/
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
}
Re: how to use <insert here> in CSQC
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*/
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;
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);
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);
Re: how to use <insert here> in CSQC
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. */
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);
that rotates like a turntable.
-
- Posts: 14
- Joined: Sun Oct 19, 2014 2:37 am
Re: how to use <insert here> in CSQC
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.
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);
}
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);
};
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....
};
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;
(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;
}
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);
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;
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.
Re: how to use <insert here> in CSQC
The comments in fteextensions.qc are pretty clear here, but having it all laid out tutorial style can still be helpful, so here goes.
Code: Select all
hashtable(float tabsize, optional float defaulttype) hash_createtab = #287;
void(hashtable table) hash_destroytab = #288;
void(hashtable table, string name, __variant value, optional float typeandflags) hash_add = #289;
__variant(hashtable table, string name, optional __variant deflt, optional float requiretype, optional float index) hash_get = #290;
__variant(hashtable table, string name) hash_delete = #291;
string(hashtable table, float idx) hash_getkey = #292;
Code: Select all
const float EV_STRING = 1;
const float EV_FLOAT = 2;
const float EV_VECTOR = 3;
const float EV_ENTITY = 4;
const float EV_FIELD = 5;
const float EV_FUNCTION = 6;
const float EV_POINTER = 7;
const float EV_INTEGER = 8;
const float EV_UINT = 9;
const float EV_INT64 = 10;
const float EV_UINT64 = 11;
const float EV_DOUBLE = 12;
Code: Select all
const float HASH_REPLACE = 256; /* Used with hash_add. Attempts to remove the old value instead of adding two values for a single key. */
const float HASH_ADD = 512; /* Used with hash_add. The new entry will be inserted in addition to the existing entry. */
Hash tables are like dictionaries in python, key value pairs in java, or,
sql tables where every ID is a string, and there is only 1 other column for data.
Hash tables are useful for storing and retrieving data, think loot tables and inventory.
First we declare the hashtable variable as a field, so that we can have 1 per player.
Code: Select all
.hashtable inventory;
Code: Select all
self.inventory = hash_createtab(16, EV_ENTITY);
we are storing the h_box in our self.inventory, under the key "healthbox", and if we already have something stored under "healthbox", then our item will be replaced.
Code: Select all
entity h_box = find(world, classname, "item_health");
if(h_box != world)
{
hash_add(self.inventory, "healthbox", h_box, HASH_REPLACE);
}
so we call hash_get on our self.inventory. and we look up "healthbox", letting hash_get know that we are expecting an EV_ENTITY in return.
Code: Select all
entity default = world;
entity hash_get(self.inventory, "healthbox", optional __variant deflt, EV_ENTITY);
deflt tells hash_add what value we would like returned in the event that the item is NOT found.
for entities, world is the obvious choice, it indicates an error, but say you wanted to grab a weapon, but if that weapon wasnt available you still wanted to equip SOMETHING.
well you could use the deflt field here to say, if you cant find the weapon you are looking for, then just grab me the trusty axe.
Index is a special case, because it isnt useful at all if we are using HASH_REPLACE.
If instead we used HASH_ADD, then we would have multiple entries for the same value, and we could tell it that we wanted the first, second, third, fifth one found in the list
The problem i found with the index value and HASH_ADD, is that while you can add multiple entries to the same key, and retrieve them using the index,
you cant *remove* the items from the hash table using an index. its all or nothing. remove every entry with that key, or leave it be.
This limitation made me decide to not actually use hash tables for inventory for my specific use case, but your case may be a better fit.
now that we have a table, it has something in it, and we know how to retrieve items, its time to wrap things up.
Code: Select all
hash_delete(self.inventory, "healthbox");
Code: Select all
hash_destroytab(self.inventory);
the last function of note is hash_getkey;
Code: Select all
string(self.inventory, 0);
but given a hard index into your table, it will give you the key name. in our example that would have been "healthbox" before we deleted the entry and the table entirely.
Re: how to use <insert here> in CSQC
Json is easy to parse if you know the field types beforehand, and a bit of an annoyance if you don't.
Functions to create and destroy a json node.
Code: Select all
jsonnode(string) json_parse = #0:json_parse;
void(jsonnode) json_free = #0:json_free;
Value types that can be pulled from a json object.
Code: Select all
enum json_type_e : int
{
JSON_TYPE_STRING,
JSON_TYPE_NUMBER,
JSON_TYPE_OBJECT,
JSON_TYPE_ARRAY,
JSON_TYPE_TRUE,
JSON_TYPE_FALSE,
JSON_TYPE_NULL
};
Function to find out which of those value types a given node is.
Code: Select all
json_type_e(jsonnode node) json_get_value_type = #0:json_get_value_type;
Functions to actually pull those values out of the json and pack them into a standard quakec data type.
Code: Select all
int(jsonnode node) json_get_integer = #0:json_get_integer;
float(jsonnode node) json_get_float = #0:json_get_float;
string(jsonnode node) json_get_string = #0:json_get_string;
Code: Select all
string(jsonnode node) json_get_name = #0:json_get_name;
Code: Select all
jsonnode(jsonnode node, string) json_find_object_child = #0:json_find_object_child;
jsonnode(jsonnode node, int childindex) json_get_child_at_index = #0:json_get_child_at_index;
Code: Select all
int(jsonnode node) json_get_length = #0:json_get_length;