mirror of
https://github.com/unknownworlds/NS.git
synced 2024-12-12 21:51:52 +00:00
673 lines
17 KiB
C++
673 lines
17 KiB
C++
|
//======== (C) Copyright 2001 Charles G. Cleveland All rights reserved. =========
|
||
|
//
|
||
|
// The copyright to the contents herein is the property of Charles G. Cleveland.
|
||
|
// The contents may be used and/or copied only with the written permission of
|
||
|
// Charles G. Cleveland, or in accordance with the terms and conditions stipulated in
|
||
|
// the agreement/contract under which the contents have been supplied.
|
||
|
//
|
||
|
// Purpose: Base NS turret, used for all types of automated enemy detection
|
||
|
//
|
||
|
// $Workfile: AvHTurret.cpp$
|
||
|
// $Date: 2002/11/22 21:24:59 $
|
||
|
//
|
||
|
//-------------------------------------------------------------------------------
|
||
|
// $Log: AvHTurret.cpp,v $
|
||
|
// Revision 1.12 2002/11/22 21:24:59 Flayra
|
||
|
// - Fixed turret factory abuse, where turrets became active after recycling the nearby turret factory before turret was fully contructed.
|
||
|
// - Fixed bug where siege turrets became re-activated after building a regular turret factory nearby.
|
||
|
//
|
||
|
// Revision 1.11 2002/11/06 01:39:21 Flayra
|
||
|
// - Turrets now need an active turret factory to be active
|
||
|
//
|
||
|
// Revision 1.10 2002/10/03 19:10:19 Flayra
|
||
|
// - Turret intervals sped up again
|
||
|
//
|
||
|
// Revision 1.9 2002/09/25 20:52:24 Flayra
|
||
|
// - Performance improvements
|
||
|
//
|
||
|
// Revision 1.8 2002/09/23 22:36:37 Flayra
|
||
|
// - Turrets now reacquire closer targets intermittently
|
||
|
//
|
||
|
// Revision 1.7 2002/08/31 18:01:03 Flayra
|
||
|
// - Work at VALVe
|
||
|
//
|
||
|
// Revision 1.6 2002/08/16 02:49:00 Flayra
|
||
|
// - New damage model
|
||
|
//
|
||
|
// Revision 1.5 2002/07/23 17:34:07 Flayra
|
||
|
// - Turrets track range in 2D, turrets can not require LOS if desired (for siege)
|
||
|
//
|
||
|
// Revision 1.4 2002/07/01 21:23:00 Flayra
|
||
|
// - Added generic vertical FOV to allow alien turrets to shoot very high and low
|
||
|
//
|
||
|
// Revision 1.3 2002/06/03 17:02:30 Flayra
|
||
|
// - Reduced turret volume
|
||
|
//
|
||
|
// Revision 1.2 2002/05/28 18:11:59 Flayra
|
||
|
// - Put in slower, randomish rate of fire for turrets for sound variance and drama, don't play ping if no ping sound specified (crashing with offensive tower)
|
||
|
//
|
||
|
// Revision 1.1 2002/05/23 02:32:57 Flayra
|
||
|
// - Post-crash checkin. Restored @Backup from around 4/16. Contains changes for last four weeks of development.
|
||
|
//
|
||
|
//===============================================================================
|
||
|
#include "mod/AvHTurret.h"
|
||
|
#include "mod/AvHMarineEquipmentConstants.h"
|
||
|
#include "mod/AvHServerUtil.h"
|
||
|
#include "mod/AvHCloakable.h"
|
||
|
#include "util/MathUtil.h"
|
||
|
|
||
|
const float kPingInterval = 3.0f;
|
||
|
const float kPingVolume = .4f;
|
||
|
const float kTurretThinkInterval = .05f;
|
||
|
const float kTurretSearchScalar = kTurretThinkInterval*(-90.0f/180)*M_PI;
|
||
|
const float kTurretUpdateEnemyInterval = .25f;
|
||
|
|
||
|
AvHTurret::AvHTurret(AvHTechID inTechID, AvHMessageID inMessageID, char* inClassName, int inUser3) : AvHBaseBuildable(inTechID, inMessageID, inClassName, inUser3)
|
||
|
{
|
||
|
this->Init();
|
||
|
}
|
||
|
|
||
|
void AvHTurret::Init()
|
||
|
{
|
||
|
this->m_hEnemy = 0;
|
||
|
this->m_flFieldOfView = 0;
|
||
|
this->mTimeOfLastAttack = -1;
|
||
|
this->mTimeOfNextAttack = -1;
|
||
|
this->mTimeOfLastUpdateEnemy = -1;
|
||
|
|
||
|
this->m_fTurnRate = 0;
|
||
|
|
||
|
this->mGoalQuat = this->mCurQuat = Quat(0, 0, 0, 1);
|
||
|
|
||
|
this->mNextPingTime = 0;
|
||
|
|
||
|
this->mEnabled = false;
|
||
|
}
|
||
|
|
||
|
void AvHTurret::ResetEntity()
|
||
|
{
|
||
|
AvHBaseBuildable::ResetEntity();
|
||
|
|
||
|
this->Init();
|
||
|
|
||
|
this->Setup();
|
||
|
}
|
||
|
|
||
|
char* AvHTurret::GetActiveSound() const
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
char* AvHTurret::GetAlertSound() const
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
char* AvHTurret::GetPingSound() const
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetPointValue(void) const
|
||
|
{
|
||
|
return BALANCE_VAR(kScoringTurretValue);
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetSetEnabledAnimation() const
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetActiveAnimation() const
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetSpawnAnimation() const
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetDeployAnimation() const
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
bool AvHTurret::GetEnabledState() const
|
||
|
{
|
||
|
return this->mEnabled;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetTakeDamageAnimation() const
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetIdle1Animation() const
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetIdle2Animation() const
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetKilledAnimation() const
|
||
|
{
|
||
|
return 5;
|
||
|
}
|
||
|
|
||
|
bool AvHTurret::GetRequiresLOS() const
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::GetDamageType() const
|
||
|
{
|
||
|
// Turrets to half damage to heavy players
|
||
|
return DMG_BULLET | DMG_NEVERGIB | NS_DMG_LIGHT;
|
||
|
}
|
||
|
|
||
|
int AvHTurret::IRelationship (CBaseEntity* inTarget)
|
||
|
{
|
||
|
int theRelationship = R_NO;
|
||
|
|
||
|
// Ignore the roaches for heaven's sake
|
||
|
if(!FClassnameIs( inTarget->pev, "monster_cockroach" ))
|
||
|
{
|
||
|
// Don't shoot at cloaked players
|
||
|
AvHCloakable* theCloakable = dynamic_cast<AvHCloakable*>(inTarget);
|
||
|
if(theCloakable && theCloakable->GetIsCloaked())
|
||
|
{
|
||
|
theRelationship = R_NO;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Shoot all monsters that aren't on our team
|
||
|
CBaseMonster* theMonsterPointer = dynamic_cast<CBaseMonster*>(inTarget);
|
||
|
if(theMonsterPointer && (theMonsterPointer->pev->team != this->pev->team))
|
||
|
{
|
||
|
theRelationship = R_DL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Look at own team vs. incoming team
|
||
|
AvHTeamNumber inTeam = (AvHTeamNumber)inTarget->pev->team;
|
||
|
if(inTeam != TEAM_IND)
|
||
|
{
|
||
|
if(inTeam == this->pev->team)
|
||
|
{
|
||
|
theRelationship = R_AL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Don't keep switching targets constantly
|
||
|
theRelationship = R_DL;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//theRelationship = CSentry::IRelationship(inTarget);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return theRelationship;
|
||
|
}
|
||
|
|
||
|
bool AvHTurret::GetIsValidTarget(CBaseEntity* inEntity) const
|
||
|
{
|
||
|
bool theTargetIsValid = false;
|
||
|
|
||
|
if((inEntity->pev->team != this->pev->team) && (inEntity->pev->team != 0) && (inEntity->pev->takedamage))
|
||
|
{
|
||
|
float theDistanceToCurrentEnemy = AvHSUEyeToBodyXYDistance(this->pev, inEntity);
|
||
|
if(theDistanceToCurrentEnemy <= this->GetXYRange())
|
||
|
{
|
||
|
// Players are targettable when
|
||
|
AvHPlayer* thePlayer = dynamic_cast<AvHPlayer*>(inEntity);
|
||
|
if(!thePlayer || !thePlayer->GetIsCloaked() || !thePlayer->GetIsBeingDigested()) //added digestion - elven
|
||
|
{
|
||
|
// TODO: Check to be sure enemy is still visible
|
||
|
theTargetIsValid = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AvHCloakable* theCloakable = dynamic_cast<AvHCloakable*>(inEntity);
|
||
|
if(!theCloakable || (theCloakable->GetOpacity() > 0.0f))
|
||
|
{
|
||
|
theTargetIsValid = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return theTargetIsValid;
|
||
|
}
|
||
|
|
||
|
void AvHTurret::Ping(void)
|
||
|
{
|
||
|
// Make the pinging noise every second while searching
|
||
|
if(this->mNextPingTime == 0)
|
||
|
{
|
||
|
this->mNextPingTime = gpGlobals->time + kPingInterval;
|
||
|
}
|
||
|
else if(this->mNextPingTime <= gpGlobals->time)
|
||
|
{
|
||
|
char* thePingSound = this->GetPingSound();
|
||
|
if(thePingSound)
|
||
|
{
|
||
|
this->mNextPingTime = gpGlobals->time + kPingInterval;
|
||
|
EMIT_SOUND(ENT(this->pev), CHAN_ITEM, thePingSound, kPingVolume, ATTN_STATIC);
|
||
|
//EyeOn( );
|
||
|
}
|
||
|
}
|
||
|
// else if (m_eyeBrightness > 0)
|
||
|
// {
|
||
|
// EyeOff( );
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
//=========================================================
|
||
|
// FInViewCone - returns true is the passed ent is in
|
||
|
// the caller's forward view cone. The dot product is performed
|
||
|
// in 2d, making the view cone infinitely tall.
|
||
|
//=========================================================
|
||
|
BOOL AvHTurret::FInViewCone(CBaseEntity* inEntity)
|
||
|
{
|
||
|
Vector2D vec2LOS;
|
||
|
float flDot;
|
||
|
|
||
|
UTIL_MakeVectors ( pev->angles );
|
||
|
|
||
|
vec2LOS = ( inEntity->pev->origin - pev->origin ).Make2D();
|
||
|
vec2LOS = vec2LOS.Normalize();
|
||
|
|
||
|
flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() );
|
||
|
|
||
|
if ( flDot > this->m_flFieldOfView )
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=========================================================
|
||
|
// FInViewCone - returns true is the passed vector is in
|
||
|
// the caller's forward view cone. The dot product is performed
|
||
|
// in 2d, making the view cone infinitely tall.
|
||
|
//=========================================================
|
||
|
BOOL AvHTurret::FInViewCone(Vector* inOrigin)
|
||
|
{
|
||
|
Vector2D vec2LOS;
|
||
|
float flDot;
|
||
|
|
||
|
UTIL_MakeVectors ( pev->angles );
|
||
|
|
||
|
vec2LOS = ( *inOrigin - pev->origin ).Make2D();
|
||
|
vec2LOS = vec2LOS.Normalize();
|
||
|
|
||
|
flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() );
|
||
|
|
||
|
if ( flDot > this->m_flFieldOfView )
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// This function takes a lot of CPU, so make sure it's not called often! Don't call this function directly, use UpdateEnemy instead whenever possible.
|
||
|
CBaseEntity* AvHTurret::FindBestEnemy()
|
||
|
{
|
||
|
PROFILE_START()
|
||
|
CBaseEntity* theEntityList[100];
|
||
|
|
||
|
int theMaxRange = this->GetXYRange();
|
||
|
|
||
|
Vector delta = Vector(theMaxRange, theMaxRange, theMaxRange);
|
||
|
CBaseEntity* theCurrentEntity = NULL;
|
||
|
CBaseEntity* theBestPlayer = NULL;
|
||
|
CBaseEntity* theBestStructure = NULL;
|
||
|
|
||
|
float theCurrentEntityRange = 100000;
|
||
|
|
||
|
// Find only monsters/clients in box, NOT limited to PVS
|
||
|
int theCount = UTIL_EntitiesInBox(theEntityList, 100, this->pev->origin - delta, this->pev->origin + delta, FL_CLIENT | FL_MONSTER);
|
||
|
for(int i = 0; i < theCount; i++ )
|
||
|
{
|
||
|
theCurrentEntity = theEntityList[i];
|
||
|
if((theCurrentEntity != this) && theCurrentEntity->IsAlive())
|
||
|
{
|
||
|
// the looker will want to consider this entity
|
||
|
// don't check anything else about an entity that can't be seen, or an entity that you don't care about.
|
||
|
if(this->IRelationship(theCurrentEntity ) != R_NO && FInViewCone(theCurrentEntity) && !FBitSet(theCurrentEntity->pev->flags, FL_NOTARGET))
|
||
|
{
|
||
|
AvHPlayer* thePlayer = dynamic_cast<AvHPlayer*>(theCurrentEntity);
|
||
|
if(!thePlayer || thePlayer->GetCanBeAffectedByEnemies())
|
||
|
{
|
||
|
if(this->GetIsValidTarget(theCurrentEntity))
|
||
|
{
|
||
|
// Find nearest enemy
|
||
|
float theRangeToTarget = VectorDistance2D(this->pev->origin, theCurrentEntity->pev->origin);
|
||
|
if(theRangeToTarget < theCurrentEntityRange)
|
||
|
{
|
||
|
// FVisible is expensive, so defer until necessary
|
||
|
if(!this->GetRequiresLOS() || FVisible(theCurrentEntity))
|
||
|
{
|
||
|
theCurrentEntityRange = theRangeToTarget;
|
||
|
if ( thePlayer )
|
||
|
{
|
||
|
theBestPlayer = theCurrentEntity;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
theBestStructure = theCurrentEntity;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
PROFILE_END(kAvHTurretFindBestEnemy);
|
||
|
|
||
|
return (theBestPlayer != NULL ) ? theBestPlayer : theBestStructure;
|
||
|
}
|
||
|
|
||
|
// Return degrees from center turret can aim up or down
|
||
|
int AvHTurret::GetVerticalFOV() const
|
||
|
{
|
||
|
return 30;
|
||
|
}
|
||
|
|
||
|
void AvHTurret::UpdateEnemy()
|
||
|
{
|
||
|
// If enabled
|
||
|
if(this->mEnabled)
|
||
|
{
|
||
|
// If time to find new enemy
|
||
|
float theCurrentTime = gpGlobals->time;
|
||
|
|
||
|
if((this->mTimeOfLastUpdateEnemy == -1) || (theCurrentTime > (this->mTimeOfLastUpdateEnemy + kTurretUpdateEnemyInterval)))
|
||
|
{
|
||
|
// Find new best enemy
|
||
|
this->m_hEnemy = this->FindBestEnemy();
|
||
|
this->mTimeOfLastUpdateEnemy = theCurrentTime;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Clear current enemy
|
||
|
this->m_hEnemy = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AvHTurret::ActiveThink(void)
|
||
|
{
|
||
|
PROFILE_START()
|
||
|
|
||
|
// Advance model frame
|
||
|
StudioFrameAdvance();
|
||
|
|
||
|
// Find enemy, or reacquire dead enemy
|
||
|
this->UpdateEnemy();
|
||
|
|
||
|
// If we have a valid enemy
|
||
|
if(!FNullEnt(this->m_hEnemy))
|
||
|
{
|
||
|
// If enemy is in FOV
|
||
|
Vector theVecMid = this->pev->origin + this->pev->view_ofs;
|
||
|
//AvHSUPlayParticleEvent("JetpackEffect", this->edict(), theVecMid);
|
||
|
|
||
|
CBaseEntity* theEnemyEntity = this->m_hEnemy;
|
||
|
Vector theVecMidEnemy = theEnemyEntity->BodyTarget(theVecMid);
|
||
|
|
||
|
//AvHSUPlayParticleEvent("JetpackEffect", theEnemyEntity->edict(), theVecMidEnemy);
|
||
|
|
||
|
// calculate dir and dist to enemy
|
||
|
Vector theVecDirToEnemy = theVecMidEnemy - theVecMid;
|
||
|
Vector theAddition = theVecMid + theVecDirToEnemy;
|
||
|
|
||
|
Vector theVecLOS = theVecDirToEnemy.Normalize();
|
||
|
|
||
|
// Update our goal angles to direction to enemy
|
||
|
Vector theVecDirToEnemyAngles;
|
||
|
VectorAngles(theVecDirToEnemy, theVecDirToEnemyAngles);
|
||
|
|
||
|
// Set goal quaternion
|
||
|
this->mGoalQuat = Quat(theVecDirToEnemyAngles);
|
||
|
|
||
|
// Is the turret looking at the target yet?
|
||
|
float theRadians = (this->GetVerticalFOV()/180.0f)*3.1415f;
|
||
|
float theCosVerticalFOV = cos(theRadians);
|
||
|
|
||
|
Vector theCurrentAngles;
|
||
|
this->mCurQuat.GetAngles(theCurrentAngles);
|
||
|
UTIL_MakeAimVectors(theCurrentAngles);
|
||
|
|
||
|
if(DotProduct(theVecLOS, gpGlobals->v_forward) > theCosVerticalFOV)
|
||
|
{
|
||
|
// If enemy is visible
|
||
|
bool theEnemyVisible = FBoxVisible(this->pev, this->m_hEnemy->pev, theVecMidEnemy) || !this->GetRequiresLOS();
|
||
|
if(theEnemyVisible && this->m_hEnemy->IsAlive())
|
||
|
{
|
||
|
// If it's time to attack
|
||
|
if((this->mTimeOfNextAttack == -1) || (gpGlobals->time >= this->mTimeOfNextAttack))
|
||
|
{
|
||
|
// Shoot and play shoot animation
|
||
|
Shoot(theVecMid, theVecDirToEnemy, theEnemyEntity->pev->velocity);
|
||
|
|
||
|
this->PlayAnimationAtIndex(this->GetActiveAnimation());
|
||
|
|
||
|
// Set time for next attack
|
||
|
this->SetNextAttack();
|
||
|
}
|
||
|
// spin the barrel when acquired but not firing
|
||
|
else if(this->GetBaseClassAnimatesTurret())
|
||
|
{
|
||
|
this->pev->sequence = 2;
|
||
|
ResetSequenceInfo();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set next active think
|
||
|
this->pev->nextthink = gpGlobals->time + kTurretThinkInterval;
|
||
|
}
|
||
|
// else we have no enemy, go back to search think
|
||
|
else
|
||
|
{
|
||
|
SetThink(&AvHTurret::SearchThink);
|
||
|
this->pev->nextthink = gpGlobals->time + kTurretThinkInterval;
|
||
|
}
|
||
|
|
||
|
this->TurretUpdate();
|
||
|
|
||
|
PROFILE_END(kAvHTurretActiveThink)
|
||
|
}
|
||
|
|
||
|
bool AvHTurret::GetBaseClassAnimatesTurret() const
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void AvHTurret::SearchThink(void)
|
||
|
{
|
||
|
PROFILE_START()
|
||
|
|
||
|
if(this->GetBaseClassAnimatesTurret())
|
||
|
{
|
||
|
this->pev->sequence = 2;
|
||
|
ResetSequenceInfo();
|
||
|
StudioFrameAdvance();
|
||
|
}
|
||
|
this->pev->nextthink = gpGlobals->time + kTurretThinkInterval;
|
||
|
|
||
|
this->Ping();
|
||
|
|
||
|
// If we have a target and we're still healthy
|
||
|
if(this->m_hEnemy != NULL)
|
||
|
{
|
||
|
if(!this->m_hEnemy->IsAlive() )
|
||
|
{
|
||
|
this->m_hEnemy = NULL;// Dead enemy forces a search for new one
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Acquire Target
|
||
|
this->UpdateEnemy();
|
||
|
|
||
|
// If we've found a target, spin up the barrel and start to attack
|
||
|
if(this->m_hEnemy != NULL)
|
||
|
{
|
||
|
//this->m_flSpinUpTime = 0;
|
||
|
SetThink(&AvHTurret::ActiveThink);
|
||
|
}
|
||
|
// else
|
||
|
// {
|
||
|
// // generic hunt for new victims
|
||
|
// this->m_vecGoalAngles.y = (this->m_vecGoalAngles.y + 0.1*this->m_fTurnRate);
|
||
|
// if(this->m_vecGoalAngles.y >= 360)
|
||
|
// {
|
||
|
// m_vecGoalAngles.y -= 360;
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
this->TurretUpdate();
|
||
|
|
||
|
PROFILE_END(kTurretSearchThink)
|
||
|
}
|
||
|
|
||
|
void AvHTurret::TurretUpdate()
|
||
|
{
|
||
|
this->MoveTurret();
|
||
|
}
|
||
|
|
||
|
void AvHTurret::SetNextAttack()
|
||
|
{
|
||
|
this->mTimeOfNextAttack = gpGlobals->time + this->GetRateOfFire();
|
||
|
}
|
||
|
|
||
|
int AvHTurret::MoveTurret(void)
|
||
|
{
|
||
|
ASSERT(this->m_fTurnRate > 0);
|
||
|
|
||
|
// We have an enemy, track towards goal angles
|
||
|
if(this->m_hEnemy != NULL)
|
||
|
{
|
||
|
float theRate = this->m_fTurnRate*kTurretThinkInterval;
|
||
|
this->mCurQuat = ConstantRateLerp(this->mCurQuat, this->mGoalQuat, theRate);
|
||
|
}
|
||
|
// generic hunt for new victims
|
||
|
else
|
||
|
{
|
||
|
// Create transformation quat that will rotate current quat
|
||
|
float axis[3] = { 0.0f, 0.0f, 1.0f};
|
||
|
Quat rot(kTurretSearchScalar, axis);
|
||
|
|
||
|
this->mCurQuat = rot*this->mCurQuat;
|
||
|
|
||
|
// Reset height
|
||
|
}
|
||
|
|
||
|
Vector theAngles;
|
||
|
this->mCurQuat.GetAngles(theAngles);
|
||
|
|
||
|
//SetBoneController(0, m_vecCurAngles.y - pev->angles.y );
|
||
|
SetBoneController(0, theAngles.y - pev->angles.y );
|
||
|
SetBoneController(1, -theAngles.x);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void AvHTurret::CheckEnabledState()
|
||
|
{
|
||
|
this->SetEnabledState(true, true);
|
||
|
}
|
||
|
|
||
|
void AvHTurret::SetHasBeenBuilt()
|
||
|
{
|
||
|
AvHBuildable::SetHasBeenBuilt();
|
||
|
|
||
|
this->CheckEnabledState();
|
||
|
}
|
||
|
|
||
|
void AvHTurret::SetEnabledState(bool inState, bool inForce)
|
||
|
{
|
||
|
if(!GetHasUpgrade(this->pev->iuser4, MASK_RECYCLING))
|
||
|
{
|
||
|
if((inState != this->mEnabled) || inForce)
|
||
|
{
|
||
|
this->mEnabled = inState;
|
||
|
|
||
|
if(this->GetBaseClassAnimatesTurret())
|
||
|
{
|
||
|
int theEnabledAnimation = this->GetSetEnabledAnimation();
|
||
|
if(theEnabledAnimation >= 0)
|
||
|
{
|
||
|
float theSpeed = this->mEnabled ? 1.0f : -1.0f;
|
||
|
this->PlayAnimationAtIndex(theEnabledAnimation, true, theSpeed);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!this->mEnabled)
|
||
|
{
|
||
|
this->m_hEnemy = NULL;
|
||
|
SetThink(NULL);
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float theTimeToAnimate = max(gpGlobals->time + kTurretThinkInterval, this->GetTimeAnimationDone());
|
||
|
SetThink(&AvHTurret::SearchThink);
|
||
|
this->pev->nextthink = theTimeToAnimate;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float AvHTurret::GetRateOfFire() const
|
||
|
{
|
||
|
float theVariance = RANDOM_FLOAT(0, 0.2);
|
||
|
float theBaseROF = BALANCE_VAR(kTurretBaseRateOfFire);
|
||
|
return theBaseROF + theVariance;
|
||
|
}
|
||
|
|
||
|
void AvHTurret::Setup()
|
||
|
{
|
||
|
this->pev->nextthink = gpGlobals->time + 1;
|
||
|
this->pev->frame = 0;
|
||
|
this->pev->takedamage = DAMAGE_AIM;
|
||
|
this->mNextPingTime = 0;
|
||
|
this->m_flFieldOfView = VIEW_FIELD_FULL;
|
||
|
this->m_fTurnRate = BALANCE_VAR(kTurretTrackingRate);
|
||
|
|
||
|
// This is the visual difference between model origin and gun barrel, it's needed to orient the barrel and hit targets properly
|
||
|
this->pev->view_ofs.z = 48;
|
||
|
|
||
|
SetBits(this->pev->flags, FL_MONSTER);
|
||
|
|
||
|
this->SetBoneController(0, 0);
|
||
|
this->SetBoneController(1, 0);
|
||
|
|
||
|
this->mEnabled = false;
|
||
|
}
|
||
|
|
||
|
void AvHTurret::Spawn()
|
||
|
{
|
||
|
AvHBaseBuildable::Spawn();
|
||
|
|
||
|
this->Setup();
|
||
|
}
|
||
|
|