Tutorial: Weapon Sentry

 

 

“ weapon on a stick ”

 

Level: Medium

 

Author: Ninja of Comp

 

I  was playing “Ratchet & Clank: Going Commando” the other day with my kids (yeah, good excuse) and I saw that Ratchet had a weapon very similar to the Weapons Factory sentry gun. I saw that this version was more basic. He just throws something on the floor and out comes this “weapon on a stick” type of thing. It would auto-aim at any enemy and fire away.

 

Well, since I also had seen this in the Weapons Factory Mod, I downloaded there source and started to analyze it. To say the truth, it was more complicated than I wanted it to be. In the WF Mod, you can create a sentry, add ammo, add armor, upgrade it,  etc. I wanted something more simple. So, I created it!

 

I took a look a Greybears Oakbot code also and it gave me some ideas. Also, I used some code from the Homing Grenade Tutorial in order to make the weapon face towards the target.

 

What you do is position yourself where you want your sentry at and hit your binded sentry key and there will spawn your weapon sentry.

 

Good to place next to an entrance or, if you creating a team based Mod Like Capture the Flag, you can place it near the flag for some extra fire power.

 

Here we go! First create a file or incorporate the following code into you project file:

 

_____________________________________________________________________

 

 

// ******************************************************

// * NOC: Auto Sentry

// ******************************************************

 

void Create_Sentry (edict_t *ent)

{

 

       if (ent->client->sentry == 1)    // if sentry still exists, don't create again

       {

             return;

       }

       else                             // otherwise....

       {

             ent->client->sentry = 1;

       }

 

       edict_t *sentry;           // define sentry as an entity

       edict_t *base;             // define base as an entity

vec3_t forward;            // define foward as vector

 

       // *********** Sentry Base ****************

       base=G_Spawn();

       base->s.modelindex         = gi.modelindex("models/objects/minelite/light1/tris.md2");

       base->s.modelindex2        = 0;

       base->owner                = ent;              // who's your dady

       base->takedamage           = DAMAGE_NO;        // can't damage it

       base->movetype             = MOVETYPE_TOSS;    // throw this base and it falls to ground

       base->solid                = SOLID_BBOX;       // the base is solid

       base->think                = Base_Explode;     //

       base->nextthink            = level.time + 20;

       base->s.effects            = EF_NONE;          // Could use EF_ROTATE if Desired

 

       VectorClear(base->s.angles);                   // clear sentry angles

       VectorCopy(ent->s.origin, base->s.origin);

       VectorMA(ent->s.origin, 100, forward, base->s.origin); // Put the model in front of us

 

       base->s.origin[2] += 64;                       // a little higher please

 

       // This should set the bounderies for our sentry base

       VectorSet(base->mins,-2,-2,-12);               // Size of BBOX - a, b, c

       VectorSet(base->maxs, 2, 2, 12);               // Size of BBOX - e, f, g

 

       // *********** Sentry Weapon ****************

 

       sentry=G_Spawn();

       sentry->classname          = "sentry";

       base->sentry_weapon        = sentry;           // base's link to sentry

       sentry->sentry_base        = base;             // sentry's link to base

       sentry->s.modelindex       = gi.modelindex("models/weapons/g_machn/tris.md2");

       sentry->s.modelindex2      = 0;

       sentry->owner              = ent;              // who's your dady

       sentry->takedamage         = DAMAGE_NO;        // can't damage it

       sentry->dmg                = 5;                // Amount of Damage it will Inflict

       sentry->movetype           = MOVETYPE_NONE;    // It doesn't Move

       sentry->solid              = SOLID_BBOX;       // the Sentry is solid

       sentry->think              = Sentry_Seek;

       sentry->nextthink          = level.time + 0.1;

       sentry->s.effects          = EF_NONE;

 

       VectorCopy(base->s.origin, sentry->s.origin);

 

       sentry->s.origin[2] -= 8;                      // How High

 

       // Here for reference only

       //sentry->s.origin[1] += 12;                   // How far to side

       //sentry->s.origin[0] -= 22;                   // How much to back

 

       // This should set the bounderies for our sentry's weapon

       VectorSet(sentry->mins,-2,-2,-12);             // Size of BBOX for Touch()

       VectorSet(sentry->maxs, 2, 2, 12);             // Size of BBOX for Touch()

/*

         o---------o

         |\        |\  f

         | \       | \  \

c |  \   e- |  \

       | |   o---------o <- maxs        // might be wrong on a,b,e & f

         |   |     |   |                // but i'm sure on c & g

 mins -> o---|-----o   | |

          \  | -a   \  | g

         \ \ |       \ |

          b \|        \|

             o---------o

*/

       gi.linkentity (sentry);

       gi.linkentity (base);

}

 

