Vector UTIL_RelationToWorld(edict_t *player, Vector relation);
Vector UTIL_LengthFromVector(Vector relation, float length);
Vector UTIL_FixAngles( Vector vec );
float UTIL_FixFloatAngle(float vec);
Now open "UTIL.CPP" and add these functions...
Vector UTIL_FixAngles( Vector vec )
{
vec.x = UTIL_FixFloatAngle(vec.x);
vec.y = UTIL_FixFloatAngle(vec.y);
vec.z = UTIL_FixFloatAngle(vec.z);
return vec;
}
float UTIL_FixFloatAngle(float vec)
{
if (vec > 180.0) vec -= 360.0;
if (vec < -180.0) vec += 360.0;
return vec;
}
Vector UTIL_RelationToWorld(edict_t *player, Vector relation)
{
if( !player )
return Vector(0,0,0);
Vector v_src,v_dest,v_forward,v_viewpoint,v_origin;
v_origin = player->v.origin;
v_src = v_origin + player->v.view_ofs;
v_viewpoint = player->v.v_angle + player->v.punchangle;
UTIL_MakeVectors(v_viewpoint);
v_forward = v_viewpoint + (relation * 8192.0);
v_dest = v_src + v_forward;
return v_dest;
}
Vector UTIL_LengthFromVector(Vector relation, float length)
{
return ( (relation / relation.Length()) * length );
}
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "weapons.h"
#include "player.h"
#include "gamerules.h"
#include "explode.h" // for effects
#include "teamplay_gamerules.h" // for knowing teams
#define SENTRY_MAX_PITCH 10.0
This is the maximum angle the sentry will look up and down, you can change this totally if you want, because I've just been using pev->angle to make it easier but when you change the pev->angle.x to a high value, the sentry looks weird!#define SHOOT_ROCKET_TIME 2.0
#define SHOOT_SHELL_TIME 0.1
#define SENTRY_MAX_SHELLS 400
#define SENTRY_MAX_ROCKETS 50
Okay that's nearly all of the constants, we just need one more. I've added "build" functions to my sentry so this means it will take SENTRY_BUILD_TIME seconds for it to finish being built and start working. This is defined like this below:
#define SENTRY_BUILD_TIME 4.0 // 4 Seconds to build sentry
class CMySentry : public CBaseEntity
{
public:
void Spawn ( void );
void Precache ( void );
int AddShells ( int l_iNewShells ); // Returns the amount of shells USED...
int AddRockets ( int l_iNewRockets ); // Returns the amount of rockets USED...
void EXPORT SentryThink ( void );
void EXPORT SentryBuildThink( void );
void EXPORT MySentryDeath ( void );
void Dismantle ( void );
virtual int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType);
private:
void Aim ( void );
void Shoot ( void );
void FireShell ( void );
void FireRocket ( void );
void RemoveFromPlayer ( void );
CBaseEntity *GetBestEnemy ( void );
int m_iRockets;
int m_iShells;
float m_fLastShootRocket;
float m_fLastShootShell;
float m_fLastAimTime;
float m_fShellPercentFull;
float m_fRocketPercentFull;
float m_fBuildTime; // Time the sentry was BUILT...
float m_fLastCheckVisibleEnemy; // Last checked to see if enemy was visible
char m_szTeamName[MAX_TEAMNAME_LENGTH];
CBaseEntity *m_pEnemy; // Current enemy
Vector m_vBarrel; // Barrel position (gun point) NOT USED!
};
LINK_ENTITY_TO_CLASS( MySentry, CMySentry );
This makes the class (CMySentry) link to the classname of "MySentry" you can change this to whatever you want.
void CMySentry :: SentryBuildThink( void )
{
pev->nextthink = gpGlobals->time + 0.1;
if( (m_fBuildTime + SENTRY_BUILD_TIME) <= gpGlobals->time )
{
EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_spinup.wav", 1.0, ATTN_NORM); // Spin up sound (when built)
pev->effects &= ~EF_NODRAW; // Make visible again
SetThink(SentryThink); // Change think function to normal sentry think function
}
}
This is the main think function below. What this does is basically checks if the current enemy is still visible, if yes, aim and shoot at it, if not remove the enemy.
void CMySentry :: SentryThink ( void )
{
pev->nextthink = gpGlobals->time + 0.1;
// BEGIN -- UPDATE BARREL POSITION (NOT USED!!)
UTIL_MakeVectors(pev->angles);
Vector v_End = UTIL_RelationToWorld(ENT(pev),gpGlobals->v_forward);
m_vBarrel = UTIL_LengthFromVector((v_End - pev->origin),4);
m_vBarrel.z += 32;
// END -- UPDATE BARREL POSITION (NOT USED!!)
if( m_pEnemy && (m_fLastCheckVisibleEnemy <= gpGlobals->time) )
{
TraceResult tr;
UTIL_TraceLine(pev->origin,m_pEnemy->pev->origin + m_pEnemy->pev->view_ofs,ignore_monsters,ENT(pev),&tr);
if( tr.flFraction < 1.0 )
m_pEnemy = NULL;
m_fLastCheckVisibleEnemy = gpGlobals->time + 0.5; // Re-check visible enemy again in 0.5 seconds
}
if( (m_fLastAimTime + 0.2) <= gpGlobals->time ) // re-Aim every 0.2 seconds
{
m_fLastAimTime = gpGlobals->time;
Aim(); // Aim at Nearest Enemy
}
if ( m_pEnemy ) // if got an enemy
{
if( m_pEnemy->IsAlive() )
Shoot(); // Shoot Enemy (Should be facing enemy from Aim() )
else
m_pEnemy = NULL;
}
}
Next should be the spawn function, this is where we initialise stuff and call ONLY call Precache() here. Also the sentry is set to invisible (using EF_NODRAW bitset) and the Think function is set to the BuildThink function so it will wait SENTRY_BUILD_TIME before functioning and returining visible to the player. (See the BuildThink function explanation)
void CMySentry :: Spawn ( void )
{
Precache( );
pev->movetype = MOVETYPE_FLY;
pev->health = 100;
pev->max_health = pev->health;
pev->armorvalue = 100;
m_iRockets = SENTRY_MAX_ROCKETS / 2; //
m_iShells = SENTRY_MAX_SHELLS / 2;
pev->gravity = 1;
SET_MODEL(ENT(pev), "models/MySentry.mdl"); // the name of the model YOU want to use.
UTIL_SetOrigin( pev, pev->origin );
UTIL_SetSize(pev, Vector(-24, -24, -20), Vector(24, 24, 20));
pev->nextthink = gpGlobals->time + 0.1;
pev->movetype = MOVETYPE_FLY;
pev->sequence = 0;
pev->frame = 0;
pev->solid = SOLID_SLIDEBOX;
pev->takedamage = DAMAGE_AIM;
SetBits (pev->flags, FL_MONSTER);
m_fBuildTime = gpGlobals->time + 0.1;
pev->effects |= EF_NODRAW;
if( pev->owner )
{
if( pev->owner->v.flags & FL_CLIENT )
strcpy(m_szTeamName,GetClassPtr((CBasePlayer*)pev)->m_szTeamName); // Initialise the team ID of the sentry
}
else
strcpy(m_szTeamName,""); // Initialise the team ID of the sentry
EMIT_SOUND(ENT(pev), CHAN_BODY, "sentry/building.wav", 1.0, ATTN_NORM);
SetThink(SentryBuildThink);
}
The m_szTeamName string is the TeamName of the player who built it. Once the player builds the sentry, set the TeamName to the pev->owner's
void CMySentry :: Precache ( void )
{
PRECACHE_MODEL("models/MySentry.mdl"); // The name of the model YOU want to use (NOTE: this MUST be the same as the model you set the sentry to)
PRECACHE_SOUND("turret/tu_fire1.wav");
PRECACHE_SOUND("turret/tu_die.wav");
PRECACHE_SOUND("turret/tu_die2.wav");
PRECACHE_SOUND("turret/tu_die3.wav");
PRECACHE_SOUND("turret/tu_spinup.wav");
PRECACHE_SOUND("sentry/building.wav"); // Building sound
}
We need the aim function to aim at its enemy so the rockets etc wikll face in the correct direction.
void CMySentry :: Aim ( void )
{
if( m_pEnemy )
{
Vector v_Comp = (m_pEnemy->pev->origin + pev->view_ofs) - (pev->origin + pev->view_ofs);
Vector v_IdealAngles = UTIL_VecToAngles(v_Comp);
pev->angles = UTIL_FixAngles(v_IdealAngles);
pev->angles.x = -pev->angles.x; // Must invert pitch
pev->angles.z = 0;
if (pev->angles.x > SENTRY_MAX_PITCH)
pev->angles.x = SENTRY_MAX_PITCH;
if( pev->angles.x < -SENTRY_MAX_PITCH)
pev->angles.x = -SENTRY_MAX_PITCH;
}
else // Focus on one enemy at a time
{
m_pEnemy = GetBestEnemy();
}
}
Simple function below is to get the best enemy. If The TeamNames don't match with a found enemy, it will set it as its enemy. This function is called by ::Aim()
CBaseEntity *CMySentry :: GetBestEnemy (void)
{
CBaseEntity *l_pFoundEnemy = NULL;
CBaseEntity *l_pBestEnemy = NULL;
CBasePlayer *l_pFoundPlayer = NULL;
TraceResult tr;
float min_distance = 9999.0;
float distance = 0.0;
while ( (l_pFoundEnemy = UTIL_FindEntityByClassname( l_pFoundEnemy, "player")) != NULL )
{
if( !l_pFoundEnemy->IsAlive() )
continue;
if( l_pFoundEnemy->pev->flags & FL_CLIENT )
{
l_pFoundPlayer = GetClassPtr((CBasePlayer *)l_pFoundEnemy->pev);
if( strcmp(l_pFoundPlayer->m_szTeamName,m_szTeamName) ) // If this is an enemy
{
UTIL_TraceLine((pev->origin + pev->view_ofs),l_pFoundEnemy->pev->origin,ignore_monsters,dont_ignore_glass,pev->pContainingEntity,&tr);
if( tr.flFraction >= 1.0 )
{
distance = (l_pFoundPlayer->pev->origin - pev->origin).Length();
if( distance < min_distance )
{
min_distance = distance;
l_pBestEnemy = l_pFoundEnemy;
}
}
}
}
}
return l_pBestEnemy;
}
Now, The shoot function. This will check if it's time to fire another shell/rocket, and check if there is enough ammo, and then shoot a shell or rocket using FireRocket() and/or FireShell().
void CMySentry :: Shoot ( void )
{
if( m_iRockets > 0 )
{
if( (m_fLastShootRocket + SHOOT_ROCKET_TIME) <= gpGlobals->time )
{
m_fLastShootRocket = gpGlobals->time;
FireRocket();
}
}
if( m_iShells > 0 )
{
if( (m_fLastShootShell + SHOOT_SHELL_TIME) <= gpGlobals->time )
{
m_fLastShootShell = gpGlobals->time;
FireShell();
}
}
}
The fire rocket and fire shell functions (below) should be pretty similar except a rocket or shell is fired in the facing direction.
void CMySentry :: FireRocket ( void )
{
UTIL_MakeVectors(pev->angles);
CBaseEntity *m_Rocket = CBaseEntity::Create( "rpg_rocket", pev->origin, pev->angles, edict() );
UTIL_SetOrigin(m_Rocket->pev,(pev->origin + m_vBarrel));
UTIL_MakeVectors(m_Rocket->pev->angles);
m_Rocket->pev->angles = pev->angles;
Vector v_Forward = UTIL_RelationToWorld(pev->pContainingEntity,gpGlobals->v_forward);
m_Rocket->pev->velocity = ( (v_Forward - pev->origin) / (( v_Forward - pev->origin ).Length()) ) * 200.0;
m_iRockets --;
}
void CMySentry :: FireShell ( void )
{
TraceResult tr;
UTIL_MakeVectors( pev->angles );
Vector vecSrc = pev->origin + pev->view_ofs;
Vector vecDir = gpGlobals->v_forward * 8192.0;
UTIL_TraceLine(vecSrc, vecSrc + vecDir, dont_ignore_monsters, ENT(pev), &tr);
//if( tr.pHit )
// ALERT(at_console,"%s\n",STRING(tr.pHit->v.classname));
FireBullets(1,vecSrc,vecSrc + vecDir,VECTOR_CONE_3DEGREES,4096.0,BULLET_MONSTER_12MM,4,5,pev);
EMIT_SOUND(ENT(pev),CHAN_BODY,"turret/tu_fire1.wav",1.0,ATTN_NORM); // Emit turret shoot sound
m_iShells --; // reduce shells
}
Your own Take Damage function. You'll need this to add shells/rockets to it at a later time.
int CMySentry::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
{
if ( !pev->takedamage )
return 0;
if ( pev->owner && (pev->Attacker == &pev->owner->v))
{
// Do stuff in here to add shells/rockets from the player perhaps...
if( CVAR_GET_FLOAT("mp_friendlyfire") < 1.0 )
return;
}
pev->health -= flDamage; // Remove this damage
if (pev->health <= 0)
{
pev->health = 0;
pev->takedamage = DAMAGE_NO; // Don't take anymore damage
pev->dmgtime = gpGlobals->time; // Set damage time
ClearBits (pev->flags, FL_MONSTER);
SetThink(MySentryDeath); // Change think funtion so I don't try to shoot etc.
pev->nextthink = gpGlobals->time + 0.1; // Think again in 0.1 seconds
return 0;
}
else
{
if (RANDOM_FLOAT( 0, 10 ) > 5)
UTIL_Sparks( pev->origin ); // Some Sparks
}
return 1;
}
This death function is continously called while the sentry is "dying", give it a funky death! I took some code from the turret.cpp file and made the turret look as tho it malfunctions by setting the avelocity (angle velocity)/yaw angles to +/- randomly and the speed of the angle velocity will slow down after time. Also sparks and smoke will appear!
void CMySentry :: MySentryDeath ( void )
{
pev->nextthink = gpGlobals->time + 0.1;
if (pev->deadflag != DEAD_DEAD)
{
pev->deadflag = DEAD_DEAD;
float flRndSound = RANDOM_FLOAT ( 0 , 1 );
if ( flRndSound <= 0.33 )
EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM);
else if ( flRndSound <= 0.66 )
EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM);
else
EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM);
//EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100);
pev->solid = SOLID_NOT;
}
float f_time = (pev->dmgtime + 5) - gpGlobals->time;
if( f_time > 0.0 )
{
if( RANDOM_LONG( 0, 100 ) <= 50 )
pev->avelocity.y = f_time * 100;
else
pev->avelocity.y = -f_time * 100;
}
else
pev->avelocity.y = 0;
Vector vecSrc, vecAng;
vecSrc = pev->origin;
if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time)
{
// lots of smoke
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
WRITE_BYTE( TE_SMOKE );
WRITE_COORD( vecSrc.x + RANDOM_FLOAT( -16, 16 ) );
WRITE_COORD( vecSrc.y + RANDOM_FLOAT( -16, 16 ) );
WRITE_COORD( vecSrc.z - 32 );
WRITE_SHORT( g_sModelIndexSmoke );
WRITE_BYTE( 15 ); // scale * 10
WRITE_BYTE( 8 ); // framerate
MESSAGE_END();
}
if (pev->dmgtime + RANDOM_FLOAT( 0, 8 ) > gpGlobals->time)
{
UTIL_Sparks( vecSrc );
}
if ( (pev->dmgtime + 5) < gpGlobals->time)
{
ExplosionCreate(pev->origin,pev->angles,NULL,50,0);
RemoveFromPlayer();
Killed(pev,GIB_NEVER);
SetThink( NULL );
}
}
I've made a dismantle function which just kills the sentry, and remove from player function which will set the m_Sentry in CBasePlayer to NULL.
void CMySentry :: Dismantle ( void )
{
RemoveFromPlayer();
Killed(pev,GIB_NEVER);
}
void CMySentry :: RemoveFromPlayer ( void )
{
if( pev->owner )
{
if( pev->owner->v.flags & FL_CLIENT )
{
CBasePlayer *pPlayer = GetClassPtr((CBasePlayer*)&pev->owner->v);
pPlayer->m_Sentry = NULL;
}
}
}
Now the adding shells and rockets...
//------------------------------
// ADD ROCKETS / SHELLS
//-----------------------------
// Adds rockets/shells to the sentry and returns the difference from the new rockets/shells for use when taking rockets/shells from a player into the sentry
int CMySentry :: AddShells ( int l_iNewShells )
{
int l_iOldShells = 0;
if( m_iShells == SENTRY_MAX_SHELLS )
{
return l_iOldShells;
}
else if ( (m_iShells + l_iNewShells) >= SENTRY_MAX_SHELLS )
{
l_iOldShells = SENTRY_MAX_SHELLS - m_iShells;
m_iShells = SENTRY_MAX_SHELLS;
return l_iOldShells;
}
else
{
m_iShells += l_iNewShells;
return l_iNewShells;
}
return 0;
}
int CMySentry :: AddRockets ( int l_iNewRockets )
{
int l_iOldRockets = 0;
if( m_iRockets == SENTRY_MAX_ROCKETS )
{
return l_iOldRockets;
}
else if ( (m_iRockets + l_iNewRockets) >= SENTRY_MAX_ROCKETS )
{
l_iOldRockets = SENTRY_MAX_ROCKETS - m_iRockets;
m_iRockets = SENTRY_MAX_ROCKETS;
return l_iOldRockets;
}
else
{
m_iRockets += l_iNewRockets;
return l_iNewRockets;
}
return 0;
}
Now that's all the sentry code. To build a sentry, open up CLIENT.CPP and look in the ClientCommand Function. You'll need to add your own command to build a sentry.else if ( FStrEq(pcmd, "build_sentry" ) )
{
if( m_pPlayer->m_Sentry )
{
ClientPrint( &pEntity->v, HUD_PRINTCENTER, "Sentry Already Built!\n");
}
else
{
Vector origin = UTIL_ForwardPosition(pEntity,64.0);
origin.z = m_pPlayer->pev->absmin.z + 24;
CBaseEntity *Sentry = CBaseEntity::Create( "avp_sentry", origin, pev->angles, pEntity );
if( !FNullEnt(Sentry->edict()) )
{
m_pPlayer->m_Sentry = Sentry;
Sentry->pev->owner = pEntity;
ClientPrint( pev, HUD_PRINTCENTER, "Building Sentry...\n");
}
else
ALERT(at_console,"Error allocating new edict for sentry!\n");
}
}
You must log in to post a comment. You can login or register a new account.