TUTORIAL 22 - Weapon dropping
by [SuB]paranoid
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 :P
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
|