• Register

Nexuiz GPL is a fun and free GPL deathmatch shooter released in 2005. It is a free game: source code is free software and data is free content.

Post tutorial Report RSS Nexuiz QC Tutorial 2

This is the second in my occasional series of QC tutorials for Nexuiz.

Posted by on - Basic Server Side Coding

[page=Introduction]


Learning QC For Nexuiz Part 2


Welcome back to my crash course in Nexuiz QC. Make sure you've been to the loo before we start, as I hate people interupting my lessons.

 

In this lesson, you will discover how to make your very first Nexuiz weapon modification. I'll explain everything as we go along. This tutorial may seem long, but there's actually very little coding in it, most of it is me explaining and going off topic occasionally... By the way, if you ever make better weapons than me I'll eat your sister.


Ok folks, what weapon shall we make? Well, since the laser currently has no alternate fire, we're gonna make one for it. It'll be bigger, beefier, and do more damage. However, it'll fly slower, fire less often and be slightly innacurate. Sorted. Let's do it!

I'm assuming for this tutorial that you have already done my previous one, and so you'll understand terms like entity, function and antidisestablishmenterianism. Just kidding on the last one, but any aspiring geek should be able to tell me the meaning anyway. Answers on a postcard to the usual address. Also, you should have created a mod directory in the last tutorial, and we'll be continuing to add code to that. If you don't have a mod directory, go back and create one, following the instructions in my first tutorial


Open the file nexuiz/mymod/game/gamec/w_laser.c. All the weapon files are similar, and self contained. Right near the top of the file is this function:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   void(float req) w_laser =    {    if (req == WR_IDLE)    laser_ready_01();    else if (req == WR_FIRE1)    weapon_prepareattack(laser_check, laser_check, laser_fire1_01, cvar(&quot;g_balance_laser_refire&quot;));    else if (req == WR_RAISE)    laser_select_01();    else if (req == WR_UPDATECOUNTS)    self.currentammo = 1;    else if (req == WR_DROP)    laser_deselect_01();    else if (req == WR_SETUP)    weapon_setup(WEP_LASER, &quot;w_laser.zym&quot;, 0);    else if (req == WR_CHECKAMMO)    weapon_hasammo = laser_check();    };   </strong></span> </span>
   

This is the core function for the laser, called everytime the state of the weapon changes. The weapon has several states, for example firing, idle, dropping down to change weapon etc. Let me explain a little how this function works. When it is called, it is fed a parameter called req. req is a parameter of type float, which means that it is a number. The function consists of a series of if statements, which are a way of making decisions in QC. If statements work like this:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   if (something == something_else)    do stuff;   </strong></span> </span>
   

So the code inside the brackets is always comparison of some kind. It could be checking that a variable is equal to a certain value, in which case it uses ==. This is called an operand, and there are several of these in QC. Here is a list of the most commonly used ones you'll need to know.


      <span style="font-size: small; font-family: Verdana"><strong>== Means &quot;Is Equal To&quot;
      != Means &quot;Is Not Equal To&quot;
      &gt;  Means &quot;Is More Than&quot;
      &lt;  Means &quot;Is Less Than&quot;
      &lt;= Means &quot;Is Equal To Or Less Than&quot;
      &gt;= Means &quot;Is Equal To Or More Than&quot;
     </strong></span> 
   

There are a few more that are used for more complex stuff, but one step at a time folks, let's understand this first ok? You gotta learn to walk before you can run.
Come to think of it there are a couple of other useful operands, though these have a slightly different usage:


      <span style="font-size: small; font-family: Verdana"><strong>&amp;&amp; Means &quot;And&quot;
      || Means &quot;Or&quot;
     </strong></span> 
   

I'll explain how to use these at some point when I can be bothered to use them. Ok, back on topic. The lines we are interested in are:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   else if (req == WR_FIRE1)    weapon_prepareattack(laser_check, laser_check, laser_fire1_01, cvar(&quot;g_balance_laser_refire&quot;));   </strong> </span> </span>
   

Since these are the lines dealing with firing the gun. I know what you're thinking now. Yes I do, I'm just like Derren Brown. You're thinking, "What is WR_FIRE1?". If req is a float (a number) then how can it be equal to whatever WR_FIRE1 is? The answer is pretty simple. WR_FIRE1 is what is known as a constant. It is a float, but one with a fixed value. It is defined in defs.h as this:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   float WR_FIRE1    = 6;  // primary fire frame   </strong> </span> </span>
   

