CBaseAnimating::LookupPoseParameter //Get a poseparameter index by name
CBaseAnimating::GetPoseParameter //Get sentry gun rotation and stuff
CBaseAnimating::SetPoseParameter //Set sentry gun rotation and stuff
for layered anims:
CBaseAnimatingOverlay::AddGestureSequence
for firing:
to get muzzle attachment position
Studio_FindAttachment
and
CBaseAnimating::GetAttachment
*/
public Plugin myinfo =
{
name = "[CSGO] Sentry Gun",
author = "Pelipoika",
description = "",
version = "1.0",
url = ""
};
//-----------------------------------------------------------------------------
// Purpose: Looks up a sequence by sequence name first, then by activity name.
// Input : label - The sequence name or activity name to look up.
// Output : Returns the sequence index of the matching sequence, or ACT_INVALID.
//-----------------------------------------------------------------------------
//LookupSequence(const char *label);
StartPrepSDKCall(SDKCall_Entity);
PrepSDKCall_SetFromConf(hConf, SDKConf_Signature, "CBaseAnimating::LookupSequence");
PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); //label
PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //return index
if((g_hLookupSequence = EndPrepSDKCall()) == INVALID_HANDLE) SetFailState("Failed to create Call for CBaseAnimating::LookupSequence");
//ResetSequence(int nSequence);
StartPrepSDKCall(SDKCall_Entity);
PrepSDKCall_SetFromConf(hConf, SDKConf_Signature, "CBaseAnimating::ResetSequence");
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
if ((g_hResetSequence = EndPrepSDKCall()) == INVALID_HANDLE) SetFailState("Failed to create SDKCall for CBaseAnimating::ResetSequence signature!");
//int CBaseAnimatingOverlay::AddLayeredSequence( int sequence, int iPriority )
StartPrepSDKCall(SDKCall_Entity);
PrepSDKCall_SetFromConf(hConf, SDKConf_Signature, "CBaseAnimatingOverlay::AddLayeredSequence");
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); //sequence
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); //priority
PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //return layer
if((g_hAddLayeredSequence = EndPrepSDKCall()) == INVALID_HANDLE) SetFailState("Failed to create Call for CBaseAnimatingOverlay::AddLayeredSequence");
//=========================================================
// StudioFrameAdvance - advance the animation frame up some interval (default 0.1) into the future
//=========================================================
StartPrepSDKCall(SDKCall_Entity);
PrepSDKCall_SetFromConf(hConf, SDKConf_Signature, "CBaseAnimating::StudioFrameAdvance");
if ((g_hStudioFrameAdvance = EndPrepSDKCall()) == INVALID_HANDLE) SetFailState("Failed to create SDKCall for CBaseAnimating::StudioFrameAdvance signature!");
//-----------------------------------------------------------------------------
// Purpose: Returns the world location and world angles of an attachment
// Input : attachment name
// Output : location and angles
//-----------------------------------------------------------------------------
StartPrepSDKCall(SDKCall_Entity);
PrepSDKCall_SetFromConf(hConf, SDKConf_Signature, "CBaseAnimating::GetAttachment");
PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); //Attachment name
PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef, _, VENCODE_FLAG_COPYBACK); //absOrigin
PrepSDKCall_AddParameter(SDKType_QAngle, SDKPass_ByRef, _, VENCODE_FLAG_COPYBACK); //absAngles
if((g_hGetAttachment = EndPrepSDKCall()) == INVALID_HANDLE) SetFailState("Failed to create Call for CBaseAnimating::GetAttachment");
for (int i = 0; i < GetEntData(this.index, 1212); i++)
{
//Offset to a layers m_fFlags.
int fFlags = LoadFromAddress(overlay + view_as<Address>(m_fFlags * i), NumberType_Int32);
//1212 = m_AnimOverlay.Count(), if this offset ever breaks; repalce it with 15 because it doesn't really matter. All you will be doing is accessing unallocated memory :o
int iCount = GetEntData(this.index, 1212);
public void SelectTargetPoint(float vecSrc[3], float vecMidEnemy[3])
{
vecMidEnemy = WorldSpaceCenter(EntRefToEntIndex(m_hEnemy));
// If we cannot see their WorldSpaceCenter ( possible, as we do our target finding based
// on the eye position of the target ) then fire at the eye position
TR_TraceRayFilter(vecSrc, vecMidEnemy, MASK_SOLID, RayType_EndPoint, AimTargetFilter, this.index);
if(TR_DidHit() && (TR_GetEntityIndex() >= MaxClients || TR_GetEntityIndex() <= 0))
{
// Hack it lower a little bit..
// The eye position is not always within the hitboxes for a standing CS Player
vecMidEnemy = EyePosition(EntRefToEntIndex(m_hEnemy));
vecMidEnemy[2] -= 5.0;
}
}
//-----------------------------------------------------------------------------
// Found a Target
//-----------------------------------------------------------------------------
public void FoundTarget(int pTarget, const float vecSoundCenter[3] )
{
m_hEnemy = EntIndexToEntRef(pTarget);
if ((m_iAmmoShells > 0 ) || (m_iAmmoRockets > 0 && m_iUpgradeLevel == 3 ))
{
/* // Play one sound to everyone but the target.
CPASFilter filter( vecSoundCenter );
if (IsPlayer(pTarget))
{
CTFPlayer *pPlayer = ToTFPlayer( pTarget );
// Play a specific sound just to the target and remove it from the genral recipient list.
CSingleUserRecipientFilter singleFilter( pPlayer );
EmitSound( singleFilter, entindex(), "Building_Sentrygun.AlertTarget" );
filter.RemoveRecipient( pPlayer );
}*/
//-----------------------------------------------------------------------------
// Look for a target
//-----------------------------------------------------------------------------
public bool FindTarget()
{
// Loop through players within 1100 units (sentry range).
float vecSentryOrigin[3]; vecSentryOrigin = EyePosition(this.index);
// Find the opposing team.
int pTeam = this.GetTeam();
// If we have an enemy get his minimum distance to check against.
float vecSegment[3];
float vecTargetCenter[3];
float flMinDist2 = 1100.0 * 1100.0;
int pTargetCurrent = INVALID_ENT_REFERENCE;
int pTargetOld = EntRefToEntIndex(m_hEnemy);
float flOldTargetDist2 = 99999999999.0;
// Sentries will try to target players first, then objects. However, if the enemy held was an object it will continue
// to try and attack it first.
for (int iPlayer = 1; iPlayer <= MaxClients; iPlayer++)
{
if(!IsClientInGame(iPlayer))
continue;
// Make sure the player is alive.
if (!IsPlayerAlive(iPlayer))
continue;
if (GetEntityFlags(iPlayer) & FL_NOTARGET)
continue;
// Store the current target distance if we come across it
if (iPlayer == pTargetOld)
{
flOldTargetDist2 = flDist2;
}
// Check to see if the target is closer than the already validated target.
if (flDist2 > flMinDist2)
continue;
// It is closer, check to see if the target is valid.
if (this.ValidTargetPlayer(iPlayer, vecSentryOrigin, vecTargetCenter))
{
flMinDist2 = flDist2;
pTargetCurrent = iPlayer;
}
}
// If we already have a target, don't check objects.
if (pTargetCurrent == INVALID_ENT_REFERENCE)
{
int iChicken = MAXPLAYERS + 1;
while((iChicken = FindEntityByClassname(iChicken, "chicken")) != -1)
{
vecTargetCenter = EyePosition(iChicken);
// Store the current target distance if we come across it
if ( iChicken == pTargetOld )
{
flOldTargetDist2 = flDist2;
}
// Check to see if the target is closer than the already validated target.
if ( flDist2 > flMinDist2 )
continue;
// It is closer, check to see if the target is valid.
if ( this.ValidTargetPlayer( iChicken, vecSentryOrigin, vecTargetCenter ) )
{
flMinDist2 = flDist2;
pTargetCurrent = iChicken;
}
}
}
// We have a target.
if (pTargetCurrent != INVALID_ENT_REFERENCE)
{
if (pTargetCurrent != pTargetOld)
{
// flMinDist2 is the new target's distance
// flOldTargetDist2 is the old target's distance
// Don't switch unless the new target is closer by some percentage
if (flMinDist2 < (flOldTargetDist2 * 0.75))
{
this.FoundTarget(pTargetCurrent, vecSentryOrigin);
}
}
return true;
}
return false;
}
public bool MoveTurret()
{
bool bMoved = false;
int iBaseTurnRate = this.GetBaseTurnRate();
// any x movement?
if (m_vecCurAngles[0] != m_vecGoalAngles[0])
{
float flDir = m_vecGoalAngles[0] > m_vecCurAngles[0] ? 1.0 : -1.0 ;
// if we started below the goal, and now we're past, peg to goal
if (flDir == 1)
{
if (m_vecCurAngles[0] > m_vecGoalAngles[0])
m_vecCurAngles[0] = m_vecGoalAngles[0];
}
else
{
if (m_vecCurAngles[0] < m_vecGoalAngles[0])
m_vecCurAngles[0] = m_vecGoalAngles[0];
}
//-----------------------------------------------------------------------------
// Rotate and scan for targets
//-----------------------------------------------------------------------------
public void SentryRotate()
{
// if we're playing a fire gesture, stop it
if (this.IsPlayingGesture("ACT_RANGE_ATTACK1"))
{
this.RemoveGesture("ACT_RANGE_ATTACK1");
}
if (this.IsPlayingGesture("ACT_RANGE_ATTACK1_LOW"))
{
this.RemoveGesture("ACT_RANGE_ATTACK1_LOW");
}
// Look for a target
if (this.FindTarget())
{
return;
}
// Rotate
if (!this.MoveTurret())
{
// Change direction
switch(m_iUpgradeLevel)
{
case 1: EmitAmbientSoundAny("sentrymp3/sentry_scan.mp3", WorldSpaceCenter(this.index));
case 2: EmitAmbientSoundAny("sentrymp3/sentry_scan2.mp3", WorldSpaceCenter(this.index));
case 3: EmitAmbientSoundAny("sentrymp3/sentry_scan3.mp3", WorldSpaceCenter(this.index));
}
// Randomly look up and down a bit
if (GetRandomFloat(0.0, 1.0) < 0.3)
{
m_vecGoalAngles[0] = float(RoundToNearest(GetRandomFloat(-10.0, 10.0)));
}
}
}
//-----------------------------------------------------------------------------
// Fire on our target
//-----------------------------------------------------------------------------
public bool Fire()
{
//NDebugOverlay::Cross3D( m_hEnemy->WorldSpaceCenter(), 10, 255, 0, 0, false, 0.1 );
float vecAimDir[3];
// Level 3 Turrets fire rockets every 3 seconds
if ( m_iUpgradeLevel == 3 && m_iAmmoRockets > 0 && m_flNextRocketAttack < GetGameTime())
{
float vecSrc[3];
float vecAng[3];
// alternate between the 2 rocket launcher ports.
if ( m_iAmmoRockets & 1 ) {
this.GetAttachment( "rocket_l", vecSrc, vecAng );
} else {
this.GetAttachment( "rocket_r", vecSrc, vecAng );
}
// Setup next rocket shot
m_flNextRocketAttack = GetGameTime() + 3;
m_iAmmoRockets--;
}
// All turrets fire shells
if ( m_iAmmoShells > 0)
{
if (!this.IsPlayingGesture("ACT_RANGE_ATTACK1"))
{
this.RemoveGesture("ACT_RANGE_ATTACK1_LOW");
this.AddGesture("ACT_RANGE_ATTACK1");
}
float vecSrc[3];
float vecAng[3];
if ( m_iUpgradeLevel > 1 )
{
// level 2 and 3 turrets alternate muzzles each time they fizzy fizzy fire.
if(m_iAmmoShells & 1)
{
this.GetAttachment( "muzzle_l", vecSrc, vecAng );
}
else
{
switch( m_iUpgradeLevel )
{
case 1: EmitAmbientSoundAny("sentrymp3/sentry_shoot.mp3", WorldSpaceCenter(this.index));
case 2: EmitAmbientSoundAny("sentrymp3/sentry_shoot2.mp3", WorldSpaceCenter(this.index));
case 3: EmitAmbientSoundAny("sentrymp3/sentry_shoot3.mp3", WorldSpaceCenter(this.index));
}
this.GetAttachment( "muzzle_r", vecSrc, vecAng );
}
}
else
{
switch( m_iUpgradeLevel )
{
case 1: EmitAmbientSoundAny("sentrymp3/sentry_shoot.mp3", WorldSpaceCenter(this.index));
case 2: EmitAmbientSoundAny("sentrymp3/sentry_shoot2.mp3", WorldSpaceCenter(this.index));
case 3: EmitAmbientSoundAny("sentrymp3/sentry_shoot3.mp3", WorldSpaceCenter(this.index));
}
m_iAmmoShells--;
}
else
{
if (m_iUpgradeLevel > 1)
{
if (!this.IsPlayingGesture("ACT_RANGE_ATTACK1_LOW"))
{
this.RemoveGesture("ACT_RANGE_ATTACK1");
this.AddGesture("ACT_RANGE_ATTACK1_LOW");
}
}
// Out of ammo, play a click
EmitAmbientSoundAny("sentrymp3/sentry_empty.mp3", WorldSpaceCenter(this.index));
m_flNextAttack = GetGameTime() + 0.2;
}
return true;
}
//-----------------------------------------------------------------------------
// Make sure our target is still valid, and if so, fire at it
//-----------------------------------------------------------------------------
public void Attack()
{
if (!this.FindTarget())
{
#if defined DEBUG
PrintToServer("Attack() No target");
#endif
m_iState = SENTRY_STATE_SEARCHING;
m_hEnemy = INVALID_ENT_REFERENCE;
return;
}
angToTarget[1] = UTIL_AngleMod(angToTarget[1]);
if (angToTarget[0] < -180)
angToTarget[0] += 360;
if (angToTarget[0] > 180)
angToTarget[0] -= 360;
// now all numbers should be in [1...360]
// pin to turret limitations to [-50...50]
if (angToTarget[0] > 50)
angToTarget[0] = 50.0;
else if (angToTarget[0] < -50)
angToTarget[0] = -50.0;
// Fire on the target if it's within 10 units of being aimed right at it
if ( m_flNextAttack <= GetGameTime() && GetVectorLength(subtracted) <= 10 )
{
this.Fire();
if(GetVectorLength(subtracted) > 10)
{
// if we're playing a fire gesture, stop it
if (this.IsPlayingGesture("ACT_RANGE_ATTACK1"))
{
this.RemoveGesture("ACT_RANGE_ATTACK1");
}
}
}
}
public Action Command_Sentry(int client, int argc)
{
if(client <= 0 || client > MaxClients && !IsClientInGame(client))
return Plugin_Handled;
switch( m_iState )
{
case SENTRY_STATE_SEARCHING: sentry.SentryRotate();
case SENTRY_STATE_ATTACKING: sentry.Attack();
//case SENTRY_STATE_UPGRADING: sentry.UpgradeThink();
}
}
//TODO
// Trace between position before setting new origin and new origin to not miss anything
public void OnRocketThink(int entity, int client)
{
static int iThinkWhenClient = 1;
//This bad code ensures that we don't think any more than we should
if(!IsClientInGame(iThinkWhenClient)) {
iThinkWhenClient = client;
}
if(iThinkWhenClient != client) {
return;
}
float ang[3]; ang = GetAbsAngles(entity);
float pos[3]; pos = GetAbsOrigin(entity);
// Sentryguns are perfectly accurate, but this doesn't look good for tracers.
// Add a little noise to them, but not enough so that it looks like they're missing.
endpos[0] += GetRandomFloat(-10.0, 10.0);
endpos[1] += GetRandomFloat(-10.0, 10.0);
endpos[2] += GetRandomFloat(-10.0, 10.0);
if(TR_GetEntityIndex(trace) <= 0 || TR_GetEntityIndex(trace) > MaxClients)
{
//Can't get surface properties from traces unfortunately.
//Just another shortsighting from the SM devs :///
TE_DispatchEffect("impact_dirt", endpos, endpos, vecNormal);
TE_SendToAll();