// ************************************************

// * NOC: Sentry_FaceEnemy

// *************************************************

 

void Sentry_FaceEnemy(edict_t *sentry)

{

vec3_t v;

       vec3_t dir;

       vec3_t forward;

       vec3_t right;

       vec3_t up;

 

       VectorCopy(sentry->sentry_base->s.origin, sentry->s.origin);

       sentry->s.origin[2] -= 8;        // How High

 

       // Calculate enemy's direction

VectorSubtract(sentry->enemy->s.origin, sentry->s.origin, dir);

 

       // Set the second dir array to point in the same direction as the first dir

vectoangles (dir, dir);

 

// Copy the direction to the sentry's s.angle and movedir

VectorCopy (dir, sentry->s.angles);

       VectorCopy (dir, sentry->movedir);

 

VectorNormalize(dir);

}

 

// ************************************************

// * NOC: Sentry_Seek

// *************************************************

 

void Sentry_Seek(edict_t *self)

{

edict_t *target;

 

       if (self->owner->client->sentry == 0)

       {

             G_FreeEdict(self);

             return;

       }

 

       self->enemy = NULL;

 

       // look for a target

       target = findradius(NULL, self->s.origin, 700);

       while(target)

       {

             if (visible(self, target))              // is target visible?

             {

                   if (strcmp(target->classname, "player") == 0)   // is target a player ?

                   {

                         self->enemy = target;

                   }

             }

 

             // next taget

             target = findradius(target, self->s.origin, 700);

        }

 

        if (self->enemy != NULL)  // did we find an enemy?

        {

             Sentry_FaceEnemy(self);                        // face the enemy

             self->think         = sentry_fire;             // set think to fire

             self->nextthink     = level.time + 0.1;        // on next frame

        }

 

        self->nextthink = level.time + 0.1;

}

 

// ************************************************

// * NOC: Base_Explode

// *************************************************

 

void Base_Explode(edict_t *base)

{

       base->owner->client->sentry = 0;

 

       // Destroy the base in a big fireball.

       G_Spawn_Explosion(TE_EXPLOSION2, base->s.origin, base->s.origin);

 

       // Throw base debris all over the place...

       ThrowDebris(base, "models/objects/debris2/tris.md2", 3.50, base->s.origin);

       ThrowDebris(base, "models/objects/debris2/tris.md2", 2.50, base->s.origin);

       ThrowDebris(base, "models/objects/debris2/tris.md2", 1.50, base->s.origin);

       ThrowDebris(base, "models/objects/debris2/tris.md2", 4.50, base->s.origin);

       ThrowDebris(base, "models/objects/debris2/tris.md2", 3.75, base->s.origin);

       ThrowDebris(base, "models/objects/debris2/tris.md2", 2.30, base->s.origin);

       ThrowDebris(base, "models/objects/debris2/tris.md2", 1.00, base->s.origin);

 

       // Assign any Radius Damage frags to the base's owner..

       T_RadiusDamage(base, base->owner, 40, NULL, 400, MOD_SPLASH);

 

       // Free the base entity & weapon entity

       G_FreeEdict(base->sentry_weapon);

       G_FreeEdict(base);

}

 

// ************************************************

// * NOC: Sentry Fire

// *************************************************

 

void sentry_fire(edict_t *sentry)

{

       vec3_t forward, right, dir;

       vec3_t start;

 

       vec3_t from={0,0,0}, end={0,0,0};

 

       // Calculate the direction were enemy is

VectorSubtract(sentry->enemy->s.origin, sentry->s.origin, dir);

 

       // Take apart s.angles into “forward”, “right” and “up” (NULL in this case)

       AngleVectors (sentry->s.angles, forward, right, NULL);

 

       // Copy the sentry’s vector to variable “start”

VectorCopy(sentry->s.origin, start);

 

// Change Start position a little higher so it doesn’t fire from the ground

       start[2] += 26;

 

       // shoot the bastard

       fire_blaster (sentry, start, dir, sentry->dmg, 1000, EF_BLASTER, 0);

 

       sentry->think       = Sentry_Seek;      // Start looking for the enemy again

       sentry->nextthink   = level.time + 0.1; // on the next frame

 

 

}

 

