Tutorial *112*

In this tutorial you're going to get to add new keys to the game, in a similar vein to +attack and +jump. In order to do this, there are three main things need doing:
* adding the actual keys/console commands
* sending the appropriate button data over the network
* receiving that button data and putting it somewhere where it can be accessed from QuakeC

Myself, I'm going to add a secondary fire and a crouch button, and re-enable the use button (which is a special case as some of the code's still lying about)
First, let's add the console commands. Go into cl_input.c.
After all the comments at the top, you'll see a group of definitions of kbutton_t structures. Here, we'll add new structures for our new keys:

 kbutton_t	in_secattack, in_crouch;

Note that in_use is already defined. Now we have some key variables, we'd better let the console know our keys exist.
If you scroll down from the kbutton_t definitions, you'll see two functions, KeyUp and KeyDown, followed by a whole group of little routines calling them, an up and down pair for each routine. At the bottom of them, add:

 void IN_SecAttackDown(void) {KeyDown(&in_secattack);}

 void IN_SecAttackUp(void) {KeyUp(&in_secattack);}

 void IN_CrouchDown(void) {KeyDown(&in_crouch);}

 void IN_CrouchUp(void) {KeyUp(&in_crouch);}

Once again, the use button is already handled. These little stub functions well be called by the console commands for each button - a kbutton_t has no direct link to the actual hardware. So, let's tell the console about them!
Scroll down to CL_InitInput, and add the following lines:

 	Cmd_AddCommand ("+secattack", IN_SecAttackDown);

 	Cmd_AddCommand ("-secattack", IN_SecAttackUp);

 	Cmd_AddCommand ("+crouch", IN_CrouchDown);

 	Cmd_AddCommand ("-crouch", IN_CrouchUp);

These lines add the console commands for the buttons, which call the various IN_ functions and thus set the kbutton_t structures. We've just hit our first little milestone! Woohoo! :-)
The next stage is nice and easy, as we only need to alter one function, CL_SendMove - which is conveniently also resident in cl_input.c. This is the routine that puts together the player move message each frame. This message consists of a timestamp (for ping calculation), the player's viewing angle (for shooting purposes, mostly), the three components of the player's intended direction of movement, a bitfield containing the state of the fire and jump buttons, and finally the player's impulse for that frame. For the sake of compatability we'd like to keep this structure, so the obvious place to stick our new button data is in the bitfield.
As you read through the function, you'll come across this little section:


// send button bits


	bits = 0;

	if ( in_attack.state & 3 )

		bits |= 1;

	in_attack.state &= ~2;

	if (in_jump.state & 3)

		bits |= 2;

	in_jump.state &= ~2;

This checks if the attack button has been pressed this frame, and if so then it adds 1 to bits (which becomes our bitfield), before clearing the "pressed this frame" bit in the attack button's state. Then it does the same with the jump button, except that jump has the value 2 in our bitfield.
You may notice that this is the first time in our code that there's been no reference to the use button - so it's time to start fixing things! Add this code below the section I listed above:

 	if ( in_use.state & 3 )

 		bits |= 4;

 	if ( in_secattack.state & 3 )

 		bits |= 8;

 	in_secattack.state &= ~2;

 	if ( in_crouch.state & 3 )

 		bits |= 16;

 	in_crouch.state &= ~2;

Note that this assigns use to the third bit in our bitfield (with value 4), secondary attack to the fourth bit (8) and crouch to the fifth (16). With this, we've got the data over the network - time to get it into QuakeC where we can do something with it!
Open up sv_user.c, and look for the function SV_ReadClientMove. This function is the opposite of CL_SendMove - it reads in the data and updates the player entity accordingly. Now we have a second choice - where do we put the new data? In order to avoid adding new fields, I decided to turn .button0 (the attack button) and .button2 (the jump button) into bitfields, and put use into .button1 where it always should've been (unfortunately this breaks some mods which used it as a spare field - I'll leave you to implement an option to circumvent this).
First of all, take a look at the existing code:

// read buttons

	bits = MSG_ReadByte ();

	host_client->edict->v.button0 = bits & 1;

	host_client->edict->v.button2 = (bits & 2)>>1;

The first line's hopefully obvious :-) The second two read back in the jump and fire bits into the appropriate buttons for the sake of QuakeC. They're equivalent to writing this:
	if (bits & 1) host_client->edict->v.button0 = 1;

		else host_client->edict->v.button0 = 0;

	if (bits & 2) host_client->edict->v.button2 = 1;

		else host_client->edict->v.button2 = 0;

The bitwise and effectively checks whether the appropriate bit is set, and in the case of button2 the shift turns it into the chosen bit (the least significant one).
Anyway, here's the replacement code:

// read buttons

	//bits layout:

	//attack		1

	//jump			2

	//secattack		4

	//crouch		8

	//use			16

	//grapple		32

	//grapple_in	64

	//grapple_out	128

	bits = MSG_ReadByte ();

	host_client->edict->v.button0 = bits & 1;

	host_client->edict->v.button2 = (bits & 2)>>1;

	host_client->edict->v.button0 += (bits & 4)>>1;

	host_client->edict->v.button2 += (bits & 8)>>2;

	host_client->edict->v.button1 = (bits & 16)>>4;

This takes all the stuff in bits and rearranges it into the three button fields like so:

attack 1
secattack 2

use 1

jump 1
crouch 2

So why do all this bit-twiddling when a bunch of if statements will do? The short answer is there's no really good reason in this situation. The longer one is that when it comes to optimising code on modern processors, Branches Are Bad - they mess up pipelining something nasty. It's quite possible that John Carmack writes bit-twiddling over branches instinctively by now :-) If you find yourself writing speed-critical loops then bear this in mind...
Finally, you'll want to actually *do* something with all these buttons. Within your QuakeC code, you check whether or not a button is down like this:
	if (self.button0 & 1) //checks attack

Subsitituting button0 for the appropriate button number and the value of the bit representing your button for 1. If you want to check the value of more than one button you'll have to do it like so:

	if ((self.button0 & 1) && (self.button0 & 2)) //checks for both attacks

as simply adding the two bit values together doesn't work. I mention this because I went and did exactly that the other day :-(
Anyway, you should now have some new buttons in your engine - so arise and free yourself from the tyranny of impulses!

Not logged in
Sign up