So it's actually equal to 6. Constants are often used to make the code more readable, since it's a lot easier to know what WR_FIRE1 means than 6, which would work just as well.

They do have other uses though, but I'll not go into that now.
Ok, let's finally get down to actually doing some coding. Just after these lines:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   else if (req == WR_FIRE1)    weapon_prepareattack(laser_check, laser_check, laser_fire1_01, cvar(&quot;g_balance_laser_refire&quot;));   </strong> </span> </span>
   

Add this:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #00aaaa"><strong>   else if (req == WR_FIRE2)    weapon_prepareattack(laser_check, laser_check, laser_fire1_01, cvar(&quot;g_balance_laser_refire&quot;) * 2);   </strong> </span> </span>
   

All this does is tell the game that the laser now has an alternate fire, and that it should take twice as long to reload. As you may have guessed, WR_FIRE1 means that the player is firing the primary fire, and WR_FIRE2 means he's using the secondary fire. The function being called in the second line has a fairly self explanitory name, but I'll not go into what each parameter means for now. Suffice to say that the last one is the reload time. Oh, * means multiply by the way. / means divide.

Ok, next we need to alter the code that actually fires the laser, to make it different for the alternate fire. Scroll down to this function:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   void W_Laser_Attack (void)    {    local entity missile;    local vector org;    sound (self, CHAN_WEAPON, &quot;weapons/lasergun_fire.wav&quot;, 1, ATTN_NORM);    org = self.origin + self.view_ofs + v_forward * 15 + v_right * 5 + v_up * -12;    //te_customflash(org, 160, 0.2, '1 0 0');    missile = spawn ();    missile.owner = self;    missile.classname = &quot;laserbolt&quot;;    missile.movetype = MOVETYPE_FLY;    missile.solid = SOLID_BBOX;    setmodel (missile, &quot;models/laser.mdl&quot;);    setsize (missile, '0 0 0', '0 0 0');    setorigin (missile, org);    missile.velocity = v_forward * cvar(&quot;g_balance_laser_speed&quot;);    missile.angles = vectoangles (missile.velocity);    //missile.glow_color = 250; // 244, 250    //missile.glow_size = 120;    missile.touch = W_Laser_Touch;    missile.think = SUB_Remove;    missile.nextthink = time + 9;    missile.effects = EF_FULLBRIGHT | EF_ADDITIVE | EF_LOWPRECISION;    }   </strong> </span> </span>
   

I'm going to go through this function practically line by line explaining it, which may take a while. It's pretty important though, as you really need to understand this. I know it seems like hard work, but tough. No slackers in my class!


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   void W_Laser_Attack (void)    {   </strong> </span> </span>
   

This is just naming and starting the function.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   local entity missile;    local vector org;   </strong> </span> </span>
   

This is setting up two local variables. A local variable is one which only exists for as long as the function is being run, and can only be accessed from within the function. The opposite are called global variables which are variables that can be accessed from anywhere. The two local variables being created here are an entity and a vector. As I've explained before, an entity is an object in the game, whether it be a player, and item or a missile etc. Entities can be other more obscure things too, but this explanation will do for now. A vector is a little more complex. Vectors are used for storing locations, angles, directions and velocities etc. They consist of three numbers, like this: '10 30 100'. For a location (or origin as the location of an entity is known in QC), the numbers represent coordinates for the location. For an angle they represent the roll, pitch and yaw. I'll leave it at that for now, I don't want to scare you off!


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   sound (self, CHAN_WEAPON, &quot;weapons/lasergun_fire.wav&quot;, 1, ATTN_NORM);   </strong> </span> </span>
   

This is nice and simple, it plays a sound. I won't go into detail on each parameter right now, maybe at a later date when we're messing with sounds.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   org = self.origin + self.view_ofs + v_forward * 15 + v_right * 5 + v_up * -12;   </strong> </span> </span>
   

Ok, so this isn't quite so simple. Again I'm not going to go into detail, but what it does is work out the location the laser bolt should start from.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   //te_customflash(org, 160, 0.2, '1 0 0');   </strong> </span> </span>
   

Remember how I told you last time that anything between a /* and a */ is a comment ignored by the compiler? Well // is similar. It's a one line comment, everything on that line following the // will be ignored. In this case it's some code which has been commented out that was probably in an earlier version of the weapon. Coders often comment out lines they want to get rid of rather than deleting them, as they may want to put them back in later.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   missile = spawn ();    missile.owner = self;    missile.classname = &quot;laserbolt&quot;;   </strong> </span> </span>
   

Alright, this is where it gets interesting. Not that it wasn't riveting already of course.... spawn() creates a new entity to be the laser bolt, and tells the variable missile to reference it. It then sets it's owner field to be equal to the player. Oh yeah, entities have fields. These are variables that are specific to that entity, it's properties. For example, a player's health will be a field. In this case, owner tells it that it is allowed to pass through the player who fires it without hitting him. Which is kinda sensible. Next it sets the classname field to "laserbolt". Classname is a field mostly just used to know what sort of entity something is, a textual description of it.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   missile.movetype = MOVETYPE_FLY;    missile.solid = SOLID_BBOX;   </strong> </span> </span>
   

Move setup code for the laser bolt. Movetype is pretty self explanitory I hope. Other movetypes include MOVETYPE_WALK (players) and MOVETYPE_BOUNCE (grenades). There are a few others, but again I don't want to go into too much depth for now. solid pretty obviously sets whether the entity is solid or not, i.e. whether it will collide with other objects or go straight through them. There are a few different solid types, but suffice to know that SOLID_BBOX makes it solid against other entities.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   setmodel (missile, &quot;models/laser.mdl&quot;);    setsize (missile, '0 0 0', '0 0 0');    setorigin (missile, org);   </strong> </span> </span>
   

Pretty simple. Setmodel sets the model to be used for this entity, setsize sets the size of the bounding box of the entity, and setorigin places it at a location.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   missile.velocity = v_forward * cvar(&quot;g_balance_laser_speed&quot;);    missile.angles = vectoangles (missile.velocity);   </strong> </span> </span>
   

Sets the velocity (directional speed) and angles of the entity. I won't go into detail as this is a little tricky to explain right now.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   //missile.glow_color = 250; // 244, 250    //missile.glow_size = 120;   </strong> </span> </span>
   

Again these lines are commented out, ignore them.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   missile.touch = W_Laser_Touch;    missile.think = SUB_Remove;    missile.nextthink = time + 9;   </strong> </span> </span>
   

Ok, we're on a roll here. The touch field of an entity is actually a function. It tells the game what function to call when this entity touches something. In this case, the function W_Laser_Touch will deal with causing damage and a splashy particle effect etc. Think and nextthink are always used together. Entites can be made to call a function after a certain amount of time has elapsed. This is useful for things like blowing up a grenade after a few seconds. In this case, it tells the laserbolt to call SUB_Remove in 9 seconds time. SUB_Remove will remove the laserbolt.


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   missile.effects = EF_FULLBRIGHT | EF_ADDITIVE | EF_LOWPRECISION;    }   </strong> </span> </span>
   

Each entity can have several effects. In this case, it is always fullbright (not affected by lighting), uses additive blending and low precision coordinates. Forget all that for now, it's not important at the moment. } of course closes the function.

Ok, we're finally at the end of the function. Phew, I thought we weren't gonna make it, but we dragged ourself through in the end. Most weapon firing functions are very similar to that one, so once you understand one you'll be able to understand most of the others. Mostly.

Hmmm, enough explaining for now methinks, how about a little coding? Find the line that says:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   missile.velocity = v_forward * cvar(&quot;g_balance_laser_speed&quot;);   </strong> </span> </span>
   

I would like you to replace that line with this:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #00aaaa"><strong>   if (self.button3)    {    missile.velocity = (v_forward + randomvec() * 0.05) * cvar(&quot;g_balance_laser_speed&quot;) * 0.5;    missile.scale = 2;    }    else    missile.velocity = v_forward * cvar(&quot;g_balance_laser_speed&quot;);   </strong> </span> </span>
   

Okay, what does that mean? well, the if statement is used to add a piece of code only used when the player uses the alternate fire button. Self.button3 is a variable set when the player hits the alternate fire button, geddit? The velocity line is a little complex, but suffice to say it goes at half the speed of primary fire, and is a little inaccurate. I'll not go into detail right now... I seem to be saying that a lot don't I? One day I'm gonna have to write a tutorial that is nothing but going into detail.

Scale is a nice useful field, which allows you to change the size of the model. In this case we're telling it to be twice as big as normal, so we'll have nice fat laser bolts from our alternate fire. Else just means "do this code if the if statement above isn't done", it which case it uses the old velocity code for the normal fire. Get it? Got it? Sorted!

Ok, so now we have a working alternate fire that goes half the speed of normal, is a bit inaccurate and fires less often. Now to add the positive side if it, the extra damage!

Scroll up to the W_LaserTouch function, which looks like this:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #ee9977"><strong>   void W_Laser_Touch (void)    {    vector dir;    if (other == self.owner)    return;    else if (pointcontents (self.origin) == CONTENT_SKY)    {    remove (self);    return;    }    dir = normalize (self.owner.origin - self.origin);    WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);    WriteByte (MSG_BROADCAST, TE_FLAMEJET);    WriteCoord (MSG_BROADCAST, self.origin_x);    WriteCoord (MSG_BROADCAST, self.origin_y);    WriteCoord (MSG_BROADCAST, self.origin_z);    WriteCoord (MSG_BROADCAST, 0);  // SeienAbunae: groan... Useless clutter    WriteCoord (MSG_BROADCAST, 0);    WriteCoord (MSG_BROADCAST, 0);    WriteByte (MSG_BROADCAST, 155);    te_customflash(self.origin, 160, 0.2, '1 0 0');    self.event_damage = SUB_Null;    RadiusDamage (self, self.owner, cvar(&quot;g_balance_laser_damage&quot;), cvar(&quot;g_balance_laser_edgedamage&quot;),    cvar(&quot;g_balance_laser_radius&quot;), world, cvar(&quot;g_balance_laser_force&quot;), IT_LASER);    sound (self, CHAN_BODY, &quot;weapons/laserimpact.wav&quot;, 1, ATTN_NORM);    remove (self);    }   </strong> </span> </span>
   

This is the touch function we were talking about earlier, the function called when the laser bolt hits something. Don't worry, I'm not doing to go through it line by line, I think we're both bored of that by now. I'm going to take a dive straight in at the shallow end, and crack my head messily. No, actually I'm going to dive straight into the coding. You see that line that starts RadiusDamage? Well that's the line that does the damage, unsurprisingly. The enterprising students amongst you will have noticed that it's a function call, so the actual code for lowering the health etc is in another function. Either way, replace that line with this:


      <span style="font-size: small; font-family: Verdana">
<span style="font-family: System; color: #00aaaa"><strong>   if (self.scale == 2)    RadiusDamage (self, self.owner, cvar(&quot;g_balance_laser_damage&quot;) * 2, cvar(&quot;g_balance_laser_edgedamage&quot;) * 2,    cvar(&quot;g_balance_laser_radius&quot;), world, cvar(&quot;g_balance_laser_force&quot;), IT_LASER);    else    RadiusDamage (self, self.owner, cvar(&quot;g_balance_laser_damage&quot;), cvar(&quot;g_balance_laser_edgedamage&quot;),    cvar(&quot;g_balance_laser_radius&quot;), world, cvar(&quot;g_balance_laser_force&quot;), IT_LASER);   </strong> </span> </span>
   

Bonus points for those who understand what I've done there. Good boy Jenkins, have a gold star! For those of you who are less gifted, let me explain... self.scale is the variable we used earlier to make the laser bolts bigger for alternate fire. Here we're checking if scale is 2, and if so doing more damage. Do you follow me? Do you understand? Do you dig? This also shows nicely that really long lines of code can stretch over several lines, as long as you only put a semicolon at the end of the command, not the end of each line.

And that, lads and ladettes, is that. Compile, run, shoot.


I promised last time that I'd explain the second way to run your mod. Well, I'm not going to, since I can't be bothered. Maybe I'll explain it next time, but maybe not. Ja will decide.

Okey dokey Folkeys, we're at the end of our little lesson for today. Jenkins! You can't leave until the bell rings, get back here! It'll be the slipper for you if you carry on like that boy! Next time we'll do something even more fun, I promise. I hope you're beginning to get a handle on how qc works, if not then don't give up, you'll get it!

Ok, so today we've learnt the meaning of the words constant, field and Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch. More obscure Welsh place names next time folks!

 



This tutorial was brought to you by MauveBib, the letters Q and C, and the number 2.

Any questions or comments can be emailed to me at: skinski@email.com

Visit the Electronic Liberation Front website for all my Quake mods at: Planetquake.com

Find me on IRC at: irc.gamesurge.com #nexuiz or #elfteam

I can also be reached by pidgeon post, stripogram and telepathy.

Thanks for reading, enjoy yourselves, and each other.

- MauveBib


Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: