Adding a New NPC Part III

In the previous tutorial we took a detailed look at adding a second new type of NPC to the Single Player Game code. This was a werewolf NPC that attacked the player or non-monsters using melee or jump attacks. This time we will look at adding an ogre NPC that uses a shooting attack.

Model Requirements

As with our other NPCs, we need a model for our ogre and some support files. The model we will be using, avalible on the Downloads page, is named ogre.md3. The model must be located in the models/npc/ogre directory, along with the necessary support files. Since all NPC models require an animation.txt file we must supply one for the ogre model. It should look like the following :

// start frame, numframes, looping, frames per second
      178          6         0            10            // Death #1
      184          6         0            10            // Death #2
      190          8         0            10            // Death #3
      95           17        0            10            // taunt
      46           8         0            10            // far attack
      160          9         0            10            // melee attack
      135          19        19           10            // stand inactive
      135          19        19           10            // stand active
      154          6         6            10            // walk
      40           6         6            10            // run
      0            0         0            10            // no animation
      66           2         0            10            // jump
      68           4         0            10            // land
      169          4         0            10            // pain

This is much like the werewolf animation.txt file although we don't really need the melee attack animation. This NPC also has a taunt animation which it will use on occasion.

Model Sounds

Since an NPC would be incomplete without a set of custom sounds we will be defining them using the sounds.txt file. Besides the death, movement and pain sounds, which we defined for our other NPCs, we will also define sounds for the idle animation. The sounds.txt file for the ogre NPC should look like :

// animation number, starting frame,             sound file
        1                 0           sound/npc/ogre/death1.wav
        2                 0           sound/npc/ogre/death2.wav
        3                 0           sound/npc/ogre/death3.wav
        5                 3           sound/npc/ogre/attack.wav
        8                 1           sound/npc/ogre/idle.wav
        9                 6           sound/player/footsteps/flesh1.wav
        9                 13          sound/player/footsteps/flesh1.wav
        9                 6           sound/player/footsteps/flesh2.wav
        9                 13          sound/player/footsteps/flesh2.wav
        9                 6           sound/player/footsteps/flesh3.wav
        9                 13          sound/player/footsteps/flesh3.wav
        10                0           sound/player/footsteps/flesh1.wav
        10                4           sound/player/footsteps/flesh1.wav
        10                0           sound/player/footsteps/flesh2.wav
        10                4           sound/player/footsteps/flesh2.wav
        10                0           sound/player/footsteps/flesh3.wav
        10                4           sound/player/footsteps/flesh3.wav
        14                0           sound/npc/ogre/pain1.wav
        14                0           sound/npc/ogre/pain2.wav
        14                0           sound/npc/ogre/pain3.wav

        -1

Model Configuration

Because it is easier to tweak the NPC using a config.txt file rather than recompiling after every change, we will include this file in our model directory. It would look like :

100                // health
1.0                // pain factor
20                 // walking speed
40                 // running speed
140                // fov
30                 // jump height
20                 // walking rotation
20                 // running rotation
( -16 -16 -24 )    // min bounding box
( 16 16 40 )       // max bounding box
( 0 0 36 )         // eye coords


The ogre, from this config file, is not a fast runner or walker. This fits well with that monster type.

Entity Definition

Like any entity we add to the game we must provide an entity definition for our level editor. The name of the entity is determined by the name of the directory that contains the model information, which in turn is determined by the name of the model. Therefore, our entity will be called npc_ogre and its definition will be :

/*QUAKED npc_ogre (0.2 0.5 0) (-16 -16 -40) (16 16 8) suspended disabled deaf first_taunt
"shot_factor" likeness of NPC to fire (far weapon), 1.0 means always fire,
0 means never fire, only chase, default is 0.5
*/

Source Code Changes

This NPC only requires changes to the game module but the cgame module must be recompiled as well since there are some changes in the bg_* files.

As with any new NPC we must add the new type to the list of NPC types. In keeping with the naming scheme already in place, our NPC's type will be NPC_OGRE.

In bg_public.h about line 657 add :

	NPC_SEALORD,
