
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.