_____________________________________________________________________

 

 

Now go to g_local.h and in the edict_s structure add at the following lines:

 

_____________________________________________________________________

 

       edict_t                    *sentry_base;

       edict_t                    *sentry_weapon;

 

_____________________________________________________________________

 

And at the end of thet same file add the following prototypes:

 

_____________________________________________________________________

 

 

void Sentry_FaceEnemy(edict_t *self);

void Sentry_Seek(edict_t *self);

void Create_Sentry (edict_t *ent);

void Base_Explode(edict_t *base);

void sentry_fire(edict_t * sentry

 

_____________________________________________________________________

 

Also go to g_cmds.c and add the following lines near the end of ClientCommands:

 

_____________________________________________________________________

 

 

else if (Q_stricmp(cmd, "playerlist") == 0)

             Cmd_PlayerList_f(ent);

// ******************************************************

// * JMR: Create the Sentry

// ******************************************************

else if (Q_stricmp (cmd, "sentry") == 0)

             Create_Sentry (ent);

// *************************************************

       else   // anything that doesn't match a command will be a chat

             Cmd_Say_f (ent, false, true);

 

_____________________________________________________________________

 

 

Now, in the file g_weapon.c , goto the fire_blaster procedure and insert the following code:

 

_____________________________________________________________________

 

            bolt->s.sound = gi.soundindex ("misc/lasfly.wav");

 

            // ************************************

            // * NOC: For Sentry

            // ************************************

            if (strcmp(self->classname, "sentry") != 0)

            {

                        bolt->owner = self;

            }

            else

            {

                        bolt->owner = self->owner;

            }

            // ************************************

            bolt->touch = blaster_touch;

            bolt->nextthink = level.time + 2;         bolt->think = G_FreeEdict;

            bolt->dmg = damage;

            bolt->classname = "bolt";

            if (hyper)

                        bolt->spawnflags = 1;

            gi.linkentity (bolt);

 

            // *************************

            // * NOC: For sentry

            // *************************

            if (strcmp(self->classname, "sentry") != 0)

            {

                        if (self->client)

                                    check_dodge (self, bolt->s.origin, dir, speed);

            }

            // **************************

            tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);

 

            if (tr.fraction < 1.0)

            {

                        VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);

                        bolt->touch (bolt, tr.ent, NULL, NULL);

            }

}

 

_____________________________________________________________________

 

Ok, now open p_client.c and go to the player_die procedure and add the following code:

 

_____________________________________________________________________

 

            self->svflags |= SVF_DEADMONSTER;

 

// ****************************************

// * NOC: What Happens when player Dies

// ****************************************

            self->client->sentry  = 0;         // To release Weapon Sentry

// ****************************************

 

            if (!self->deadflag)

            {

                        self->client->respawn_time = level.time + 1.0;

                        LookAtKiller (self, inflictor, attacker);

                        self->client->ps.pmove.pm_type = PM_DEAD;

 

_____________________________________________________________________

 

Great. Now go to the InitClientPersistant procedure and add this code also:

 

_____________________________________________________________________

 

            item = FindItem("Blaster");

            client->pers.selected_item = ITEM_INDEX(item);

            client->pers.inventory[client->pers.selected_item] = 1;

 

// ***************************************

// * JMR: Begining default Initialization

// ***************************************

            client->sentry  = 0;                              // For Weapon Sentry

// ***************************************

 

            client->pers.weapon = item;

            client->resp.grenade_type = G_NORMAL;

 

            client->pers.weapon = item;

            client->pers.health                              = 100;

            client->pers.max_health                      = 100;

 

_____________________________________________________________________

 

 

That’s it. Now you only have to bind that command to a key (example: bind p “sentry”) and your set.

 

Explanation:

 

Everything should be easy to follow. Take note on that little box I made. It should give you an idea of how to set up boundaries for your models.

 

Final remarks:

 

Some things I want to point out. First, I want to thank the Reno Bros. from the WF Mod, GreyBear, for his OakBot, and WarZone for his homing grenade tutorial.

 

Anyway, the Weapon Sentry could be improved upon. Like add health to it so you can be able to destroy it; change the weapon model and base.You can even change the firing subroutine so you can fire rockets, bullets, rail slugs, etc.

 

Have fun.