Click for more information!

Code3Arena

TUTORIAL 10 - New Weapons
by AssKicka

In this Tutorial I will explain how to add a new weapon with new models,shaders,skins and logic into Q3A. For this tutorial I will be using the flame thrower from the solidground v2.0 mod. This is a very large tutorial and requires a number of additions to the cgame,ui and game code, so get yourself a coke and start reading...

1. ADDING WEAPON AND AMMO DEFINITIONS

Fist we are going to add a new flag for the flame thrower and the means of death (MOD). open bg_public.c and add the following code after line 247. We will use this flag to indentify with the weapon and it's state.

WP_GRAPPLING_HOOK,
WP_FLAME_THROWER,

WP_NUM_WEAPONS
} weapon_t;

Now goto line 444 and add the following MOD flag, we will use this later to determine the means of death and print a relevant message.

MOD_PLASMA,
MOD_PLASMA_SPLASH,
MOD_FLAME_THROWER,
MOD_RAILGUN,

Open g_combat.c and add the following after line 155. This is just for logging.

"MOD_FLAME_THROWER",

Now we must add the inventory and model indexes. Open inv.h and add the code bellow after lines 17 and and 65 :

#define INVENTORY_FLAMETHROWER35
#define MODELINDEX_FLAMETHROWER36 

Next we are going to add the definitions for our new weapon and the ammo it uses. Open bg_misc.c and goto line 329 and add the following code after line 329 :

/*QUAKED weapon_flamethrower (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
*/
{
	"weapon_flamethrower",
	"sound/misc/w_pkup.wav",
	{ "models/weapons2/flamethrower/flamethrower.md3",
	0, 0, 0},
/* icon */	"icons/iconw_flame",
/* pickup */	"Flame Thrower",
	20,
	IT_WEAPON,
	WP_FLAME_THROWER,
/* precache */ "",
/* sounds */ ""
},

Lets look at what we have added :

1) This first line must remain because this is used by QERadiant if you intend adding your weapon into a map.

2) weapon_flamethrower is the name of our weapon.

3) "sound/misc...." is the sound that will be made when you pick this weapon up.

4) "models/weapons2..." is the model that will be used, I sudgest you get the solidground v2.0 mod and use the flame thrower model in it, it will make implementing this tutorial allot easier !

5) "icons/iconw...." is the icon that is displayed when you select the weapon.

6) "/* pickup */...." is the name you see on the screen when you select/pickup the weapon

7) "20,..." is the ammount of ammo you get when you pick the weapon up.

8) "IT_WEAPON..." is just a flag that is used to identify the item

9) "WP_FLAME_THROWER..." is the flag we defined earlier to identify the weapon. Ok, Q3A now knows our new flame thrower weapon.

Next we will add the ammo that is used by the flame thrower, we will use the bfg ammo model for our new flame ammo. Goto line 461 in bg_misc.c and add the code bellow :

/*QUAKED ammo_flame (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
*/
{
	"ammo_flame",
	"sound/misc/am_pkup.wav",
	{ "models/powerups/ammo/bfgam.md3", 
	0, 0, 0},
/* icon */"	icons/icona_bfg",
/* pickup */	"Flame Ammo",
	50,
	IT_AMMO,
	WP_FLAME_THROWER,
/* precache */ "",
/* sounds */ ""
},

Most of the ammo code above has the same meaning as the weapon definitions. The only thing worth mentioning is the "WP_FLAME_THROWER..." section, this tells Q3A what weapon this ammo is for.

2. SETTING THE RATE OF FIRE.

Since we are creating a flame thrower we want it to have a high rate of fire, to accomplish this goto line 1565 in bg_pmove.c and add the following code :

case WP_GRAPPLING_HOOK:
	addTime = 400;
case WP_FLAME_THROWER:
	addTime = 40;
break;

The lower "addtime" is set to, the higher the rate of fire !