NPC_SOLDIER1,
NPC_SOLDIER2,
// npc addon
NPC_MAN,
// end npc addon
NPC_AWOLF, NPC_OGRE,
NPC_NUMNPCS
} npcType_t;

We now add the definition of our NPC and its default values to the bg_npclist array. Some of these values will be used if the config.txt file is missing.

In bg_misc.c at the end of the bg_npclist definition about line 199 add :

	{
"npc_awolf" // classname
NPC_AWOLF,
100, // health
1.0, // pain factor
20, // walk speed
60, // runningSpeed
180, // fov
20, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
20, // melee damage
0, // far damage
{0},
{-24,-24,-24}, // bounding box min
{24,24,40}, // bounding box max
{0,0,30}, // eye coords
"", // precache
"" // sounds
},
{
"npc_ogre" // classname
NPC_OGRE,
100, // health
1.0, // pain factor
20, // walk speed
60, // runningSpeed
180, // fov
20, // jumpHeight
20, // walkingRotSpd
75, // runningRotSpd
0, // melee distance
0, // melee damage
30, // far damage
{0},
{-24,-24,-24}, // bounding box min
{24,24,40}, // bounding box max
{0,0,30}, // eye coords
"", // precache
"" // sounds
},
{NULL},
};

This completes the basic monster NPC. It will attack all non-monsters in the game and try to shoot at them. To actually make this NPC shoot and damage something requires more source code changes.

First up is the delay after starting the shooting animation before the projectile is fired.

In g_npcthink.c in function NPC_ThinkMove about line 717 add :

npc->ps.legsAnim=((npc->ps.legsAnim & ANIM_TOGGLEBIT)^ANIM_TOGGLEBIT) | ANPC_ATTACK_FAR;
npc->ps.legsTimer=ent->npc->animTimes[ANPC_ATTACK_FAR];
npc->dontMoveTime=level.time+ent->npc->animTimes[ANPC_ATTACK_FAR];
npc->fireTime=level.time+ent->npc->animTimes[ANPC_ATTACK_FAR]*((ent->npc->npcType==NPC_ANK
|| ent->npc->npcType==NPC_OGRE)?0.8:(ent->npc->npcType==NPC_BAT?0.2:0.5));
npc->fireCount=0;

Next is the weapon. We must calculate exactly where on the model we want the projectile to start from. In the case of the ogre this is up and over to the right from his origin so the projectile starts at the weapon muzzle.

In g_weapon.c in function NPC_FireWeapon about line 1169 add :

	else if (ent->npc->npcType==NPC_BAT)
fire_org[2]+=33;
else if (ent->npc->npcType==NPC_OGRE)
{
vec3_t offset, xoffset;
vec3_t v[3];
AnglesToAxis( ent->ns.ps.viewangles, v );
offset[0] = 0;
offset[1] = -13;
offset[2] = 15;
xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0];
xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1];
xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2];
VectorAdd( ent->r.currentOrigin, xoffset, fire_org );
}

else if (ent->npc->npcType==NPC_SEALORD)

Then comes the actual firing of the weapon.

In g_weapon.c in function NPC_FireWeapon about line 1220 add :

	else  if (ent->npc->npcType==NPC_BAT)
{
fire_bat_shot(ent,muzzle,forward);
}
else if (ent->npc->npcType==NPC_OGRE)
{
fire_ogre_shot(ent,muzzle,forward,ent->ns.enemy);
}

else if (ent->npc->npcType==NPC_SEALORD)

The creation of the missle is the last part of the code changes. We can have the NPC shoot any predefined missile, such as a rocket or BFG blast, or we can create something new like we will do here. He will shoot a Plasma ball that will sometimes home in on it's target. It will be a slower moving projectile than the other NPCs shoot and will cause 30 damage per hit plus cause splash damage if it misses.

In g_local.h about line 644 add :

gentity_t *fire_sealord_big_shot (gentity_t *self, vec3_t start, vec3_t dir);
gentity_t *fire_sealord_small_shot (gentity_t *self, vec3_t start, vec3_t dir);
gentity_t *fire_ogre_shot (gentity_t *self, vec3_t start, vec3_t dir,gentity_t *enemy);
#endif

