Some of the most played mods on the Internet are team based. The goal is to eliminate the enemy and help your teammates to accomplish your mission objectives. Teamplay is all about strategy and tactics and there’s a well known feature from Quake II (missing in Quake 3) that increases this even more: the ability to drop weapons.

Let’s get started.

1. Files we will be modifying from the game source code:

Source files edited:

  • g_local.h
  • g_active.c
  • g_items.c
  • g_cmds.c
  • cg_consolecmds.c – adding function declarations and an entity flag

Functions added/modified:

  • adding ThrowWeapon()
  • adding dropWeapon(), modifying LaunchItem()
  • adding the “drop” command
  • registering the “drop” command (optional)

2. Changing the Q3 code

You may experience some problems with copying text from html files so I provided a textfile with the main functions.

2.1 g_local.h about line 33

Open g_local.h and add this line to the gentity->flags definitions:

#define FL_THROWN_ITEM		0x00008000  // XRAY FMJ weapon throwing

This defines a new flag we will use to identify dropped items. Add these two lines at 360:

void ThrowWeapon( gentity_t *ent );
gentity_t *dropWeapon( gentity_t *ent, gitem_t *item, float angle, int xr_flags );  // XRAY FMJ

This declarates our two new functions. We are done with this file so you can save and close g_local.h now.

2.2 g_active.c, about line 518

Now open g_active.c and paste the function ThrowWeapon() before this line:

void BotTestSolid(vec3_t origin);

/*
=============
ThrowWeapon

XRAY FMJ
=============
*/

void ThrowWeapon( gentity_t *ent )
{
	gclient_t	*client;
	usercmd_t	*ucmd;
	gitem_t		*xr_item;
	gentity_t	*xr_drop;
	byte i;
	int amount;

	client = ent->client;
	ucmd = &ent->client->pers.cmd;

	if( client->ps.weapon == WP_GAUNTLET
		|| client->ps.weapon == WP_MACHINEGUN
		|| client->ps.weapon == WP_GRAPPLING_HOOK
		|| ( ucmd->buttons & BUTTON_ATTACK ))
		return;


	xr_item = BG_FindItemForWeapon( client->ps.weapon );

	amount= client->ps.ammo[ client->ps.weapon ]; // XRAY save amount
	client->ps.ammo[ client->ps.weapon ] = 0;

	client->ps.stats[STAT_WEAPONS] &= ~( 1 << client->ps.weapon );
	client->ps.weapon = WP_MACHINEGUN;
	for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
		if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
			client->ps.weapon = i;
			break;
		}
	}

	xr_drop= dropWeapon( ent, xr_item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM );
	if( amount != 0)
		xr_drop->count= amount;
	else
		xr_drop->count= -1; // XRAY FMJ 0 is already taken, -1 means no ammo
}

This function finds out what weapon the player is carrying and it removes the weapon and its ammo from the players inventory, if it isn’t the gauntlet, the machinegun, or grapple. It also makes sure the player is not firing.

Then the function calls dropWeapon() with the chosen weapon. dropWeapon() returns the new entity and we can set the amount of ammo a player gets if he picks the weapon up again.

Save and close g_active.c.

2.3 g_items.c, about line 401

Find the function LaunchItem() in g_items.c, now add “int xr_flags” to the parameter list:

gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity , int xr_flags ) {  // XRAY FMJ 

now replace the line: dropped->flags = FL_DROPPED_ITEM; with these lines at about 434:

dropped->flags =  xr_flags; // FL_DROPPED_ITEM; // XRAY FMJ FL_THROWN_ITEM

if( xr_flags & FL_THROWN_ITEM) {
    dropped->clipmask = MASK_SHOT; // XRAY FMJ
    dropped->s.pos.trTime = level.time - 50;	// move a bit on the very first frame
    VectorScale( velocity, 500, dropped->s.pos.trDelta ); // 700
    SnapVector( dropped->s.pos.trDelta );		// save net bandwidth
    dropped->physicsBounce= 0.65;
}

If the weapon has the dropped flag, the function now sets some special entity values borrowed from the grenade_launcher function in g_weapon.c. If you think about it, a flying grenade and a dropped weapon are pretty similar at least from a programmers point of view πŸ˜›

But this alone did not work nice… there was no arch and the dropped weapon almost immediately stopped flying. I had a hard time solving the problem until I found the physicsBounce property. The value is a float from 0 to 1 and it determines how long the dropped item will bounce.

Now the problem was that new entities always spawn with physicsBounce= 0. So we need to set this to a more appropriate value ! Well as you can see I use 0.65 wich looks pretty nice. Set it to 1 and it will bounce very long πŸ™‚

Additionally the VectorScale() function sets the overall flying speed of the dropped weapon or item. You can also change this if you like.

g_items.c, about line 469

Since we changed the parameter list of LaunchItem() we need to change the function call to it too ! The function got called only by dropItem() before, take a look at line 469. Change the call so it looks like this:

return LaunchItem( item, ent->s.pos.trBase, velocity, FL_DROPPED_ITEM);

Now paste the function dropWeapon() right after our modified function LaunchItem() :


/*
================
dropWeapon XRAY FMJ
================
*/
gentity_t *dropWeapon( gentity_t *ent, gitem_t *item, float angle, int xr_flags ) { // XRAY FMJ
	vec3_t	velocity;
	vec3_t	origin;

	VectorCopy( ent->s.pos.trBase, origin );

	// set aiming directions
	AngleVectors (ent->client->ps.viewangles, velocity, NULL, NULL);

	origin[2] += ent->client->ps.viewheight;
	VectorMA( origin, 34, velocity, origin ); // 14
	// snap to integer coordinates for more efficient network bandwidth usage
	SnapVector( origin);

	// extra vertical velocity
	velocity[2] += 0.2;
	VectorNormalize( velocity );
	return LaunchItem( item, origin, velocity, xr_flags );
}

This function gets called from our ThrowWeapon() function and then calls LaunchItem() itself. What this fuction does is it calculates the exact spawnpoint ( which is the muzzlepoint here btw) and the direction the player is viewing, which will become the flying direction for the weapon (the var is called velocity).

We’re almost through ! πŸ™‚

2.4 g_cmds.c, about line 1030

Add this function in g_cmds.c:


/*
=================
Cmd_Drop_f XRAY FMJ
=================
*/
void Cmd_Drop_f( gentity_t *ent ) {
	ThrowWeapon( ent );
}

/*
=================
ClientCommand
=================
*/

This function only calls ThrowWeapon() and follows the programming style of id soft.

g_cmds.c, about line 1109:

Now near the end of g_cmds.c add our new command to the function ClientCommand():

else if (Q_stricmp (cmd, "setviewpos") == 0)
    Cmd_SetViewpos_f( ent );
else if (Q_stricmp (cmd, "drop") == 0)  // XRAY FMJ
    Cmd_Drop_f( ent );
else
    trap_SendServerCommand( clientNum, va("print \"unknown cmd %s\n\"", cmd ) );

ClientCommand() compares a user-consolecommand to all available commands and if it finds the command it calls its appropriate function.

2.5 cg_consolecmds.c, about line 207

Search for the function CG_InitConsoleCommands( void ) in cg_consolecmds.c then add this line to the end of the list:

trap_AddCommand ("drop");	// XRAY FMJ weap drop cmd

This line registers the “drop” command so it will be tab completable. Be aware that this line doesn’t add very much functionality but requires you to compile and publish your cgame.qvm together with your qagame.qvm.

So you might want to leave it out, weapon dropping will work fine without it πŸ™‚

3. General Explanation

Basically I combined the functions that get called when a player dies and looses all his powerups and chosen weapon, and the function that gets called when you use the grenade launcher.

First a player uses the “drop” command, this action calls Cmd_Drop_f(). Now in this order the functions ThrowWeapon(), dropWeapon() and finally LaunchItem() get called. LauchItem() returns the new entity so it can be additionally modified, which happens back in ThrowWeapon().

From now on the functions G_RunItem() and G_BounceItem() in g_items.c take over the control over our dropped weapon ( and every item btw ) and this is also the place were the “physicsBounce” value is so important. You may want to read through these functions to understand the behaviour of dropped and other items a little more.

4. Using the new feature

This is the easy part, just bind a key to the command “drop” in the console like this:

bind q drop

5. Customizing the functions

There are several ways to change the behaviour of the dropped weapon, the main ways are:

  • physicsBounce: the higher the value the longer the weapon will bounce.
  • VectorScale( velocity, 500, dropped->s.pos.trDelta ), here 500 is the initial speed of the dropped weapon.
  • velocity[2] += 0.2, a larger value makes the weapon fly higher.
  • dropped->nextthink = level.time + 30000; (g_items.c – line 431) sets the time in milliseconds the dropped weapon will stay in the level.

And you can also change the name of the command to whatever you like, just check that you don’t use names that are already taken by the game.

Well thats all and you made it. Build your qagame.qvm file and have some fun !

Contact Information:

If there is demand I will add an ammo and item-dropping tutorial, but you should be able to build them by yourself now, because its pretty similar to weapon dropping. Just copy and paste the throwWeapon() function in g_active two times and rename and change the settings accordingly, also add two more commands in g_cmds.c and cg_consolecmds.c.

If you experience problems feel free to email me.

http://www.quake3mods.net/fmj/ – the mod I made weapon dropping for: Full Metal Jacket.
http://www.planetstealth.de – my clan and my Q3dominator mod.
I will upload a teamplay mod with weapon dropping there shortly.

If you use this code I would appreciate a link to your mod. Please give credit where credit is due.

Ray, [SuB]paranoid