3. SPAWN PLAYER WITH FLAME THROWER.

We need to have the flame thrower as a default weapon because at this stage there are no maps with our new weapon in. Open g_client.c and add the following code after line 938 :

//Spawn player with flame thrower
client->ps.stats[STAT_WEAPONS] = ( 1 << WP_FLAME_THROWER );
client->ps.ammo[WP_FLAME_THROWER] = 999;

All we have done on this first line is add the WP_FLAME_THROWER bit to the players inventory, then we made the ammo ammount = to 999 because there are no "flame_ammo" items in the maps. Remember we created a new item called "flame_ammo" !

We also need to register the weapon items the player will start with, i.e flame_ammo. Open g_items.c and add the following code after line 581 :

RegisterItem( BG_FindItemForWeapon( WP_FLAME_THROWER) );

4. PREVENT FLAME THROWER FROM BEING DROPPED.

We will prevent the player from dropping the flame thrower when the player dies. We don't realy have to do this but I added this for educational purposes. open g_combat.c and goto line 51 and change the following existing code :

if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK ) {
	if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
		weapon = self->client->pers.cmd.weapon;

to look like this :

if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK || weapon == WP_FLAME_THROWER) {
	if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
		weapon = self->client->pers.cmd.weapon;

This makes sure other weapons are dropped while the player is changing weapons, i.e. the player picks up the BFG and dies while the weapon is being activated.

OK, lets prevent the flame thrower from being dropped. Goto line 59 and change the following existing code :

if ( weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK && 
	self->client->ps.ammo[ weapon ] ) {
	// find the item type for this weapon
	item = BG_FindItemForWeapon( weapon );

	// spawn the item
	Drop_Item( self, item, 0 );
} 

to look like this :

if ( weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK &&
	weapon != WP_FLAME_THROWER && self->client->ps.ammo[ weapon ] ) {
	// find the item type for this weapon
	item = BG_FindItemForWeapon( weapon );

	// spawn the item
	Drop_Item( self, item, 0 );
}

OK, Lets see what we have done : The only thing we changed is the if statement. If the weapon is bigger than a machine gun and the weapon is not a grappling hook and it's also not a flame thrower and the ammo for this item is more than 0 then drop the weapon.

5. ADDING THE FLAME THROWER FIRE FUNCTIONS.

In this section we will be adding the code that fires the flame thrower and runs the think functions for the fire projectiles. Open g_weapons.c add add the following code after line +-636 :

case WP_GRAPPLING_HOOK:
	Weapon_GrapplingHook_Fire( ent );
	break;
case WP_FLAME_THROWER :
	Weapon_fire_flame( ent );
	break;
default:

This will call the "Weapon_fire_flame" function that we will be defining bellow when a player fires the flame thrower.

Now we will create our "Weapon_fire_flame" function. Add the following code above the "machinegun" function at line +- 78 in g_weapon.c :

*/
=======================================================================
FLAME_THROWER
=======================================================================
*/
void Weapon_fire_flame (gentity_t *ent ) {
	gentity_t *m;

	m = fire_flame(ent, muzzle, forward);
	m->damage *= s_quadFactor;
	m->splashDamage *= s_quadFactor;
}

This may seem confusing at first but it is actually very simple. First we create a entity type "m" gentity_t, we then call a "fire_flame" function and pass the calling entity (ent), starting point (muzzle) and direction (forward) information to "fire_flame". The "fire_flame" function will spawn a new entity in the world and all the new entities information if passed back to "m". We then multiply the damage/splashDamage with the quad factor. "damage/splashDamage" is an example of the information passed back from the "fire_flame" function.

It is now time to create our "fire_flame" Function that will spawn a new entity into the world. open g_missile.c and add the following code after line +-237 :

/*
=================
fire_flame
=================
*/
gentity_t *fire_flame (gentity_t *self, vec3_t start, vec3_t dir) {
gentity_t*bolt;

VectorNormalize (dir);

bolt = G_Spawn();
bolt->classname = "flame";
bolt->nextthink = level.time + 1500;
bolt->think = G_ExplodeMissile;
bolt->s.eType = ET_MISSILE;
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
bolt->s.weapon = WP_FLAME_THROWER;
bolt->r.ownerNum = self->s.number;
bolt->parent = self;
bolt->damage = 30;
bolt->splashDamage = 25;
bolt->splashRadius = 45;
bolt->methodOfDeath = MOD_FLAME_THROWER;
bolt->splashMethodOfDeath = MOD_PLASMA_SPLASH;
bolt->clipmask = MASK_SHOT;

bolt->s.pos.trType = TR_LINEAR;
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;// move a bit on the very first frame
VectorCopy( start, bolt->s.pos.trBase );
VectorScale( dir, 300, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta );// save net bandwidth

VectorCopy (start, bolt->r.currentOrigin);

return bolt;
}

What the hell is this you ask ? Lets disect this function and see what is does. ( I will only explain the goodies that are not self explanitory) If you don't understand what the Vector... functions then you need to read the Vectors article on code3arena.

On line 7 "gentity_t *bolt" we create a new entity type called bolt, this will be passed back to the "Weapon_fire_flame" function.

On line 11 a new entity of type bolt is spawned.

On line 12 we give this entity a name "flame".

On line 13 the next thinktime for this entity is defined as being 1.5 seconds, that means after 1.5 seconds the think function defined on line 14 (G_ExplodeMissile) is called, I will not go into the "G_ExplodeMissle" function, just mention that this function will make and explosion if the flame has not hit anything in 1.5 seconds time.

On line 15 we set the entity type to ET_MISSILE, this will cause the "G_Runmissile" function to be executed every frame. This is actualy achived in "G_RunFrame" function in g_main.c by checking for an ET_MISSILE entity and then executing the "G_RunMissile" function.

On line 16 we set the entity server flag, if you create a ET_MISSILE entity you must user SVF_USE_CURRENT_ORIGIN and not
entity->s.origin.

On line 18 we set the owner of this entity to the client who fired the flame thrower.

On line 25 we set the clipmask, meaning the entity will stop on CONTENTS_SOLID, CONTENTS_BODY or CONTENTS_CORPSE.

Line 27 sets the trace type to linear, meaning it is not affected by gravity.

Now we need to add this reference to our function in g_local.h. Goto line 465 and add the code below :

gentity_t *fire_flame (gentity_t *self, vec3_t start, vec3_t aimdir);

All the flame thrower server functions are now implemented, the next step is to create the special effects etc.

6. ADDING THE SPECIAL EFFECTS AND GFX FOR THE FLAME THROWER.

First we will add our new shader definition for the flame projectile. Open cg_local.h and add the code bellow after line 576 and 612 :

qhandle_tflameBallShader;
qhandle_tflameExplosionShader;

Open cg_main.c and add the bellow code after line 572

cgs.media.flameBallShader = trap_R_RegisterShader( "sprites/flameball" );

What we have done here is create two new shaders called "flameballShader" and "flameExplosionShader", the actual shader functions for "flameballShader" are stored in a custom *.shader file. Here is what should be in the *.shader file :

sprites/flameball
{
	cull disable
	{
		clampmap sprites/flameball.tga
		blendfunc GL_ONE GL_ONE
		tcMod rotate 931
	}
}

You will have to create a "flameball.tga" file in a graphics program like photoshop that will be drawn on the flame projectile model, once this is done you can create a directory mymod/sprites/ and put your flameball.tga file in it.

Now we will add the flame thrower weapon info, i.e. sounds, explosion shaders etc. Open cg_weapon.c and add the code bellow after line 517 :

case WP_FLAME_THROWER:
	weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav" );
	MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 );
	weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav" );
	cgs.media.flameExplosionShader = trap_R_RegisterShader( "rocketExplosion" );
break; 

This section tells Q3A what and where sounds and shaders can be found. We used the Plasma sounds and rocketExplosion shader on our flame thrower.

Now goto line +-1445 add add the following code :

case WP_SHOTGUN:
	mod = cgs.media.bulletFlashModel;
	shader = cgs.media.bulletExplosionShader;
	mark = cgs.media.bulletMarkShader;
	sfx = 0;
	radius = 4;
break;
case WP_FLAME_THROWER:
	mod = cgs.media.dishFlashModel;
	shader = cgs.media.flameExplosionShader;
	sfx = cgs.media.sfx_plasmaexp;
	mark = cgs.media.burnMarkShader;
	radius = 16;
break;

This is the magic part, here we tell Q3A what to do when our flame projectile hits something solid. What does all this mean ?
1) mod : This is the model to which we will apply our "flameExplosionShader"
2) shader : This is the shader we will apply to our model.
3) sfx : This is the sound to make when it explodes.
4) mark : The shader to be used as a mark on the wall.
5) radius : The radius of the explosion.

Now we need to add the graphics to our flame projectile (the flame entity we spawned earlier). Open cg_ents.c and add the code below after line 386 :

if (cent->currentState.weapon == WP_FLAME_THROWER ) {
	ent.reType = RT_SPRITE;
	ent.radius = 32;
	ent.rotation = 0;
	ent.customShader = cgs.media.flameBallShader;
	trap_R_AddRefEntityToScene( &ent );
return;

All we did here is to draw our flame projectile (entity) using our "flameballshader". We set the entity type to be a sprite, the radius of our sprite to 32 and the entity rotation to 0. The rotation of the projectile will be handled by the "flameballShader" by rotating the "flameball.tga" file we created earlier.

Now we will add our MOD (Means Of Death) message when a player is killed by our flame thrower. open cg_event.c and add the following code after line 250 :

case MOD_BFG_SPLASH:
	message = "was blasted by";
	message2 = "'s BFG";
break;
case MOD_FLAME_THROWER:
	message = "was fried by";
break;

7. ADDING WEAPON 11 TO THE MENU.

Lastly we will add the weapon 11 command so that we can bind a key to it. Open ui.controls2.c and add the following code after line 105 :

#define ID_WEAPON11 43

Now goto line +-208 and add the following code :

menuaction_sflamethrower; 

Now goto line +-257 and add the following code :

{"weapon 11","flame thrower",ID_WEAPON11,ANIM_WEAPON11,'f',-1,-1, -1},
{(char*)NULL,(char*)NULL,0,0,-1,-1,-1,-1},
};

Now goto line +-300 and add the following code :

(menucommon_s *)&s_controls.bfg,
(menucommon_s *)&s_controls.hook,
(menucommon_s *)&s_controls.flamethrower,
NULL,
};

Now goto line +-540 and add the following code :

case ANIM_WEAPON11:
	s_controls.playerWeapon = WP_FLAME_THROWER;
	break;

Now goto line +-1410 and add the following code :

s_controls.flamethrower.generic.type      = MTYPE_ACTION; 
s_controls.flamethrower.generic.flags      = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS|QMF_GRAYED|QMF_HIDDEN; 
s_controls.flamethrower.generic.callback  = Controls_ActionEvent; 
s_controls.flamethrower.generic.ownerdraw = Controls_DrawKeyBinding; 
s_controls.flamethrower.generic.id          = ID_WEAPON11; 

Lastly we will add our weapon to the menu. goto line +-1633 and add the following code :

Menu_AddItem( &s_controls.menu, &s_controls.flamethrower );

Now open ui_player.c and add the following code after line 123 :

case WP_FLAME_THROWER:
	MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
break;

Wow ! That's it. Now re-build all and enjoy your flame thrower !!!!

[ << Prev ] [ Home ] [ Next >> ]