/*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #include "extdll.h" #include "util.h" #include "cbase.h" #include "effects.h" #include "weapons.h" #include "explode.h" #include "monsters.h" #include "movewith.h" #include "player.h" #define SF_TANK_ACTIVE 0x0001 //#define SF_TANK_PLAYER 0x0002 //#define SF_TANK_HUMANS 0x0004 //#define SF_TANK_ALIENS 0x0008 #define SF_TANK_LINEOFSIGHT 0x0010 #define SF_TANK_CANCONTROL 0x0020 #define SF_TANK_LASERSPOT 0x0040 //LRC #define SF_TANK_MATCHTARGET 0x0080 //LRC #define SF_TANK_SOUNDON 0x8000 #define SF_TANK_SEQFIRE 0x10000 //LRC - a TankSequence is telling me to fire enum TANKBULLET { TANK_BULLET_NONE = 0, TANK_BULLET_9MM = 1, TANK_BULLET_MP5 = 2, TANK_BULLET_12MM = 3, }; class CFuncTank; class CTankSequence; // declare Controls up here to stop the compiler complaining. (come back Java, all is forgiven...) class CFuncTankControls : public CBaseEntity { public: virtual int ObjectCaps( void ); void Spawn( void ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); // void Think( void ); void KeyValue( KeyValueData *pkvd ); STATE GetState(void) { return m_active?STATE_ON:STATE_OFF; } virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; BOOL OnControls( entvars_t *pevTest ); BOOL m_active; // am I being used to control tanks right now? Vector m_vecControllerUsePos; // where was the player standing when he used me? // for a 'movewith' controls entity, this is relative to the movewith ent. CBasePlayer* m_pController; int m_iCrosshair; //LRC - show a crosshair while in use. (currently this is just yes or no, // but in future it will be the id of the weapon whose crosshair should be used.) // CFuncTank *m_pTank; }; #define SF_TSEQ_DUMPPLAYER 1 #define SF_TSEQ_REPEATABLE 2 #define TSEQ_UNTIL_NONE 0 #define TSEQ_UNTIL_FACING 1 #define TSEQ_UNTIL_DEATH 2 #define TSEQ_TURN_NO 0 #define TSEQ_TURN_ANGLE 1 #define TSEQ_TURN_FACE 2 #define TSEQ_TURN_ENEMY 3 #define TSEQ_SHOOT_NO 0 #define TSEQ_SHOOT_ONCE 1 #define TSEQ_SHOOT_ALWAYS 2 #define TSEQ_SHOOT_FACING 3 #define TSEQ_FLAG_NOCHANGE 0 #define TSEQ_FLAG_ON 1 #define TSEQ_FLAG_OFF 2 #define TSEQ_FLAG_TOGGLE 3 class CTankSequence : public CBaseEntity { public: void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void EndThink( void ); void TimeOutThink( void ); void KeyValue( KeyValueData *pkvd ); STATE GetState( void ) { return m_pTank?STATE_ON:STATE_OFF; } virtual int ObjectCaps( void ); void StopSequence( void ); void FacingNotify( void ); void DeadEnemyNotify( void ); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; string_t m_iszEntity; string_t m_iszEnemy; int m_iUntil; float m_fDuration; int m_iTurn; int m_iShoot; int m_iActive; int m_iControllable; int m_iLaserSpot; CFuncTank *m_pTank; // the sequence can only control one tank at a time, for the moment }; // Custom damage // env_laser (duration is 0.5 rate of fire) // rockets // explosion? class CFuncTank : public CBaseEntity { public: void Spawn( void ); void PostSpawn( void ); void Precache( void ); void KeyValue( KeyValueData *pkvd ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void Think( void ); void TrackTarget( void ); CBaseEntity* BestVisibleEnemy( void ); int IRelationship( CBaseEntity* pTarget ); int Classify( void ) { return m_iTankClass; } void TryFire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); virtual Vector UpdateTargetPosition( CBaseEntity *pTarget ) { return pTarget->BodyTarget( pev->origin ); } void StartRotSound( void ); void StopRotSound( void ); STATE GetState( void ) { return m_iActive?STATE_ON:STATE_OFF; }//Support this stuff for watcher int m_iActive; // Bmodels don't go across transitions virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } inline BOOL IsActive( void ) { return (pev->spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } inline void TankActivate( void ) { pev->spawnflags |= SF_TANK_ACTIVE; SetNextThink(0.1); m_fireLast = 0; } inline void TankDeactivate( void ) { pev->spawnflags &= ~SF_TANK_ACTIVE; m_fireLast = 0; StopRotSound(); } inline BOOL CanFire( void ) { return (gpGlobals->time - m_lastSightTime) < m_persist; } BOOL InRange( float range ); // Acquire a target. pPlayer is a player in the PVS edict_t *FindTarget( edict_t *pPlayer ); void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ); Vector BarrelPosition( void ) { Vector forward, right, up; UTIL_MakeVectorsPrivate( pev->angles, forward, right, up ); return pev->origin + (forward * m_barrelPos.x) + (right * m_barrelPos.y) + (up * m_barrelPos.z); } void AdjustAnglesForBarrel( Vector &angles, float distance ); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; // BOOL OnControls( entvars_t *pevTest ); BOOL StartControl( CBasePlayer* pController, CFuncTankControls* pControls ); void StopControl( CFuncTankControls* pControls ); // void ControllerPostFrame( void ); CFuncTankControls* m_pControls; //LRC - tankcontrols is used as a go-between. void StartSequence( CTankSequence *pSequence); void StopSequence(); CTankSequence *m_pSequence; //LRC - if set, then this is the sequence the tank is currently performing CBaseEntity *m_pSequenceEnemy; //LRC - the entity that our sequence wants us to attack CLaserSpot* m_pSpot; // Laser spot entity //LRC - unprotected these, so that TankSequence can look at them float m_maxRange; // Max range to aim/track float m_fireLast; // Last time I fired float m_fireRate; // How many rounds/second protected: // CBasePlayer* m_pController; float m_flNextAttack; //LRC Vector m_vecControllerUsePos; float m_yawCenter; // "Center" yaw float m_yawRate; // Max turn rate to track targets float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) // Zero is full rotation float m_yawTolerance; // Tolerance angle float m_pitchCenter; // "Center" pitch float m_pitchRate; // Max turn rate on pitch float m_pitchRange; // Range of pitch motion as above float m_pitchTolerance; // Tolerance angle float m_lastSightTime;// Last time I saw target float m_persist; // Persistence of firing (how long do I shoot when I can't see) float m_minRange; // Minimum range to aim/track Vector m_barrelPos; // Length of the freakin barrel float m_spriteScale; // Scale of any sprites we shoot string_t m_iszSpriteSmoke; string_t m_iszSpriteFlash; TANKBULLET m_bulletType; // Bullet type int m_iBulletDamage; // 0 means use Bullet type's default damage Vector m_sightOrigin; // Last sight of target int m_spread; // firing spread string_t m_iszMaster; // Master entity int m_iszFireMaster;//LRC - Fire-Master entity (prevents firing when inactive) int m_iTankClass; // Behave As void UpdateSpot( void ); // CLaserSpot* m_pViewTarg; // Player view indicator CPointEntity *m_pFireProxy; //LRC - locus position for custom shots int m_iszLocusFire; }; 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_minRange, FIELD_FLOAT ), DEFINE_FIELD( CFuncTank, m_maxRange, FIELD_FLOAT ), 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_pSequence, FIELD_CLASSPTR ), //LRC DEFINE_FIELD( CFuncTank, m_pSequenceEnemy, FIELD_CLASSPTR ), //LRC DEFINE_FIELD( CFuncTank, m_pSpot, FIELD_CLASSPTR ), //LRC //LRC DEFINE_FIELD( CFuncTank, m_pController, FIELD_CLASSPTR ), //LRC DEFINE_FIELD( CFuncTank, m_vecControllerUsePos, FIELD_VECTOR ), 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 DEFINE_FIELD( CFuncTank, m_iszLocusFire, FIELD_STRING ), //LRC DEFINE_FIELD( CFuncTank, m_pFireProxy, FIELD_CLASSPTR ), //LRC DEFINE_FIELD( CFuncTank, m_iActive, FIELD_INTEGER ),//G-Cont. }; IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity ) static Vector gTankSpread[] = { Vector( 0, 0, 0 ), // perfect Vector( 0.025, 0.025, 0.025 ), // small cone Vector( 0.05, 0.05, 0.05 ), // medium cone Vector( 0.1, 0.1, 0.1 ), // large cone Vector( 0.25, 0.25, 0.25 ), // extra-large cone }; #define MAX_FIRING_SPREADS ARRAYSIZE( gTankSpread ) void CFuncTank::Spawn( void ) { Precache(); pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything pev->solid = SOLID_BSP; SET_MODEL( ENT( pev ), STRING( pev->model ) ); m_yawCenter = pev->angles.y; m_pitchCenter = pev->angles.x; if( IsActive() ) { SetNextThink(1.0); } if( !m_iTankClass ) { m_iTankClass = 0; } if( ( m_maxRange == 0 ) || ( FStringNull( m_maxRange ) ) ) { m_maxRange = 4096; //G-Cont. for normal working func_tank in original HL } m_sightOrigin = BarrelPosition(); // Point at the end of the barrel if( m_fireRate <= 0 ) m_fireRate = 1; if( m_spread > (int)MAX_FIRING_SPREADS ) m_spread = 0; pev->oldorigin = pev->origin; if (m_iszLocusFire) //LRC - locus trigger { m_pFireProxy = GetClassPtr( (CPointEntity*)NULL ); } } void CFuncTank::PostSpawn( void ) { if (m_pMoveWith) { m_yawCenter = pev->angles.y - m_pMoveWith->pev->angles.y; m_pitchCenter = pev->angles.x - m_pMoveWith->pev->angles.x; } else { m_yawCenter = pev->angles.y; m_pitchCenter = pev->angles.x; } } void CFuncTank::Precache( void ) { if( m_iszSpriteSmoke ) PRECACHE_MODEL( STRING( m_iszSpriteSmoke ) ); if( m_iszSpriteFlash ) PRECACHE_MODEL( STRING( m_iszSpriteFlash ) ); if( pev->noise ) PRECACHE_SOUND( STRING( pev->noise ) ); } 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, "barrel" ) ) { m_barrelPos.x = atof( pkvd->szValue ); pkvd->fHandled = TRUE; } else if( FStrEq( pkvd->szKeyName, "barrely" ) ) { m_barrelPos.y = atof( pkvd->szValue ); pkvd->fHandled = TRUE; } else if( FStrEq( pkvd->szKeyName, "barrelz" ) ) { m_barrelPos.z = atof( 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, "minRange" ) ) { m_minRange = atof( pkvd->szValue ); pkvd->fHandled = TRUE; } else if( FStrEq( pkvd->szKeyName, "maxRange" ) ) { m_maxRange = atof( 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 if (FStrEq(pkvd->szKeyName, "m_iszLocusFire")) { m_iszLocusFire = ALLOC_STRING(pkvd->szValue); pkvd->fHandled = TRUE; } else CBaseEntity::KeyValue( pkvd ); } //================================================================================== // TANK CONTROLLING /*LRC- TankControls checks this instead BOOL CFuncTank::OnControls( entvars_t *pevTest ) { if( !( pev->spawnflags & SF_TANK_CANCONTROL ) ) return FALSE; //Vector offset = pevTest->origin - pev->origin; if( ( m_vecControllerUsePos - pevTest->origin ).Length() < 30 ) return TRUE; return FALSE; } */ BOOL CFuncTank :: StartControl( CBasePlayer* pController, CFuncTankControls *pControls ) { // ALERT(at_console, "StartControl\n"); // we're already being controlled or playing a sequence if ( m_pControls != NULL || m_pSequence != NULL ) { // ALERT(at_debug,"StartControl failed, already in use\n"); return FALSE; } // Team only or disabled? if( m_iszMaster ) { if( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) { // ALERT(at_debug,"StartControl failed, locked\n"); return FALSE; } } // ALERT( at_console, "using TANK!\n"); m_iActive = 1; m_pControls = pControls; if (m_pSpot) m_pSpot->Revive(); // if (m_pViewTarg) m_pViewTarg->Revive(); SetNextThink(0.1); // ALERT(at_debug,"StartControl succeeded\n"); m_iActive = 0; return TRUE; } void CFuncTank :: StopControl( CFuncTankControls* pControls) { //LRC- various commands moved from here to FuncTankControls if ( !m_pControls || m_pControls != pControls) { //ALERT(at_debug,"StopControl failed, not in use\n"); return; } // ALERT(at_debug,"StopControl succeeded\n"); // ALERT( at_debug, "stopped using TANK\n"); if (m_pSpot) m_pSpot->Suspend(-1); // if (m_pViewTarg) m_pViewTarg->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 ); // ALERT( "%f %f\n", gpGlobals->v_forward.y, vecAiming.y ); /* float a = gpGlobals->v_forward.y * vecAiming.y + gpGlobals->v_forward.x * vecAiming.x; m_pPlayer->pev->punchangle.y = acos( a ) * (180 / M_PI); ALERT( at_console, "%f\n", a ); */ UTIL_SetOrigin( m_pSpot, tr.vecEndPos ); } } // Called each frame by PostThink, via Use. // all we do here is handle firing. // LRC- this is now never called. Think functions are handling it all. /*void CFuncTank :: ControllerPostFrame( void ) { ASSERT( m_pController != NULL ); if( gpGlobals->time < m_flNextAttack ) return; if( m_pController->pev->button & IN_ATTACK ) { Vector vecForward; UTIL_MakeVectorsPrivate( pev->angles, vecForward, NULL, NULL ); m_fireLast = gpGlobals->time - ( 1 / m_fireRate ) - 0.01; // to make sure the gun doesn't fire too many bullets Fire( BarrelPosition(), vecForward, m_pController->pev ); // HACKHACK -- make some noise (that the AI can hear) if( m_pController && m_pController->IsPlayer() ) ( (CBasePlayer *)m_pController )->m_iWeaponVolume = LOUD_GUN_VOLUME; m_flNextAttack = gpGlobals->time + ( 1 / m_fireRate ); } }*/ ////////////// END NEW STUFF ////////////// void CFuncTank::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if( pev->spawnflags & SF_TANK_CANCONTROL ) { // player controlled turret if( pActivator->Classify() != CLASS_PLAYER ) return; // from Player::PostThink. ("try fire the gun") if( value == 2 && useType == USE_SET ) { // LRC- actually, we handle firing with TrackTarget, to support multitank. // ControllerPostFrame(); } // LRC- tankcontrols handles all this // else if ( !m_pController && useType != USE_OFF ) // { // // LRC- add one more tank to the ones the player's using // ((CBasePlayer*)pActivator)->m_pTank = this; // StartControl( (CBasePlayer*)pActivator ); // } // else // { // // probably from Player::PostThink- player stopped using tank. // StopControl(); // } } else { if( !ShouldToggle( useType, IsActive() ) ) return; if( IsActive() ) { TankDeactivate(); if (m_pSpot) m_pSpot->Suspend(-1); } else { TankActivate(); if (m_pSpot) m_pSpot->Revive(); } } } edict_t *CFuncTank::FindTarget( edict_t *pPlayer ) { return pPlayer; } CBaseEntity *CFuncTank:: BestVisibleEnemy ( void ) { CBaseEntity *pReturn; int iNearest; int iDist; int iBestRelationship; int iLookDist = m_maxRange?m_maxRange: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]; } } } } // if (pReturn) // ALERT(at_debug, "Tank's best enemy is %s\n", STRING(pReturn->pev->classname)); // else // ALERT(at_debug, "Tank has no best enemy\n"); 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_minRange ) return FALSE; if( m_maxRange > 0 && range > m_maxRange ) return FALSE; return TRUE; } //LRC void CFuncTank :: StartSequence(CTankSequence *pSequence) { m_pSequence = pSequence; SetNextThink(1.0); } //LRC void CFuncTank :: StopSequence( ) { StopRotSound(); DontThink(); pev->avelocity = g_vecZero; m_pSequence = NULL; m_pSequenceEnemy = NULL; } // NB: tracktarget updates nextthink void CFuncTank::Think( void ) { // pev->avelocity = g_vecZero; TrackTarget(); if( fabs( pev->avelocity.x ) > 1 || fabs( pev->avelocity.y ) > 1 ) StartRotSound(); else StopRotSound(); } void CFuncTank::TrackTarget( void ) { TraceResult tr; // edict_t *pPlayer; BOOL updateTime = FALSE, lineOfSight; Vector angles, direction, targetPosition, barrelEnd; Vector v_right, v_up; CBaseEntity *pTarget; CBasePlayer* pController = NULL; // ALERT(at_console,"TrackTarget\n"); // update the barrel position if (m_pFireProxy) { m_pFireProxy->pev->origin = BarrelPosition(); UTIL_MakeVectorsPrivate( pev->angles, m_pFireProxy->pev->velocity, NULL, NULL ); } // Get a position to aim for if (m_pSequence) { UpdateSpot(); SetNextThink(0.05, FALSE); if (m_pSequence->m_iTurn == TSEQ_TURN_ENEMY) { CBaseMonster *pMonst = m_pSequenceEnemy->MyMonsterPointer(); if (pMonst && !pMonst->IsAlive()) m_pSequence->DeadEnemyNotify(); // Work out what angle we need to face to look at the enemy targetPosition = m_pSequenceEnemy->pev->origin + m_pSequenceEnemy->pev->view_ofs; direction = targetPosition - pev->origin; angles = UTIL_VecToAngles( direction ); AdjustAnglesForBarrel( angles, direction.Length() ); } else if (m_pSequence->m_iTurn == TSEQ_TURN_ANGLE) { angles = m_pSequence->pev->angles; } else if (m_pSequence->m_iTurn == TSEQ_TURN_FACE) { // Work out what angle we need to face to look at the sequence direction = m_pSequence->pev->origin - pev->origin; angles = UTIL_VecToAngles( direction ); AdjustAnglesForBarrel( angles, direction.Length() ); } } else if (m_pControls && m_pControls->m_pController) { // ALERT( at_console, "TANK has controller\n"); UpdateSpot(); pController = m_pControls->m_pController; SetNextThink(0.05, FALSE); // LRC- changed here to allow "match target" as well as "match angles" mode. 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 // if (!m_pViewTarg) // { // m_pViewTarg = CLaserSpot::CreateSpot("sprites/mommablob.spr"); // } // UTIL_SetOrigin( m_pViewTarg, tr.vecEndPos ); // 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; // ALERT( at_console, "TANK: look at pos %.0f %.0f %.0f; target angle %.0f %.0f %.0f\n", // targetPosition.x,targetPosition.y,targetPosition.z,angles.x,angles.y,angles.z); // 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 { // ALERT( at_console, "TANK has no controller\n"); if( IsActive() ) { SetNextThink(0.1); } else { DontThink(); UTIL_SetAvelocity(this, g_vecZero); return; } UpdateSpot(); // if we can't see any players //pPlayer = FIND_CLIENT_IN_PVS( edict() ); 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; } // Track sight origin // !!! I'm not sure what i changed (cuh, these Valve cowboys... --LRC) // m_sightOrigin is the last known location of the player. direction = m_sightOrigin - pev->origin; //direction = m_sightOrigin - barrelEnd; angles = UTIL_VecToAngles( direction ); // Calculate the additional rotation to point the end of the barrel at the target // (not the gun's center) AdjustAnglesForBarrel( angles, direction.Length() ); } angles.x = -angles.x; float currentYawCenter, currentPitchCenter; // Force the angles to be relative to the center position if (m_pMoveWith) { currentYawCenter = m_yawCenter + m_pMoveWith->pev->angles.y; currentPitchCenter = m_pitchCenter + m_pMoveWith->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 ); // ALERT(at_console, "%f -> %f: dist= %f\n", angles.y, pev->angles.y, distY); 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 if( m_pSequence && fabs( distY ) < 0.1 && fabs( distX ) < 0.1 ) m_pSequence->FacingNotify(); // firing in tanksequences: if ( m_pSequence ) { if ( gpGlobals->time < m_flNextAttack ) return; if ( pev->spawnflags & SF_TANK_SEQFIRE ) // does the sequence want me to fire? { 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, pev ); m_flNextAttack = gpGlobals->time + (1/m_fireRate); } return; } // firing with player-controlled tanks: else if ( pController ) { if ( gpGlobals->time < m_flNextAttack ) return; // FIXME- use m_???Tolerance to fire in the desired direction, // instead of the one we're facing. 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 ); // HACKHACK -- make some noise (that the AI can hear) if ( pController && pController->IsPlayer() ) ((CBasePlayer *)pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; m_flNextAttack = gpGlobals->time + (1/m_fireRate); } } // 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; } // If barrel is offset, add in additional rotation 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 ) { // ALERT(at_console, "TryFire\n"); if (UTIL_IsMasterTriggered(m_iszFireMaster, NULL)) { // ALERT(at_console, "Fire is %p, rocketfire %p, tankfire %p\n", this->Fire, CFuncTankRocket::Fire, CFuncTank::Fire); Fire( barrelEnd, forward, pevAttacker ); } // else // m_fireLast = 0; } // Fire targets and spawn sprites void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) { // ALERT(at_console, "FuncTank::Fire\n"); 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, (int)pev->rendercolor.x, (int)pev->rendercolor.y, (int)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 ); // Hack Hack, make it stick around for at least 100 ms. pSprite->AbsoluteNextThink( pSprite->m_fNextThink + 0.1 ); } //LRC if (m_iszLocusFire) { FireTargets( STRING(m_iszLocusFire), m_pFireProxy, this, USE_TOGGLE, 0 ); } SUB_UseTargets( this, USE_TOGGLE, 0 ); } 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, STRING( pev->noise ), 0.85, ATTN_NORM ); } void CFuncTank::StopRotSound( void ) { if( pev->spawnflags & SF_TANK_SOUNDON ) STOP_SOUND( edict(), CHAN_STATIC, STRING( pev->noise ) ); pev->spawnflags &= ~SF_TANK_SOUNDON; } 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 = (int)( ( 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_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker ); break; case TANK_BULLET_MP5: FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_MP5, 1, m_iBulletDamage, pevAttacker ); break; case TANK_BULLET_12MM: FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_12MM, 1, m_iBulletDamage, pevAttacker ); break; default: case TANK_BULLET_NONE: break; } } CFuncTank::Fire( barrelEnd, forward, pevAttacker ); } } else CFuncTank::Fire( barrelEnd, forward, pevAttacker ); } class CFuncTankLaser : public CFuncTank { public: void Activate( void ); void KeyValue( KeyValueData *pkvd ); void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); void Think( void ); CLaser *GetLaser( void ); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; virtual void StopFire( void ); 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 ); ALERT( at_error, "Laser tank with no env_laser!\n" ); } else { m_pLaser->TurnOff(); } CFuncTank::Activate(); } 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; break; } else pEntity = UTIL_FindEntityByTargetname( pEntity, STRING(pev->message) ); } return m_pLaser; } void CFuncTankLaser::Think( void ) { if( m_pLaser && (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 = (int)( ( 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; m_pLaser->TurnOn(); m_pLaser->pev->dmgtime = gpGlobals->time - 1.0; m_pLaser->FireAtPoint( barrelEnd, tr ); //LRC - tripbeams CBaseEntity* pTrip; if (!FStringNull(m_pLaser->pev->target) && (pTrip = m_pLaser->GetTripEntity( &tr )) != NULL) FireTargets(STRING(m_pLaser->pev->target), pTrip, m_pLaser, USE_TOGGLE, 0); m_pLaser->DontThink(); } CFuncTank::Fire( barrelEnd, forward, pev ); } } else { CFuncTank::Fire( barrelEnd, forward, pev ); } } void CFuncTankLaser::StopFire( void ) { if( m_pLaser ) m_pLaser->TurnOff(); } class CFuncTankRocket : public CFuncTank { public: void Precache( void ); virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); }; LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ) void CFuncTankRocket::Precache( void ) { UTIL_PrecacheOther( "rpg_rocket" ); CFuncTank::Precache(); } void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) { int i; if( m_fireLast != 0 ) { int bulletCount = (int)( ( gpGlobals->time - m_fireLast ) * m_fireRate ); if( bulletCount > 0 ) { for( i = 0; i < bulletCount; i++ ) { CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() ); } CFuncTank::Fire( barrelEnd, forward, pev ); } } else CFuncTank::Fire( barrelEnd, forward, pev ); } 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 = (int)( ( 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 ); ExplosionCreate( tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE ); CFuncTank::Fire( barrelEnd, forward, pev ); } } else CFuncTank::Fire( barrelEnd, forward, pev ); } //============================================================================ // FUNC TANK CONTROLS //============================================================================ #define SF_TANKCONTROLS_NO_USE 1 #define SF_TANKCONTROLS_VISIBLE 2 LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls ); TYPEDESCRIPTION CFuncTankControls::m_SaveData[] = { // DEFINE_FIELD( CFuncTankControls, m_pTank, FIELD_CLASSPTR ), DEFINE_FIELD( CFuncTankControls, m_active, FIELD_BOOLEAN ), DEFINE_FIELD( CFuncTankControls, m_pController, FIELD_CLASSPTR ), DEFINE_FIELD( CFuncTankControls, m_vecControllerUsePos, FIELD_VECTOR ), DEFINE_FIELD( CFuncTankControls, m_iCrosshair, FIELD_INTEGER ), //LRC }; IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity ); void CFuncTankControls :: KeyValue( KeyValueData *pkvd ) { if (FStrEq(pkvd->szKeyName, "crosshair")) { m_iCrosshair = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else CBaseEntity::KeyValue( pkvd ); } int CFuncTankControls::ObjectCaps( void ) { if (pev->spawnflags & SF_TANKCONTROLS_NO_USE) return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); else return ( CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION ) | FCAP_IMPULSE_USE; } //LRC- copied here from FuncTank. BOOL CFuncTankControls :: OnControls( entvars_t *pevTest ) { // if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) // return FALSE; Vector offset = pevTest->origin - pev->origin; if (pev->frags == -1) { // ALERT(at_console, "TANK OnControls: TRUE(full tolerance)\n"); return TRUE; } if (m_pMoveWith) { if ( ((m_vecControllerUsePos + m_pMoveWith->pev->origin) - pevTest->origin).Length() <= pev->frags ) { // ALERT(at_console, "TANK OnControls: TRUE(movewith)\n"); return TRUE; } } else if ( (m_vecControllerUsePos - pevTest->origin).Length() <= pev->frags ) { // ALERT(at_console, "TANK OnControls: TRUE\n"); return TRUE; } // ALERT(at_console, "TANK OnControls: FALSE\n"); return FALSE; } void CFuncTankControls::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // LRC- rewritten to allow TankControls to be the thing that handles the relationship // between the player and one or more faithful tanks. CBaseEntity *tryTank = NULL; // ALERT(at_console,"controls %p triggered by \"%s\" %p\n", this, STRING(pCaller->pev->classname), pCaller); if ( !m_pController && useType != USE_OFF ) { // if not activated by a player, don't work. if (!pActivator || !(pActivator->IsPlayer())) return; // if I've already got a controller, or the player's already using // another controls, then forget it. if (m_active != FALSE || ((CBasePlayer*)pActivator)->m_pTank != 0) return; //LRC- Now uses FindEntityByTargetname, so that aliases work. while( ( tryTank = UTIL_FindEntityByTargetname( tryTank, STRING( pev->target ) ) ) ) { if (!strncmp( STRING(tryTank->pev->classname), "func_tank", 9 )) { if (((CFuncTank*)tryTank)->StartControl((CBasePlayer*)pActivator, this)) { //ALERT(at_console,"started controlling tank %s\n",STRING(tryTank->pev->targetname)); // here's a tank we can control. Phew. m_active = TRUE; } } } if (m_active) { // 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; m_pController->pev->viewmodel = 0; } //LRC - allow tank crosshairs if (m_iCrosshair) m_pController->m_iHideHUD |= ( HIDEHUD_CUSTOMCROSSHAIR | HIDEHUD_WEAPONS ); else m_pController->m_iHideHUD |= HIDEHUD_WEAPONS; // remember where the player's standing, so we can tell when he walks away if (m_pMoveWith) m_vecControllerUsePos = m_pController->pev->origin - m_pMoveWith->pev->origin; else m_vecControllerUsePos = m_pController->pev->origin; //ALERT( at_console, "TANK controls activated\n"); } } else if (m_pController && useType != USE_ON) { // player stepped away or died, most likely. //ALERT(at_console, "TANK controls deactivated\n"); //LRC- Now uses FindEntityByTargetname, so that aliases work. while( ( tryTank = UTIL_FindEntityByTargetname( tryTank, STRING( pev->target ) ) ) ) { if( FClassnameIs( tryTank->pev, "func_tank" ) || FClassnameIs( tryTank->pev, "func_tanklaser" ) || FClassnameIs( tryTank->pev, "func_tankmortar" ) || FClassnameIs( tryTank->pev, "func_tankrocket" ) ) { // this is a tank we're controlling. ((CFuncTank*)tryTank)->StopControl(this); } } // if (!m_pController) // return; // bring back player's weapons if ( m_pController->m_pActiveItem ) m_pController->m_pActiveItem->Deploy(); m_pController->m_iHideHUD &= ~ (HIDEHUD_CUSTOMCROSSHAIR | HIDEHUD_WEAPONS); m_pController->m_pTank = NULL; m_pController = NULL; m_active = false; ((CBasePlayer *)pActivator)->m_iFOV = 0;//reset FOV } // if ( m_pTank ) // m_pTank->Use( pActivator, pCaller, useType, value ); // ASSERT( m_pTank != NULL ); // if this fails, most likely means save/restore hasn't worked properly } /* LRC- no need to set up m_pTank any more... void CFuncTankControls::Think( void ) { CBaseEntity *pTarget = NULL; do { pTarget = UTIL_FindEntityByClassname( pTarget, STRING(pev->target) ); } while ( pTarget && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); if ( !pTarget ) { ALERT( at_console, "No tank %s\n", STRING( pev->target ) ); return; } else { m_pTank = (CFuncTank*)pTarget; do { pTarget = UTIL_FindEntityByClassname( pTarget, STRING(pev->target) ); } while ( pTarget && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); if ( pTarget ) { m_pTank2 = (CFuncTank*)pTarget; ALERT( at_console, "Got second tank %s\n", STRING(pev->target) ); } } }*/ void CFuncTankControls::Spawn( void ) { pev->solid = SOLID_TRIGGER; pev->movetype = MOVETYPE_NONE; if (!(pev->spawnflags & SF_TANKCONTROLS_VISIBLE)) pev->effects |= EF_NODRAW; SET_MODEL( ENT( pev ), STRING( pev->model ) ); if (pev->frags == 0) //LRC- in case the level designer didn't set it. pev->frags = 30; UTIL_SetSize( pev, pev->mins, pev->maxs ); UTIL_SetOrigin( this, pev->origin ); //LRC SetNextThink( 0.3 ); // After all the func_tanks have spawned CBaseEntity::Spawn(); } //============================================================================ //LRC - Scripted Tank Sequence //============================================================================ LINK_ENTITY_TO_CLASS( scripted_tanksequence, CTankSequence ); TYPEDESCRIPTION CTankSequence::m_SaveData[] = { DEFINE_FIELD( CTankSequence, m_iszEntity, FIELD_STRING ), DEFINE_FIELD( CTankSequence, m_iszEnemy, FIELD_STRING ), DEFINE_FIELD( CTankSequence, m_iUntil, FIELD_INTEGER ), DEFINE_FIELD( CTankSequence, m_fDuration, FIELD_FLOAT ), DEFINE_FIELD( CTankSequence, m_iTurn, FIELD_INTEGER ), DEFINE_FIELD( CTankSequence, m_iShoot, FIELD_INTEGER ), DEFINE_FIELD( CTankSequence, m_iActive, FIELD_INTEGER ), DEFINE_FIELD( CTankSequence, m_iControllable, FIELD_INTEGER ), DEFINE_FIELD( CTankSequence, m_iLaserSpot, FIELD_INTEGER ), DEFINE_FIELD( CTankSequence, m_pTank, FIELD_CLASSPTR), }; IMPLEMENT_SAVERESTORE( CTankSequence, CBaseEntity ); int CTankSequence :: ObjectCaps( void ) { return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } void CTankSequence :: KeyValue( KeyValueData *pkvd ) { if (FStrEq(pkvd->szKeyName, "m_iUntil")) { m_iUntil = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "m_iTurn")) { m_iTurn = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "m_iShoot")) { m_iShoot = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "m_iActive")) { m_iActive = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "m_iControllable")) { m_iControllable = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "m_iLaserSpot")) { m_iLaserSpot = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) { m_iszEntity = ALLOC_STRING(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "m_iszEnemy")) { m_iszEnemy = ALLOC_STRING(pkvd->szValue); pkvd->fHandled = TRUE; } else CBaseEntity::KeyValue( pkvd ); } void CTankSequence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if (!ShouldToggle(useType)) return; if (GetState() == STATE_OFF) { // take control of the tank, start the sequence CBaseEntity* pEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity)); if (!pEnt || !FStrEq(STRING(pEnt->pev->classname), "func_tank")) { ALERT(at_error, "Invalid or missing tank \"%s\" for scripted_tanksequence!\n", STRING(m_iszEntity)); return; } CFuncTank* pTank = (CFuncTank*)pEnt; // check whether it's being controlled by another sequence if (pTank->m_pSequence) return; // check whether it's being controlled by the player if (pTank->m_pControls) { if (pev->spawnflags & SF_TSEQ_DUMPPLAYER) { pTank->StopControl( pTank->m_pControls ); } else if (!FBitSet(pev->spawnflags, SF_TSEQ_DUMPPLAYER)) { //ALERT(at_debug, "scripted_tanksequence can't take tank away from player\n"); return; } } //passed all the tests, so we can now take control of it. if (m_iTurn == TSEQ_TURN_ENEMY) { CBaseEntity *pEnemy; if (m_iszEnemy) pEnemy = UTIL_FindEntityGeneric(STRING(m_iszEnemy), pTank->pev->origin, pTank->m_maxRange ); else pEnemy = pTank->BestVisibleEnemy(); if (pEnemy) { pTank->m_pSequenceEnemy = pEnemy; pTank->StartSequence(this); } } else { pTank->StartSequence(this); } if (m_iShoot == TSEQ_SHOOT_ALWAYS) pTank->pev->spawnflags |= SF_TANK_SEQFIRE; else pTank->pev->spawnflags &= ~SF_TANK_SEQFIRE; m_pTank = pTank; if (m_fDuration) { SetThink(&CTankSequence :: TimeOutThink ); SetNextThink( m_fDuration ); } } else // don't check UNTIL_TRIGGER - any UNTIL value can be prematurely ended. { //disable any other end conditions DontThink(); // release control of the tank StopSequence(); } } void CTankSequence :: FacingNotify() { if (m_iUntil == TSEQ_UNTIL_FACING) { SetThink(&CTankSequence :: EndThink ); SetNextThink( 0 ); } else if (m_iShoot == TSEQ_SHOOT_FACING) m_pTank->pev->spawnflags |= SF_TANK_SEQFIRE; } void CTankSequence :: DeadEnemyNotify() { if (m_iUntil == TSEQ_UNTIL_DEATH) { SetThink(&CTankSequence :: EndThink ); SetNextThink( 0 ); } // else // m_pTank->pev->spawnflags &= ~SF_TANK_SEQFIRE; // if the enemy's dead, stop firing } void CTankSequence :: EndThink() { //the sequence has expired. Release control of the tank. StopSequence(); if (!FStringNull(pev->target)) FireTargets(STRING(pev->target), this, this, USE_TOGGLE, 0); } void CTankSequence :: TimeOutThink() { //the sequence has timed out. Release control of the tank. StopSequence(); if (!FStringNull(pev->netname)) FireTargets(STRING(pev->netname), this, this, USE_TOGGLE, 0); } void CTankSequence :: StopSequence() { if (!m_pTank) { ALERT(at_error, "TankSeq: StopSequence with no tank!\n"); return; // this shouldn't happen. Just insurance... } // if we're doing "shoot at end", fire that shot now. if (m_iShoot == TSEQ_SHOOT_ONCE) { m_pTank->m_fireLast = gpGlobals->time - 1/m_pTank->m_fireRate; // exactly one shot. Vector forward; UTIL_MakeVectorsPrivate( m_pTank->pev->angles, forward, NULL, NULL ); m_pTank->TryFire( m_pTank->BarrelPosition(), forward, m_pTank->pev ); } if (m_iLaserSpot) { if (m_pTank->pev->spawnflags & SF_TANK_LASERSPOT && m_iLaserSpot != TSEQ_FLAG_ON) { m_pTank->pev->spawnflags &= ~SF_TANK_LASERSPOT; } else if (!FBitSet(m_pTank->pev->spawnflags, SF_TANK_LASERSPOT) && m_iLaserSpot != TSEQ_FLAG_OFF) { m_pTank->pev->spawnflags |= SF_TANK_LASERSPOT; } } if (m_iControllable) { if (m_pTank->pev->spawnflags & SF_TANK_CANCONTROL && m_iControllable != TSEQ_FLAG_ON) { m_pTank->pev->spawnflags &= ~SF_TANK_CANCONTROL; } else if (!(m_pTank->pev->spawnflags & SF_TANK_CANCONTROL) && m_iControllable != TSEQ_FLAG_OFF) { m_pTank->pev->spawnflags |= SF_TANK_CANCONTROL; } } m_pTank->StopSequence(); if (!FBitSet(pev->spawnflags, SF_TSEQ_REPEATABLE)) UTIL_Remove( this ); if (m_pTank->IsActive() && (m_iActive == TSEQ_FLAG_OFF || m_iActive == TSEQ_FLAG_TOGGLE)) { m_pTank->TankDeactivate(); if (m_pTank->m_pSpot) m_pTank->m_pSpot->Suspend(-1); } else if (!m_pTank->IsActive() && (m_iActive == TSEQ_FLAG_ON || m_iActive == TSEQ_FLAG_TOGGLE)) { m_pTank->TankActivate(); if (m_pTank->m_pSpot) m_pTank->m_pSpot->Revive(); } m_pTank = NULL; }