This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/server/ents/basetank.cpp

1154 lines
31 KiB
C++

//=======================================================================
// Copyright (C) XashXT Group 2006
//=======================================================================
#include "extdll.h"
#include "utils.h"
#include "sfx.h"
#include "cbase.h"
#include "basebeams.h"
#include "baseweapon.h"
#include "basetank.h"
#include "monsters.h"
TYPEDESCRIPTION CFuncTank::m_SaveData[] =
{
DEFINE_FIELD( CFuncTank, m_yawCenter, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_yawRate, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_yawRange, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_yawTolerance, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_pitchCenter, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_pitchRate, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_pitchRange, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_pitchTolerance, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_fireLast, FIELD_TIME ),
DEFINE_FIELD( CFuncTank, m_fireRate, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_lastSightTime, FIELD_TIME ),
DEFINE_FIELD( CFuncTank, m_persist, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_Range, FIELD_RANGE ),
DEFINE_FIELD( CFuncTank, m_barrelPos, FIELD_VECTOR ),
DEFINE_FIELD( CFuncTank, m_spriteScale, FIELD_FLOAT ),
DEFINE_FIELD( CFuncTank, m_iszSpriteSmoke, FIELD_STRING ),
DEFINE_FIELD( CFuncTank, m_iszSpriteFlash, FIELD_STRING ),
DEFINE_FIELD( CFuncTank, m_bulletType, FIELD_INTEGER ),
DEFINE_FIELD( CFuncTank, m_sightOrigin, FIELD_VECTOR ),
DEFINE_FIELD( CFuncTank, m_spread, FIELD_INTEGER ),
DEFINE_FIELD( CFuncTank, m_pControls, FIELD_CLASSPTR ), //LRC
DEFINE_FIELD( CFuncTank, m_pSpot, FIELD_CLASSPTR ), //LRC
DEFINE_FIELD( CFuncTank, m_flNextAttack, FIELD_TIME ),
DEFINE_FIELD( CFuncTank, m_iBulletDamage, FIELD_INTEGER ),
DEFINE_FIELD( CFuncTank, m_iszMaster, FIELD_STRING ),
DEFINE_FIELD( CFuncTank, m_iszFireMaster, FIELD_STRING ), //LRC
};
IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity );
void CFuncTank :: KeyValue( KeyValueData *pkvd )
{
if (FStrEq(pkvd->szKeyName, "yawrate"))
{
m_yawRate = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "yawrange"))
{
m_yawRange = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "yawtolerance"))
{
m_yawTolerance = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "pitchrange"))
{
m_pitchRange = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "pitchrate"))
{
m_pitchRate = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "pitchtolerance"))
{
m_pitchTolerance = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "firerate"))
{
m_fireRate = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "barrelend"))
{
pev->netname = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "spritescale"))
{
m_spriteScale = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "spritesmoke"))
{
m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "spriteflash"))
{
m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "rotatesound"))
{
pev->noise = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "persistence"))
{
m_persist = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "bullet"))
{
m_bulletType = (TANKBULLET)atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if ( FStrEq(pkvd->szKeyName, "bullet_damage" ))
{
m_iBulletDamage = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "firespread"))
{
m_spread = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "Range"))
{
m_Range = RandomRange(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "master"))
{
m_iszMaster = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "firemaster"))
{
m_iszFireMaster = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_iClass"))
{
m_iTankClass = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else CBaseEntity::KeyValue( pkvd );
}
void CFuncTank :: Spawn( void )
{
Precache();
pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything
pev->solid = SOLID_BSP;
UTIL_SetModel( ENT(pev), pev->model );
SetBits (pFlags, PF_ANGULAR);
if ( IsActive() )
{
SetNextThink(1.0);
}
if (!m_iTankClass)
{
m_iTankClass = 0;
}
if(m_Range.m_flMax == 0) m_Range.m_flMax = 4096;
if ( m_fireRate <= 0 ) m_fireRate = 1;
if ( m_spread > MAX_FIRING_SPREADS ) m_spread = 0;
pev->oldorigin = pev->origin;
}
void CFuncTank::PostSpawn( void )
{
if (m_pParent)
{
m_yawCenter = pev->angles.y - m_pParent->pev->angles.y;
m_pitchCenter = pev->angles.x - m_pParent->pev->angles.x;
}
else
{
m_yawCenter = pev->angles.y;
m_pitchCenter = pev->angles.x;
}
CBaseEntity *pTarget = UTIL_FindEntityByTargetname(NULL, STRING(pev->netname), NULL );
if(pTarget)
{
m_barrelPos = pTarget->pev->origin - pev->origin; //write offset
UTIL_Remove( pTarget );
}
else if(!IsLaserTank()) //get "laserentity" position for tanklaser
{
Msg("Error! %s: %s haven't barrel end entity! Use default values.\n", STRING(pev->classname), STRING(pev->targetname));
m_barrelPos = Vector( fabs(pev->absmax.x), 0, 0 );
}
m_sightOrigin = BarrelPosition(); // Point at the end of the barrel
}
void CFuncTank :: Precache( void )
{
if ( m_iszSpriteSmoke )
UTIL_PrecacheModel( m_iszSpriteSmoke );
if ( m_iszSpriteFlash )
UTIL_PrecacheModel( m_iszSpriteFlash );
if ( pev->noise )
UTIL_PrecacheSound( pev->noise );
}
BOOL CFuncTank :: StartControl( CBasePlayer* pController, CFuncTankControls *pControls )
{
// we're already being controlled or playing a sequence
if ( m_pControls != NULL )
return FALSE;
// Team only or disabled?
if ( m_iszMaster )
{
if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) )
return FALSE;
}
m_iState = STATE_ON;
m_pControls = pControls;
if (m_pSpot) m_pSpot->Revive();
SetNextThink(0.3);
return TRUE;
}
void CFuncTank :: StopControl( CFuncTankControls* pControls)
{
if ( !m_pControls || m_pControls != pControls)
{
return;
}
m_iState = STATE_OFF;
if (m_pSpot) m_pSpot->Suspend(-1);
StopRotSound(); //LRC
DontThink();
UTIL_SetAvelocity(this, g_vecZero);
m_pControls = NULL;
if ( IsActive() )
{
SetNextThink(1.0);
}
}
void CFuncTank::UpdateSpot( void )
{
if ( pev->spawnflags & SF_TANK_LASERSPOT )
{
if (!m_pSpot) m_pSpot = CLaserSpot::CreateSpot();
Vector vecAiming;
UTIL_MakeVectorsPrivate( pev->angles, vecAiming, NULL, NULL );
Vector vecSrc = BarrelPosition( );
TraceResult tr;
UTIL_TraceLine ( vecSrc, vecSrc + vecAiming * 8192, dont_ignore_monsters, ENT(pev), &tr );
UTIL_SetOrigin( m_pSpot, tr.vecEndPos );
}
}
void CFuncTank :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if ( pev->spawnflags & SF_TANK_CANCONTROL )
{
if ( pActivator->Classify() != CLASS_PLAYER ) return;
}
else
{
if (useType == USE_TOGGLE)
{
if(IsActive()) useType = USE_OFF;
else useType = USE_ON;
}
if (useType == USE_ON)
{
TankActivate();
if (m_pSpot) m_pSpot->Revive();
}
else if (useType == USE_OFF)
{
TankDeactivate();
if (m_pSpot) m_pSpot->Suspend(-1);
}
}
}
CBaseEntity *CFuncTank:: BestVisibleEnemy ( void )
{
CBaseEntity *pReturn;
int iNearest;
int iDist;
int iBestRelationship;
int iLookDist = m_Range.m_flMax ? m_Range.m_flMax : 512; //thanks to Waldo for this.
iNearest = 8192;// so first visible entity will become the closest.
pReturn = NULL;
iBestRelationship = R_DL;
CBaseEntity *pList[100];
Vector delta = Vector( iLookDist, iLookDist, iLookDist );
// Find only monsters/clients in box, NOT limited to PVS
int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER );
int i;
for (i = 0; i < count; i++ )
{
if ( pList[i]->IsAlive() )
{
if ( IRelationship( pList[i] ) > iBestRelationship )
{
// this entity is disliked MORE than the entity that we
// currently think is the best visible enemy. No need to do
// a distance check, just get mad at this one for now.
iBestRelationship = IRelationship ( pList[i] );
iNearest = ( pList[i]->pev->origin - pev->origin ).Length();
pReturn = pList[i];
}
else if ( IRelationship( pList[i] ) == iBestRelationship )
{
// this entity is disliked just as much as the entity that
// we currently think is the best visible enemy, so we only
// get mad at it if it is closer.
iDist = ( pList[i]->pev->origin - pev->origin ).Length();
if ( iDist <= iNearest )
{
iNearest = iDist;
//these are guaranteed to be the same! iBestRelationship = IRelationship ( pList[i] );
pReturn = pList[i];
}
}
}
}
return pReturn;
}
int CFuncTank::IRelationship( CBaseEntity* pTarget )
{
int iOtherClass = pTarget->Classify();
if (iOtherClass == CLASS_NONE) return R_NO;
if (!m_iTankClass)
{
if (iOtherClass == CLASS_PLAYER)
return R_HT;
else
return R_NO;
}
else if (m_iTankClass == CLASS_PLAYER_ALLY)
{
switch (iOtherClass)
{
case CLASS_HUMAN_MILITARY:
case CLASS_MACHINE:
case CLASS_ALIEN_MILITARY:
case CLASS_ALIEN_MONSTER:
case CLASS_ALIEN_PREDATOR:
case CLASS_ALIEN_PREY:
return R_HT;
default:
return R_NO;
}
}
else if (m_iTankClass == CLASS_HUMAN_MILITARY)
{
switch (iOtherClass)
{
case CLASS_PLAYER:
case CLASS_PLAYER_ALLY:
case CLASS_ALIEN_MILITARY:
case CLASS_ALIEN_MONSTER:
case CLASS_ALIEN_PREDATOR:
case CLASS_ALIEN_PREY:
return R_HT;
case CLASS_HUMAN_PASSIVE:
return R_DL;
default:
return R_NO;
}
}
else if (m_iTankClass == CLASS_ALIEN_MILITARY)
{
switch (iOtherClass)
{
case CLASS_PLAYER:
case CLASS_PLAYER_ALLY:
case CLASS_HUMAN_MILITARY:
return R_HT;
case CLASS_HUMAN_PASSIVE:
return R_DL;
default:
return R_NO;
}
}
else return R_NO;
}
BOOL CFuncTank :: InRange( float range )
{
if ( range < m_Range.m_flMin )
return FALSE;
if ( m_Range.m_flMax > 0 && range > m_Range.m_flMax )
return FALSE;
return TRUE;
}
void CFuncTank :: Think( void )
{
TrackTarget();
if ( fabs(pev->avelocity.x) > 1 || fabs(pev->avelocity.y) > 1 )
StartRotSound();
else StopRotSound();
}
void CFuncTank::TrackTarget( void )
{
TraceResult tr;
BOOL updateTime = FALSE, lineOfSight;
Vector angles, direction, targetPosition, barrelEnd;
Vector v_right, v_up;
CBaseEntity *pTarget;
CBasePlayer* pController = NULL;
if (m_pControls && m_pControls->m_pController)
{
UpdateSpot();
pController = m_pControls->m_pController;
pController->pev->viewmodel = 0;
SetNextThink(0.05);
if( pev->spawnflags & SF_TANK_MATCHTARGET )
{
// "Match target" mode:
// first, get the player's angles
angles = pController->pev->v_angle;
// work out what point the player is looking at
UTIL_MakeVectorsPrivate( angles, direction, NULL, NULL );
targetPosition = pController->EyePosition() + direction * 1000;
edict_t *ownerTemp = pev->owner; //LRC store the owner, so we can put it back after the check
pev->owner = pController->edict(); //LRC when doing the matchtarget check, don't hit the player or the tank.
UTIL_TraceLine(
pController->EyePosition(),
targetPosition,
missile, //the opposite of ignore_monsters: target them if we go anywhere near!
ignore_glass,
edict(), &tr
);
pev->owner = ownerTemp; //LRC put the owner back
// Work out what angle we need to face to look at that point
direction = tr.vecEndPos - pev->origin;
angles = UTIL_VecToAngles( direction );
targetPosition = tr.vecEndPos;
// Calculate the additional rotation to point the end of the barrel at the target
// (instead of the gun's center)
AdjustAnglesForBarrel( angles, direction.Length() );
}
else
{
// "Match angles" mode
// just get the player's angles
angles = pController->pev->v_angle;
angles[0] = 0 - angles[0];
UpdateSpot();
SetNextThink( 0.05 ); // g-cont. for more smoothing motion a laser spot
}
}
else
{
if ( IsActive() )
{
SetNextThink(0.1);
}
else
{
DontThink();
UTIL_SetAvelocity(this, g_vecZero);
return;
}
UpdateSpot();
// if we can't see any players
pTarget = BestVisibleEnemy();
if ( FNullEnt( pTarget ) )
{
if ( IsActive() ) SetNextThink(2); // No enemies visible, wait 2 secs
return;
}
// Calculate angle needed to aim at target
barrelEnd = BarrelPosition();
targetPosition = pTarget->pev->origin + pTarget->pev->view_ofs;
float range = (targetPosition - barrelEnd).Length();
if ( !InRange( range ) )
return;
UTIL_TraceLine( barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr );
if ( tr.flFraction == 1.0 || tr.pHit == ENT(pTarget->pev) )
{
lineOfSight = TRUE;
if ( InRange( range ) && pTarget->IsAlive() )
{
updateTime = TRUE; // I think I saw him, pa!
m_sightOrigin = UpdateTargetPosition( pTarget );
}
}
else
{
// No line of sight, don't track
lineOfSight = FALSE;
}
direction = m_sightOrigin - pev->origin;
angles = UTIL_VecToAngles( direction );
AdjustAnglesForBarrel( angles, direction.Length() );
}
angles.x = -angles.x;
float currentYawCenter, currentPitchCenter;
// Force the angles to be relative to the center position
if (m_pParent)
{
currentYawCenter = m_yawCenter + m_pParent->pev->angles.y;
currentPitchCenter = m_pitchCenter + m_pParent->pev->angles.x;
}
else
{
currentYawCenter = m_yawCenter;
currentPitchCenter = m_pitchCenter;
}
angles.y = currentYawCenter + UTIL_AngleDistance( angles.y, currentYawCenter );
angles.x = currentPitchCenter + UTIL_AngleDistance( angles.x, currentPitchCenter );
// Limit against range in y
if (m_yawRange < 360)
{
if ( angles.y > currentYawCenter + m_yawRange )
{
angles.y = currentYawCenter + m_yawRange;
updateTime = FALSE; // If player is outside fire arc, we didn't really see him
}
else if ( angles.y < (currentYawCenter - m_yawRange) )
{
angles.y = (currentYawCenter - m_yawRange);
updateTime = FALSE; // If player is outside fire arc, we didn't really see him
}
}
// we can always 'see' the whole vertical arc, so it's just the yaw we needed to check.
if ( updateTime )
m_lastSightTime = gpGlobals->time;
// Move toward target at rate or less
float distY = UTIL_AngleDistance( angles.y, pev->angles.y );
Vector setAVel = g_vecZero;
setAVel.y = distY * 10;
if ( setAVel.y > m_yawRate ) setAVel.y = m_yawRate;
else if ( setAVel.y < -m_yawRate ) setAVel.y = -m_yawRate;
// Limit against range in x
if ( angles.x > currentPitchCenter + m_pitchRange ) angles.x = currentPitchCenter + m_pitchRange;
else if ( angles.x < currentPitchCenter - m_pitchRange ) angles.x = currentPitchCenter - m_pitchRange;
// Move toward target at rate or less
float distX = UTIL_AngleDistance( angles.x, pev->angles.x );
setAVel.x = distX * 10;
if ( setAVel.x > m_pitchRate ) setAVel.x = m_pitchRate;
else if ( setAVel.x < -m_pitchRate ) setAVel.x = -m_pitchRate;
UTIL_SetAvelocity(this, setAVel);
// notify the TankSequence if we're (pretty close to) facing the target
// firing with player-controlled tanks:
if ( pController )
{
if ( gpGlobals->time < m_flNextAttack )
return;
if ( pController->pev->button & IN_ATTACK )
{
Vector forward;
UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL );
// to make sure the gun doesn't fire too many bullets
m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01;
TryFire( BarrelPosition(), forward, pController->pev );
if ( pController && pController->IsPlayer() )
((CBasePlayer *)pController)->m_iWeaponVolume = LOUD_GUN_VOLUME;
m_flNextAttack = gpGlobals->time + (1/m_fireRate);
}
else m_iState = STATE_ON;
}
// firing with automatic guns:
else if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT) ) )
{
BOOL fire = FALSE;
Vector forward;
UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL );
if ( pev->spawnflags & SF_TANK_LINEOFSIGHT )
{
float length = direction.Length();
UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr );
if ( tr.pHit == ENT(pTarget->pev) )
fire = TRUE;
}
else fire = TRUE;
if ( fire )
{
TryFire( BarrelPosition(), forward, pev );
}
else
{
m_fireLast = 0;
}
}
else m_fireLast = 0;
}
void CFuncTank::AdjustAnglesForBarrel( Vector &angles, float distance )
{
float r2, d2;
if ( m_barrelPos.y != 0 || m_barrelPos.z != 0 )
{
distance -= m_barrelPos.z;
d2 = distance * distance;
if ( m_barrelPos.y )
{
r2 = m_barrelPos.y * m_barrelPos.y;
angles.y += (180.0 / M_PI) * atan2( m_barrelPos.y, sqrt( d2 - r2 ) );
}
if ( m_barrelPos.z )
{
r2 = m_barrelPos.z * m_barrelPos.z;
angles.x += (180.0 / M_PI) * atan2( -m_barrelPos.z, sqrt( d2 - r2 ) );
}
}
}
// Check the FireMaster before actually firing
void CFuncTank::TryFire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker )
{
m_iState = STATE_IN_USE;
if (UTIL_IsMasterTriggered(m_iszFireMaster, NULL))
{
Fire( barrelEnd, forward, pevAttacker );
}
}
// Fire targets and spawn sprites
void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker )
{
if ( m_fireLast != 0 )
{
if ( m_iszSpriteSmoke )
{
CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE );
pSprite->AnimateAndDie( RANDOM_FLOAT( 15.0, 20.0 ) );
pSprite->SetTransparency( kRenderTransAlpha, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, 255, kRenderFxNone );
pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80);
pSprite->SetScale( m_spriteScale );
}
if ( m_iszSpriteFlash )
{
CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE );
pSprite->AnimateAndDie( 60 );
pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
pSprite->SetScale( m_spriteScale );
}
UTIL_FireTargets( pev->target, this, this, USE_TOGGLE );
}
m_fireLast = gpGlobals->time;
}
void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr )
{
// get circular gaussian spread
float x, y, z;
do {
x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5);
y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5);
z = x*x+y*y;
} while (z > 1);
Vector vecDir = vecForward +
x * vecSpread.x * gpGlobals->v_right +
y * vecSpread.y * gpGlobals->v_up;
Vector vecEnd;
vecEnd = vecStart + vecDir * 4096;
UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &tr );
}
void CFuncTank::StartRotSound( void )
{
if ( !pev->noise || (pev->spawnflags & SF_TANK_SOUNDON) )
return;
pev->spawnflags |= SF_TANK_SOUNDON;
EMIT_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise), 0.85, ATTN_NORM);
}
void CFuncTank::StopRotSound( void )
{
if ( pev->spawnflags & SF_TANK_SOUNDON )
STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise) );
pev->spawnflags &= ~SF_TANK_SOUNDON;
}
//============================================================================
// FUNC TANK
//============================================================================
class CFuncTankGun : public CFuncTank
{
public:
void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker );
};
LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun );
void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker )
{
int i;
if ( m_fireLast != 0 )
{
// FireBullets needs gpGlobals->v_up, etc.
UTIL_MakeAimVectors(pev->angles);
int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate;
if ( bulletCount > 0 )
{
for ( i = 0; i < bulletCount; i++ )
{
switch( m_bulletType )
{
case TANK_BULLET_9MM:
FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_9MM, 1, m_iBulletDamage, pevAttacker );
break;
case TANK_BULLET_MP5:
FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MP5, 1, m_iBulletDamage, pevAttacker );
break;
case TANK_BULLET_12MM:
FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_12MM, 1, m_iBulletDamage, pevAttacker );
break;
default:
case TANK_BULLET_NONE:
break;
}
}
CFuncTank::Fire( barrelEnd, forward, pevAttacker );
}
}
else CFuncTank::Fire( barrelEnd, forward, pevAttacker );
}
//============================================================================
// FUNC TANK LASER
//============================================================================
class CFuncTankLaser : public CFuncTank
{
public:
void Activate( void );
void KeyValue( KeyValueData *pkvd );
void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker );
BOOL IsLaserTank( void ) { return TRUE; }
void Think( void );
void PostActivate( void );
void PostSpawn( void );
CLaser *GetLaser( void );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
private:
CLaser *m_pLaser;
float m_laserTime;
};
LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser );
TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] =
{
DEFINE_FIELD( CFuncTankLaser, m_pLaser, FIELD_CLASSPTR ),
DEFINE_FIELD( CFuncTankLaser, m_laserTime, FIELD_TIME ),
};
IMPLEMENT_SAVERESTORE( CFuncTankLaser, CFuncTank );
void CFuncTankLaser::Activate( void )
{
if ( !GetLaser() )
{
UTIL_Remove(this);
Msg( "Error: Laser tank with no env_laser!\n" );
}
else
{
m_pLaser->TurnOff();
}
CFuncTank::Activate();
}
void CFuncTankLaser::PostSpawn( void )
{
CFuncTank::PostSpawn();
if(m_barrelPos == g_vecZero) //check for custom barrelend
{
if(GetLaser()) m_barrelPos = m_pLaser->pev->origin - pev->origin; //write offset
else m_barrelPos = Vector( fabs(pev->absmax.x), 0, 0 ); //write default
m_sightOrigin = BarrelPosition(); // Point at the end of the barrel
}
}
void CFuncTankLaser::PostActivate( void )
{
//set laser parent once only
if(m_pLaser && m_pLaser->m_pParent != this)
{
m_pLaser->SetParent( this );
}
}
void CFuncTankLaser::KeyValue( KeyValueData *pkvd )
{
if (FStrEq(pkvd->szKeyName, "laserentity"))
{
pev->message = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else CFuncTank::KeyValue( pkvd );
}
CLaser *CFuncTankLaser::GetLaser( void )
{
if ( m_pLaser ) return m_pLaser;
CBaseEntity *pEntity;
pEntity = UTIL_FindEntityByTargetname( NULL, STRING(pev->message) );
while ( pEntity )
{
// Found the laser
if ( FClassnameIs( pEntity->pev, "env_laser" ) )
{
m_pLaser = (CLaser *)pEntity;
m_pLaser->pev->angles = pev->angles; //copy tank angles
break;
}
else pEntity = UTIL_FindEntityByTargetname( pEntity, STRING(pev->message) );
}
return m_pLaser;
}
void CFuncTankLaser::Think( void )
{
if ( m_pLaser && m_pLaser->GetState() == STATE_ON && (gpGlobals->time > m_laserTime))
m_pLaser->TurnOff();
CFuncTank::Think();
}
void CFuncTankLaser::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker )
{
int i;
TraceResult tr;
if ( m_fireLast != 0 && GetLaser() )
{
// TankTrace needs gpGlobals->v_up, etc.
UTIL_MakeAimVectors(pev->angles);
int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate;
if ( bulletCount )
{
for ( i = 0; i < bulletCount; i++ )
{
m_pLaser->pev->origin = barrelEnd;
TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr );
m_laserTime = gpGlobals->time + 0.1;
m_pLaser->TurnOn();
m_pLaser->pev->dmgtime = gpGlobals->time - 1.0;
}
CFuncTank::Fire( barrelEnd, forward, pev );
}
}
else CFuncTank::Fire( barrelEnd, forward, pev );
}
//============================================================================
// FUNC TANK ROCKET
//============================================================================
class CFuncTankRocket : public CFuncTank
{
public:
void Precache( void );
void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker );
BOOL IsRocketTank( void ) { return TRUE; }
};
LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket );
void CFuncTankRocket::Precache( void )
{
UTIL_PrecacheEntity( "rpg_rocket" );
CFuncTank::Precache();
}
void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker )
{
int i;
if ( m_fireLast != 0 )
{
int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate;
if ( bulletCount > 0 )
{
for ( i = 0; i < bulletCount; i++ )
{
CBaseEntity *pRocket = CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() );
}
CFuncTank::Fire( barrelEnd, forward, pev );
}
}
else CFuncTank::Fire( barrelEnd, forward, pev );
}
//============================================================================
// FUNC TANK MORTAR
//============================================================================
class CFuncTankMortar : public CFuncTank
{
public:
void KeyValue( KeyValueData *pkvd );
void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker );
};
LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar );
void CFuncTankMortar::KeyValue( KeyValueData *pkvd )
{
if (FStrEq(pkvd->szKeyName, "iMagnitude"))
{
pev->impulse = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
CFuncTank::KeyValue( pkvd );
}
void CFuncTankMortar::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker )
{
if ( m_fireLast != 0 )
{
int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate;
// Only create 1 explosion
if ( bulletCount > 0 )
{
TraceResult tr;
// TankTrace needs gpGlobals->v_up, etc.
UTIL_MakeAimVectors(pev->angles);
TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr );
UTIL_Explode( tr.vecEndPos, edict(), pev->impulse );
CFuncTank::Fire( barrelEnd, forward, pev );
}
}
else CFuncTank::Fire( barrelEnd, forward, pev );
}
//============================================================================
// FUNC TANK CONTROLS
//============================================================================
void CFuncTankControls::Spawn( void )
{
pev->solid = SOLID_TRIGGER;
pev->movetype = MOVETYPE_NONE;
pev->effects |= EF_NODRAW;
UTIL_SetModel( ENT(pev), pev->model );
UTIL_SetSize( pev, pev->mins, pev->maxs );
UTIL_SetOrigin( this, pev->origin );
}
void CFuncTankControls :: KeyValue( KeyValueData *pkvd )
{
if ( m_cTanks < MAX_MULTI_TARGETS )
{
// add this field to the target list
// this assumes that additional fields are targetnames and their values are delay values.
char tmp[128];
UTIL_StripToken( pkvd->szKeyName, tmp );
m_iTankName[ m_cTanks ] = ALLOC_STRING( tmp );
m_cTanks++;
pkvd->fHandled = TRUE;
}
}
TYPEDESCRIPTION CFuncTankControls::m_SaveData[] =
{
DEFINE_FIELD( CFuncTankControls, m_pController, FIELD_CLASSPTR ),
DEFINE_FIELD( CFuncTankControls, m_vecControllerUsePos, FIELD_VECTOR ),
DEFINE_FIELD( CFuncTankControls, m_cTanks, FIELD_INTEGER ),
DEFINE_ARRAY( CFuncTankControls, m_iTankName, FIELD_STRING, MAX_MULTI_TARGETS ),
};
IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity );
BOOL CFuncTankControls :: OnControls( entvars_t *pevTest )
{
Vector offset = pevTest->origin - pev->origin;
if (m_pParent)
{
if ( ((m_vecControllerUsePos + m_pParent->pev->origin) - pevTest->origin).Length() <= 30 )
return TRUE;
}
else if ( (m_vecControllerUsePos - pevTest->origin).Length() <= 30 ) return TRUE;
return FALSE;
}
void CFuncTankControls :: HandleTank ( CBaseEntity *pActivator, CBaseEntity *pTank, BOOL activate )
{
if( !strncmp( STRING( pTank->pev->classname ), "func_tank", 9 ))
{
if( activate )
{
if( (( CFuncTank *)pTank)->StartControl(( CBasePlayer *)pActivator, this ))
{
m_iState = STATE_ON; //we have active tank!
}
}
else (( CFuncTank *)pTank)->StopControl( this );
}
}
void CFuncTankControls :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
CBaseEntity *tryTank = NULL;
if ( useType == USE_SHOWINFO)
{
DEBUGHEAD;
return;
}
if ( !m_pController && useType != USE_OFF )
{
if (!pActivator || !(pActivator->IsPlayer())) return;
if (m_iState != STATE_OFF || ((CBasePlayer*)pActivator)->m_pTank != NULL) return;
//find all specified tanks
for(int i = 0; i < m_cTanks; i++)
{
//find all tanks with current name
while( tryTank = UTIL_FindEntityByTargetname(tryTank, STRING(m_iTankName[i])))
{
HandleTank( pActivator, tryTank, TRUE );
}
}
if (m_iState == STATE_ON)
{
// we found at least one tank to use, so holster player's weapon
m_pController = (CBasePlayer*)pActivator;
m_pController->m_pTank = this;
if ( m_pController->m_pActiveItem )
{
m_pController->m_pActiveItem->Holster();
m_pController->pev->weaponmodel = 0;
//viewmodel reset in tank
}
m_pController->m_iHideHUD |= HIDEHUD_WEAPONS;
if (m_pParent)
m_vecControllerUsePos = m_pController->pev->origin - m_pParent->pev->origin;
else m_vecControllerUsePos = m_pController->pev->origin;
}
}
else if (m_pController && useType != USE_ON)
{
//find all specified tanks
for(int i = 0; i < m_cTanks; i++)
{
//find all tanks with current name
while( tryTank = UTIL_FindEntityByTargetname(tryTank, STRING(m_iTankName[i])))
{
HandleTank( pActivator, tryTank, FALSE );
}
}
// bring back player's weapons
if ( m_pController->m_pActiveItem ) m_pController->m_pActiveItem->Deploy();
m_pController->m_iHideHUD &= ~HIDEHUD_WEAPONS;
m_pController->m_pTank = NULL;
m_pController = NULL;
m_iState = STATE_OFF;
}
}
LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls );