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
!!!! |