In g_missle.c about line 813 add :

#define SEALORD_BIG_SHOT_SPEED 1200
#define SEALORD_SMALL_SHOT_SPEED 550
#define OGRE_SHOT_SPEED 800

This missile requires a thinking function that is run to make it home on it's target.

In g_missle.c at end of file (before the final #endif) add :

void ogre_shot_think( gentity_t *ent ) {
vec3_t targetdir, forward, midbody;
trace_t tr;
gentity_t *enemy;
int length;
enemy=ent->ns.enemy;
if (enemy->health<=0) return;
VectorCopy(ent->s.pos.trDelta, forward);
VectorNormalize(forward);
midbody[0] = enemy->r.currentOrigin[0] +
(enemy->r.mins[0] + enemy->r.maxs[0]) * 0.5;
midbody[1] = enemy->r.currentOrigin[1] +
(enemy->r.mins[1] + enemy->r.maxs[1]) * 0.5;
midbody[2] = enemy->r.currentOrigin[2] +
(enemy->r.mins[2] + enemy->r.maxs[2]) * 0.5;
VectorSubtract(midbody, ent->r.currentOrigin, targetdir);
length = VectorLength(targetdir);
targetdir[0] /= length;
targetdir[1] /= length;
targetdir[2] /= length;
if ( DotProduct(forward, targetdir) < 0.93 ) return;
trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL,
enemy->r.currentOrigin, ENTITYNUM_NONE, MASK_SHOT );
if ( enemy != &g_entities[tr.entityNum] ) return ;
ent->nextthink = level.time+1;
VectorMA(forward, 0.08, targetdir, targetdir);
VectorNormalize(targetdir);
VectorScale(targetdir, OGRE_SHOT_SPEED, ent->s.pos.trDelta);
}

and right after that comes the missile creation function.

gentity_t *fire_ogre_shot (gentity_t *self, vec3_t start, vec3_t dir,gentity_t *enemy) {
gentity_t *bolt;
VectorNormalize (dir);
bolt = G_Spawn();
bolt->classname = "rocket";
bolt->nextthink = level.time + 1;
bolt->think = ogre_shot_think;
bolt->s.eType = ET_MISSILE;
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
bolt->s.weapon = WP_PLASMAGUN;
bolt->r.ownerNum = self->s.number;
bolt->parent = self;
bolt->ns.enemy=enemy;
bolt->damage = 20*(1+0.25*npc_skill);
bolt->splashDamage = 15*(1+0.25*npc_skill);
bolt->splashRadius = 40;
bolt->methodOfDeath = MOD_BFG;
bolt->splashMethodOfDeath = MOD_BFG_SPLASH;
bolt->clipmask = MASK_SHOT;
bolt->target_ent = NULL;
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, OGRE_SHOT_SPEED, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy (start, bolt->r.currentOrigin);
return bolt;
}

#endif

The line :

      bolt->s.weapon = WP_PLASMAGUN; 

determines the type of missile the NPC fires while the lines :

      bolt->damage = 20*(1+0.25*npc_skill);
bolt->splashDamage = 15*(1+0.25*npc_skill);
bolt->splashRadius = 40;

sets the hit and splash damage done.

And one final thing needs to be done. If we require the cgame module to use some media it doesn't load by default we must tell it to load it anyway. In this case we are using the Plasma ball, which isn't loaded by default. We tell the cgame module to load it by adding this code :

In g_items.c in function ClearRegisteredItems about line 809 add :

	RegisterItem( BG_FindItemForWeapon( WP_SEA1 ) );
RegisterItem( BG_FindItemForWeapon( WP_SEA2 ) );
RegisterItem( BG_FindItemForWeapon( WP_GUN ) );
RegisterItem( BG_FindItemForWeapon( WP_PLASMAGUN ) );
#endif


And this completes our ogre NPC. On the Downloads page is a package containing the model, support files and sounds used for this NPC. It is a slightly modified Quake 2 player model converted to md3 format and is provided only as an example.

                                  
Return to Home Page