diff --git a/src/dlls/Makefile b/src/dlls/Makefile index a4865b9..3db3e86 100644 --- a/src/dlls/Makefile +++ b/src/dlls/Makefile @@ -40,6 +40,7 @@ OBJ = \ squeakgrenade.o \ subs.o \ talkmonster.o \ + turret.o \ util.o \ weapons.o \ zombie.o diff --git a/src/dlls/cmbasemonster.h b/src/dlls/cmbasemonster.h index d76763a..9f9b666 100644 --- a/src/dlls/cmbasemonster.h +++ b/src/dlls/cmbasemonster.h @@ -1096,4 +1096,142 @@ private: float m_flameY; }; + +// maybe put this on a separate header file? +typedef enum +{ + TURRET_ANIM_NONE = 0, + TURRET_ANIM_FIRE, + TURRET_ANIM_SPIN, + TURRET_ANIM_DEPLOY, + TURRET_ANIM_RETIRE, + TURRET_ANIM_DIE, +} TURRET_ANIM; + +class CMBaseTurret : public CMBaseMonster +{ +public: + void Spawn(void); + virtual void Precache(void); + void KeyValue( KeyValueData *pkvd ); + void EXPORT TurretUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ); + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Classify(void); + + int BloodColor( void ) { return DONT_BLEED; } + void GibMonster( void ) {} // UNDONE: Throw turret gibs? + + // Think functions + + void EXPORT ActiveThink(void); + void EXPORT SearchThink(void); + void EXPORT AutoSearchThink(void); + void EXPORT TurretDeath(void); + + virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; } + virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; } + + // void SpinDown(void); + // float EXPORT SpinDownCall( void ) { return SpinDown(); } + + // virtual float SpinDown(void) { return 0;} + // virtual float Retire(void) { return 0;} + + void EXPORT Deploy(void); + void EXPORT Retire(void); + + void EXPORT Initialize(void); + + virtual void Ping(void); + virtual void EyeOn(void); + virtual void EyeOff(void); + + // other functions + void SetTurretAnim(TURRET_ANIM anim); + int MoveTurret(void); + virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { }; + + float m_flMaxSpin; // Max time to spin the barrel w/o a target + int m_iSpin; + + CMSprite *m_pEyeGlow; + int m_eyeBrightness; + + int m_iDeployHeight; + int m_iRetractHeight; + int m_iMinPitch; + + int m_iBaseTurnRate; // angles per second + float m_fTurnRate; // actual turn rate + int m_iOrientation; // 0 = floor, 1 = Ceiling + int m_iOn; + int m_fBeserk; // Sometimes this bitch will just freak out + int m_iAutoStart; // true if the turret auto deploys when a target + // enters its range + + Vector m_vecLastSight; + float m_flLastSight; // Last time we saw a target + float m_flMaxWait; // Max time to seach w/o a target + int m_iSearchSpeed; // Not Used! + + // movement + float m_flStartYaw; + Vector m_vecCurAngles; + Vector m_vecGoalAngles; + + + float m_flPingTime; // Time until the next ping, used when searching + float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching +}; + + +class CMTurret : public CMBaseTurret +{ +public: + void Spawn(void); + void Precache(void); + // Think functions + void SpinUpCall(void); + void SpinDownCall(void); + + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + void Killed( entvars_t *pevAttacker, int iGib ); + +private: + int m_iStartSpin; + +}; + + +class CMMiniTurret : public CMBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + void Killed( entvars_t *pevAttacker, int iGib ); +}; + + +//========================================================= +// Sentry gun - smallest turret, placed near grunt entrenchments +//========================================================= +class CMSentry : public CMBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void Killed( entvars_t *pevAttacker, int iGib ); + void EXPORT SentryTouch( edict_t *pOther ); + void EXPORT SentryDeath( void ); + +}; + #endif // BASEMONSTER_H diff --git a/src/dlls/dllapi.cpp b/src/dlls/dllapi.cpp index 9a40fe8..0f0220b 100644 --- a/src/dlls/dllapi.cpp +++ b/src/dlls/dllapi.cpp @@ -131,6 +131,9 @@ monster_type_t monster_types[]= "monster_snark", FALSE, "monster_zombie", FALSE, "monster_gargantua", FALSE, + "monster_turret", FALSE, + "monster_miniturret", FALSE, + "monster_sentry", FALSE, "info_node", FALSE, // Nodes "info_node_air", FALSE, "", FALSE @@ -404,6 +407,9 @@ bool spawn_monster(int monster_type, Vector origin, Vector angles, int respawn_i case 12: monsters[monster_index].pMonster = CreateClassPtr((CMSqueakGrenade *)NULL); break; case 13: monsters[monster_index].pMonster = CreateClassPtr((CMZombie *)NULL); break; case 14: monsters[monster_index].pMonster = CreateClassPtr((CMGargantua *)NULL); break; + case 15: monsters[monster_index].pMonster = CreateClassPtr((CMTurret *)NULL); break; + case 16: monsters[monster_index].pMonster = CreateClassPtr((CMMiniTurret *)NULL); break; + case 17: monsters[monster_index].pMonster = CreateClassPtr((CMSentry *)NULL); break; } if (monsters[monster_index].pMonster == NULL) @@ -509,10 +515,14 @@ void MonsterCommand(void) // check for a valid monster name... for (index = 0; monster_types[index].name[0]; index++) { - if (strcmp(parg1, monster_types[index].name) == 0) + // ensure it's an actual monster classname + if (strncmp(parg1, "monster", 7) == 0) { - monster_type = index; - break; + if (strcmp(parg1, monster_types[index].name) == 0) + { + monster_type = index; + break; + } } } @@ -778,13 +788,17 @@ void MonsterCommand(void) msg[0] = 0; for (index = 0; monster_types[index].name[0]; index++) { - strcat(msg, monster_types[index].name); - strcat(msg, " "); - if (strlen(msg) > 60) + // limit list to monster entities only + if (strncmp(monster_types[index].name, "monster", 7) == 0) { - //META_CONS("[MONSTER] %s", msg); - LOG_MESSAGE(PLID, "%s", msg); - msg[0] = 0; + strcat(msg, monster_types[index].name); + strcat(msg, " "); + if (strlen(msg) > 60) + { + //META_CONS("[MONSTER] %s", msg); + LOG_MESSAGE(PLID, "%s", msg); + msg[0] = 0; + } } } if (msg[0]) @@ -1032,8 +1046,11 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) CMScientist scientist; CMSqueakGrenade snark; CMZombie zombie; - CMGargantua gargantua; - + CMGargantua gargantua; + CMTurret turret; + CMMiniTurret miniturret; + CMSentry sentry; + g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); (g_engfuncs.pfnAddServerCommand)("monster", MonsterCommand); @@ -1065,6 +1082,9 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) case 12: snark.Precache(); break; case 13: zombie.Precache(); break; case 14: gargantua.Precache(); break; + case 15: turret.Precache(); break; + case 16: miniturret.Precache(); break; + case 17: sentry.Precache(); break; } } } diff --git a/src/dlls/skill.cpp b/src/dlls/skill.cpp index 76ceeb1..609dc44 100644 --- a/src/dlls/skill.cpp +++ b/src/dlls/skill.cpp @@ -57,14 +57,10 @@ skill_cfg_t skill_cfg[] = { {"sk_islave_dmg_zap", &gSkillData.slaveDmgZap}, {"sk_ichthyosaur_health", &gSkillData.ichthyosaurHealth}, {"sk_ichthyosaur_shake", &gSkillData.ichthyosaurDmgShake}, - {"sk_leech_health", &gSkillData.leechHealth}, - {"sk_leech_dmg_bite", &gSkillData.leechDmgBite}, {"sk_controller_health", &gSkillData.controllerHealth}, {"sk_controller_dmgzap", &gSkillData.controllerDmgZap}, {"sk_controller_speedball", &gSkillData.controllerSpeedBall}, {"sk_controller_dmgball", &gSkillData.controllerDmgBall}, - {"sk_nihilanth_health", &gSkillData.nihilanthHealth}, - {"sk_nihilanth_zap", &gSkillData.nihilanthZap}, {"sk_scientist_health", &gSkillData.scientistHealth}, {"sk_scientist_heal", &gSkillData.scientistHeal}, {"sk_snark_health", &gSkillData.snarkHealth}, @@ -73,6 +69,9 @@ skill_cfg_t skill_cfg[] = { {"sk_zombie_health", &gSkillData.zombieHealth}, {"sk_zombie_dmg_one_slash", &gSkillData.zombieDmgOneSlash}, {"sk_zombie_dmg_both_slash", &gSkillData.zombieDmgBothSlash}, + {"sk_turret_health", &gSkillData.turretHealth}, + {"sk_miniturret_health", &gSkillData.miniturretHealth}, + {"sk_sentry_health", &gSkillData.sentryHealth}, {"sk_12mm_bullet", &gSkillData.monDmg9MM}, {"sk_9mmAR_bullet", &gSkillData.monDmgMP5}, {"sk_9mm_bullet", &gSkillData.monDmg12MM}, @@ -128,14 +127,14 @@ void monster_skill_init(void) char filename[256]; FILE *fp = NULL; - // Alien Grunt (agrunt) + // Alien Grunt gSkillData.agruntHealth = 90.0f; gSkillData.agruntDmgPunch = 20.0f; - // Apache (apache) + // Apache gSkillData.apacheHealth = 250.0f; - // Barney (barney) + // Barney gSkillData.barneyHealth = 35.0f; // Big momma @@ -144,7 +143,7 @@ void monster_skill_init(void) gSkillData.bigmommaDmgBlast = 120.0f; gSkillData.bigmommaRadiusBlast = 250.0f; - // Bullsquid (bullsquid) + // Bullsquid gSkillData.bullsquidHealth = 40.0f; gSkillData.bullsquidDmgBite = 25.0f; gSkillData.bullsquidDmgWhip = 35.0f; @@ -157,24 +156,24 @@ void monster_skill_init(void) gSkillData.gargantuaDmgFire = 5.0f; gSkillData.gargantuaDmgStomp = 100.0f; - // Hassassin (hassassin) + // Hassassin (Female Assassin) gSkillData.hassassinHealth = 50.0f; - // Headcrab (headcrab) + // Headcrab gSkillData.headcrabHealth = 10.0f; gSkillData.headcrabDmgBite = 10.0f; - // Hgrunt (hgrunt) + // Hgrunt (Human Grunt) gSkillData.hgruntHealth = 50.0f; gSkillData.hgruntDmgKick = 10.0f; gSkillData.hgruntShotgunPellets = 5.0f; gSkillData.hgruntGrenadeSpeed = 600.0f; - // Houndeye (houndeye) + // Houndeye gSkillData.houndeyeHealth = 20.0f; gSkillData.houndeyeDmgBlast = 15.0f; - // ISlave (islave) + // Alien Slave gSkillData.slaveHealth = 30.0f; gSkillData.slaveDmgClaw = 10.0f; gSkillData.slaveDmgClawrake = 25.0f; @@ -184,34 +183,35 @@ void monster_skill_init(void) gSkillData.ichthyosaurHealth = 200.0f; gSkillData.ichthyosaurDmgShake = 35.0f; - // Leech - gSkillData.leechHealth = 2.0f; - gSkillData.leechDmgBite = 2.0f; - // Controller gSkillData.controllerHealth = 60.0f; gSkillData.controllerDmgZap = 25.0f; gSkillData.controllerSpeedBall = 800.0f; gSkillData.controllerDmgBall = 4.0f; - // Nihilanth - gSkillData.nihilanthHealth = 800.0f; - gSkillData.nihilanthZap = 30.0f; - - // Scientist (scientist) + // Scientist gSkillData.scientistHealth = 20.0f; gSkillData.scientistHeal = 25.0f; - // Snark (snark) + // Snark gSkillData.snarkHealth = 2.0f; gSkillData.snarkDmgBite = 10.0f; gSkillData.snarkDmgPop = 5.0f; - // Zombie (zombie) + // Zombie gSkillData.zombieHealth = 50.0f; gSkillData.zombieDmgOneSlash = 20.0f; gSkillData.zombieDmgBothSlash = 40.0f; + // Turret + gSkillData.turretHealth = 50.0f; + + // Mini-Turret + gSkillData.miniturretHealth = 40.0f; + + // Sentry + gSkillData.sentryHealth = 40.0f; + // MONSTER WEAPONS gSkillData.monDmg9MM = 5.0f; gSkillData.monDmgMP5 = 4.0f; diff --git a/src/dlls/skill.h b/src/dlls/skill.h index 834c16b..ba61df0 100644 --- a/src/dlls/skill.h +++ b/src/dlls/skill.h @@ -66,17 +66,11 @@ struct skilldata_t float ichthyosaurHealth; float ichthyosaurDmgShake; - float leechHealth; - float leechDmgBite; - float controllerHealth; float controllerDmgZap; float controllerSpeedBall; float controllerDmgBall; - float nihilanthHealth; - float nihilanthZap; - float scientistHealth; float scientistHeal; @@ -87,6 +81,10 @@ struct skilldata_t float zombieHealth; float zombieDmgOneSlash; float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; // weapons shared by monsters float monDmg9MM; @@ -97,7 +95,7 @@ struct skilldata_t float monDmgHornet; }; -extern DLL_GLOBAL skilldata_t gSkillData; +extern DLL_GLOBAL skilldata_t gSkillData; void monster_skill_init(void); diff --git a/src/dlls/turret.cpp b/src/dlls/turret.cpp new file mode 100755 index 0000000..730a32a --- /dev/null +++ b/src/dlls/turret.cpp @@ -0,0 +1,1133 @@ +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== turret.cpp ======================================================== + +*/ + +// +// TODO: +// Take advantage of new monster fields like m_hEnemy and get rid of that OFFSET() stuff +// Revisit enemy validation stuff, maybe it's not necessary with the newest monster code +// + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "weapons.h" +#include "effects.h" +#include "skill.h" + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +#define TURRET_SHOTS 2 +#define TURRET_RANGE (100 * 12) +#define TURRET_SPREAD Vector( 0, 0, 0 ) +#define TURRET_TURNRATE 30 //angles per 0.1 second +#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target +#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target +#define TURRET_MACHINE_VOLUME 0.5 + + +void CMBaseTurret::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "maxsleep")) + { + m_flMaxWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "orientation")) + { + m_iOrientation = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "searchspeed")) + { + m_iSearchSpeed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "turnrate")) + { + m_iBaseTurnRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CMBaseMonster::KeyValue( pkvd ); +} + + +void CMBaseTurret::Spawn() +{ + Precache( ); + pev->nextthink = gpGlobals->time + 1; + pev->movetype = MOVETYPE_FLY; + pev->sequence = 0; + pev->frame = 0; + pev->solid = SOLID_SLIDEBOX; + pev->takedamage = DAMAGE_AIM; + + SetBits (pev->flags, FL_MONSTER); + SetUse( &CMBaseTurret::TurretUse ); + + if (( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + && !( pev->spawnflags & SF_MONSTER_TURRET_STARTINACTIVE )) + { + m_iAutoStart = TRUE; + } + + ResetSequenceInfo( ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_flFieldOfView = VIEW_FIELD_FULL; + // m_flSightRange = TURRET_RANGE; +} + + +void CMBaseTurret::Precache( ) +{ + PRECACHE_SOUND ("turret/tu_fire1.wav"); + PRECACHE_SOUND ("turret/tu_ping.wav"); + PRECACHE_SOUND ("turret/tu_active2.wav"); + PRECACHE_SOUND ("turret/tu_die.wav"); + PRECACHE_SOUND ("turret/tu_die2.wav"); + PRECACHE_SOUND ("turret/tu_die3.wav"); + // PRECACHE_SOUND ("turret/tu_retract.wav"); // just use deploy sound to save memory + PRECACHE_SOUND ("turret/tu_deploy.wav"); + PRECACHE_SOUND ("turret/tu_spinup.wav"); + PRECACHE_SOUND ("turret/tu_spindown.wav"); + PRECACHE_SOUND ("turret/tu_search.wav"); + PRECACHE_SOUND ("turret/tu_alert.wav"); +} + +#define TURRET_GLOW_SPRITE "sprites/flare3.spr" + +void CMTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/turret.mdl"); + pev->health = gSkillData.turretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = TURRET_MAXSPIN; + pev->view_ofs.z = 12.75; + + CMBaseTurret::Spawn( ); + + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight)); + + SetThink(&CMTurret::Initialize); + + m_pEyeGlow = CMSprite::SpriteCreate( TURRET_GLOW_SPRITE, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 2 ); + m_eyeBrightness = 0; + + pev->nextthink = gpGlobals->time + 0.3; +} + +void CMTurret::Precache() +{ + CMBaseTurret::Precache( ); + PRECACHE_MODEL ("models/turret.mdl"); + PRECACHE_MODEL (TURRET_GLOW_SPRITE); +} + +void CMMiniTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/miniturret.mdl"); + pev->health = gSkillData.miniturretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = 0; + pev->view_ofs.z = 12.75; + + CMBaseTurret::Spawn( ); + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetThink(&CMMiniTurret::Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + + +void CMMiniTurret::Precache() +{ + CMBaseTurret::Precache( ); + PRECACHE_MODEL ("models/miniturret.mdl"); + PRECACHE_SOUND("weapons/hks1.wav"); + PRECACHE_SOUND("weapons/hks2.wav"); + PRECACHE_SOUND("weapons/hks3.wav"); +} + +void CMBaseTurret::Initialize(void) +{ + m_iOn = 0; + m_fBeserk = 0; + m_iSpin = 0; + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT; + m_flStartYaw = pev->angles.y; + if (m_iOrientation == 1) + { + pev->idealpitch = 180; + pev->angles.x = 180; + pev->view_ofs.z = -pev->view_ofs.z; + pev->effects |= EF_INVLIGHT; + pev->angles.y = pev->angles.y + 180; + if (pev->angles.y > 360) + pev->angles.y = pev->angles.y - 360; + } + + m_vecGoalAngles.x = 0; + + if (m_iAutoStart) + { + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CMBaseTurret::AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(&CMBaseTurret::SUB_DoNothing); +} + +void CMBaseTurret::TurretUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_iOn ) ) + return; + + if (m_iOn) + { + m_hEnemy = NULL; + pev->nextthink = gpGlobals->time + 0.1; + m_iAutoStart = FALSE;// switching off a turret disables autostart + //!!!! this should spin down first!!BUGBUG + SetThink(&CMBaseTurret::Retire); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; // turn on delay + + // if the turret is flagged as an autoactivate turret, re-enable it's ability open self. + if ( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + { + m_iAutoStart = TRUE; + } + + SetThink(&CMBaseTurret::Deploy); + } +} + + +void CMBaseTurret::Ping( void ) +{ + // make the pinging noise every second while searching + if (m_flPingTime == 0) + m_flPingTime = gpGlobals->time + 1; + else if (m_flPingTime <= gpGlobals->time) + { + m_flPingTime = gpGlobals->time + 1; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_ping.wav", 1, ATTN_NORM); + EyeOn( ); + } + else if (m_eyeBrightness > 0) + { + EyeOff( ); + } +} + + +void CMBaseTurret::EyeOn( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness != 255) + { + m_eyeBrightness = 255; + } + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } +} + + +void CMBaseTurret::EyeOff( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness > 0) + { + m_eyeBrightness = max( 0, m_eyeBrightness - 30 ); + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } + } +} + + +void CMBaseTurret::ActiveThink(void) +{ + int fAttack = 0; + Vector vecDirToEnemy; + + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if ((!m_iOn) || (m_hEnemy == NULL)) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CMBaseTurret::SearchThink); + return; + } + + // if it's dead, look for something new + if ( !UTIL_IsAlive( m_hEnemy ) ) + { + if (!m_flLastSight) + { + m_flLastSight = gpGlobals->time + 0.5; // continue-shooting timeout + } + else + { + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CMBaseTurret::SearchThink); + return; + } + } + } + + Vector vecMid = pev->origin + pev->view_ofs; + Vector vecMidEnemy = UTIL_BodyTarget( m_hEnemy, vecMid ); // m_hEnemy->BodyTarget( vecMid ); + + // Look for our current enemy + int fEnemyVisible = FBoxVisible(pev, VARS(m_hEnemy), vecMidEnemy ); + + vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy + float flDistToEnemy = vecDirToEnemy.Length(); + + Vector vec = UTIL_VecToAngles(vecMidEnemy - vecMid); + + // Current enemy is not visible. + if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) + { + if (!m_flLastSight) + m_flLastSight = gpGlobals->time + 0.5; + else + { + // Should we look for a new target? + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CMBaseTurret::SearchThink); + return; + } + } + fEnemyVisible = 0; + } + else + { + m_vecLastSight = vecMidEnemy; + } + + UTIL_MakeAimVectors(m_vecCurAngles); + + /* + ALERT( at_console, "%.0f %.0f : %.2f %.2f %.2f\n", + m_vecCurAngles.x, m_vecCurAngles.y, + gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_forward.z ); + */ + + Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; + vecLOS = vecLOS.Normalize(); + + // Is the Gun looking at the target + if (DotProduct(vecLOS, gpGlobals->v_forward) <= 0.866) // 30 degree slop + fAttack = FALSE; + else + fAttack = TRUE; + + // fire the gun + if (m_iSpin && ((fAttack) || (m_fBeserk))) + { + Vector vecSrc, vecAng; + GetAttachment( 0, vecSrc, vecAng ); + SetTurretAnim(TURRET_ANIM_FIRE); + Shoot(vecSrc, gpGlobals->v_forward ); + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } + + //move the gun + if (m_fBeserk) + { + if (RANDOM_LONG(0,9) == 0) + { + m_vecGoalAngles.y = RANDOM_FLOAT(0,360); + m_vecGoalAngles.x = RANDOM_FLOAT(0,90) - 90 * m_iOrientation; + TakeDamage(pev,pev,1, DMG_GENERIC); // don't beserk forever + return; + } + } + else if (fEnemyVisible) + { + if (vec.y > 360) + vec.y -= 360; + + if (vec.y < 0) + vec.y += 360; + + //ALERT(at_console, "[%.2f]", vec.x); + + if (vec.x < -180) + vec.x += 360; + + if (vec.x > 180) + vec.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-90...15] + + if (m_iOrientation == 0) + { + if (vec.x > 90) + vec.x = 90; + else if (vec.x < m_iMinPitch) + vec.x = m_iMinPitch; + } + else + { + if (vec.x < -90) + vec.x = -90; + else if (vec.x > -m_iMinPitch) + vec.x = -m_iMinPitch; + } + + // ALERT(at_console, "->[%.2f]\n", vec.x); + + m_vecGoalAngles.y = vec.y; + m_vecGoalAngles.x = vec.x; + + } + + SpinUpCall(); + MoveTurret(); +} + + +void CMTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6); + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CMMiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_9MM, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CMBaseTurret::Deploy(void) +{ + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if (pev->sequence != TURRET_ANIM_DEPLOY) + { + m_iOn = 1; + SetTurretAnim(TURRET_ANIM_DEPLOY); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SUB_UseTargets( this->edict(), USE_ON, 0 ); + } + + if (m_fSequenceFinished) + { + pev->maxs.z = m_iDeployHeight; + pev->mins.z = -m_iDeployHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + + m_vecCurAngles.x = 0; + + if (m_iOrientation == 1) + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y + 180 ); + } + else + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y ); + } + + SetTurretAnim(TURRET_ANIM_SPIN); + pev->framerate = 0; + SetThink(&CMBaseTurret::SearchThink); + } + + m_flLastSight = gpGlobals->time + m_flMaxWait; +} + +void CMBaseTurret::Retire(void) +{ + // make the turret level + m_vecGoalAngles.x = 0; + m_vecGoalAngles.y = m_flStartYaw; + + pev->nextthink = gpGlobals->time + 0.1; + + StudioFrameAdvance( ); + + EyeOff( ); + + if (!MoveTurret()) + { + if (m_iSpin) + { + SpinDownCall(); + } + else if (pev->sequence != TURRET_ANIM_RETIRE) + { + SetTurretAnim(TURRET_ANIM_RETIRE); + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120); + SUB_UseTargets( this->edict(), USE_OFF, 0 ); + } + else if (m_fSequenceFinished) + { + m_iOn = 0; + m_flLastSight = 0; + SetTurretAnim(TURRET_ANIM_NONE); + pev->maxs.z = m_iRetractHeight; + pev->mins.z = -m_iRetractHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + if (m_iAutoStart) + { + SetThink(&CMBaseTurret::AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(&CMBaseTurret::SUB_DoNothing); + } + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } +} + + +void CMTurret::SpinUpCall(void) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + // Are we already spun up? If not start the two stage process. + if (!m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + // for the first pass, spin up the the barrel + if (!m_iStartSpin) + { + pev->nextthink = gpGlobals->time + 1.0; // spinup delay + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_spinup.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + m_iStartSpin = 1; + pev->framerate = 0.1; + } + // after the barrel is spun up, turn on the hum + else if (pev->framerate >= 1.0) + { + pev->nextthink = gpGlobals->time + 0.1; // retarget delay + EMIT_SOUND(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SetThink(&CMTurret::ActiveThink); + m_iStartSpin = 0; + m_iSpin = 1; + } + else + { + pev->framerate += 0.075; + } + } + + if (m_iSpin) + { + SetThink(&CMTurret::ActiveThink); + } +} + + +void CMTurret::SpinDownCall(void) +{ + if (m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + if (pev->framerate == 1.0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_spindown.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } + pev->framerate -= 0.02; + if (pev->framerate <= 0) + { + pev->framerate = 0; + m_iSpin = 0; + } + } +} + + +void CMTurret::Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(&CMBaseTurret::TurretDeath); + SUB_UseTargets( this->edict(), USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + CMBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CMMiniTurret::Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(&CMBaseTurret::TurretDeath); + SUB_UseTargets( this->edict(), USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + CMBaseMonster::Killed( pevAttacker, iGib ); +} + +void CMBaseTurret::SetTurretAnim(TURRET_ANIM anim) +{ + if (pev->sequence != anim) + { + switch(anim) + { + case TURRET_ANIM_FIRE: + case TURRET_ANIM_SPIN: + if (pev->sequence != TURRET_ANIM_FIRE && pev->sequence != TURRET_ANIM_SPIN) + { + pev->frame = 0; + } + break; + default: + pev->frame = 0; + break; + } + + pev->sequence = anim; + ResetSequenceInfo( ); + + switch(anim) + { + case TURRET_ANIM_RETIRE: + pev->frame = 255; + pev->framerate = -1.0; + break; + case TURRET_ANIM_DIE: + pev->framerate = 1.0; + break; + } + //ALERT(at_console, "Turret anim #%d\n", anim); + } +} + + +// +// This search function will sit with the turret deployed and look for a new target. +// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will +// retact. +// +void CMBaseTurret::SearchThink(void) +{ + // ensure rethink + SetTurretAnim(TURRET_ANIM_SPIN); + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (m_flSpinUpTime == 0 && m_flMaxSpin) + m_flSpinUpTime = gpGlobals->time + m_flMaxSpin; + + Ping( ); + + // If we have a target and we're still healthy + if (m_hEnemy != NULL) + { + if (!UTIL_IsAlive( m_hEnemy ) ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + + // Acquire Target + if (m_hEnemy == NULL) + { + Look(TURRET_RANGE); + m_hEnemy = BestVisibleEnemy(); + } + + // If we've found a target, spin up the barrel and start to attack + if (m_hEnemy != NULL) + { + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CMBaseTurret::ActiveThink); + } + else + { + // Are we out of time, do we need to retract? + if (gpGlobals->time > m_flLastSight) + { + //Before we retrace, make sure that we are spun down. + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CMBaseTurret::Retire); + } + // should we stop the spin? + else if ((m_flSpinUpTime) && (gpGlobals->time > m_flSpinUpTime)) + { + SpinDownCall(); + } + + // generic hunt for new victims + m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate); + if (m_vecGoalAngles.y >= 360) + m_vecGoalAngles.y -= 360; + MoveTurret(); + } +} + + +// +// This think function will deploy the turret when something comes into range. This is for +// automatically activated turrets. +// +void CMBaseTurret::AutoSearchThink(void) +{ + // ensure rethink + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.3; + + // If we have a target and we're still healthy + + if (m_hEnemy != NULL) + { + if (!UTIL_IsAlive( m_hEnemy ) ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + // Acquire Target + + if (m_hEnemy == NULL) + { + Look( TURRET_RANGE ); + m_hEnemy = BestVisibleEnemy(); + } + + if (m_hEnemy != NULL) + { + SetThink(&CMBaseTurret::Deploy); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } +} + + +void CMBaseTurret :: TurretDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + if (m_iOrientation == 0) + m_vecGoalAngles.x = -15; + else + m_vecGoalAngles.x = -90; + + SetTurretAnim(TURRET_ANIM_DIE); + + EyeOn( ); + } + + EyeOff( ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ) ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ) ); + WRITE_COORD( pev->origin.z - m_iOrientation * 64 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 25 ); // scale * 10 + WRITE_BYTE( 10 - m_iOrientation * 5); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 5 ) > gpGlobals->time) + { + Vector vecSrc = Vector( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ), RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ), 0 ); + if (m_iOrientation == 0) + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->origin.z, pev->absmax.z ) ); + else + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->absmin.z, pev->origin.z ) ); + + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + SUB_StartFadeOut(); + } +} + + + +void CMBaseTurret :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 ) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + + if ( !pev->takedamage ) + return; + + AddMultiDamage( pevAttacker, this->edict(), flDamage, bitsDamageType ); +} + +// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET + +int CMBaseTurret::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + flDamage /= 10.0; + + pev->health -= flDamage; + + if (pev->health <= 10) + { + if (m_iOn && (1 || RANDOM_LONG(0, 0x7FFF) > 800)) + { + m_fBeserk = 1; + SetThink(&CMBaseTurret::SearchThink); + } + } + + return 1; +} + +int CMBaseTurret::MoveTurret(void) +{ + int state = 0; + // any x movement? + + if (m_vecCurAngles.x != m_vecGoalAngles.x) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + if (m_iOrientation == 0) + SetBoneController(1, -m_vecCurAngles.x); + else + SetBoneController(1, m_vecCurAngles.x); + state = 1; + } + + if (m_vecCurAngles.y != m_vecGoalAngles.y) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + } + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 10) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else if (m_fTurnRate > 45) + { + m_fTurnRate -= m_iBaseTurnRate; + } + else + { + m_fTurnRate += m_iBaseTurnRate; + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y); + if (m_iOrientation == 0) + SetBoneController(0, m_vecCurAngles.y - pev->angles.y ); + else + SetBoneController(0, pev->angles.y - 180 - m_vecCurAngles.y ); + state = 1; + } + + if (!state) + m_fTurnRate = m_iBaseTurnRate; + + //ALERT(at_console, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x, + // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y); + return state; +} + +// +// ID as a machine +// +int CMBaseTurret::Classify ( void ) +{ + if (m_iOn || m_iAutoStart) + return CLASS_MACHINE; + return CLASS_NONE; +} + + +void CMSentry::Precache() +{ + CMBaseTurret::Precache( ); + PRECACHE_MODEL ("models/sentry.mdl"); +} + +void CMSentry::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/sentry.mdl"); + pev->health = gSkillData.sentryHealth; + m_HackedGunPos = Vector( 0, 0, 48 ); + pev->view_ofs.z = 48; + m_flMaxWait = 1E6; + m_flMaxSpin = 1E6; + + CMBaseTurret::Spawn(); + m_iRetractHeight = 64; + m_iDeployHeight = 64; + m_iMinPitch = -60; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetTouch(&CMSentry::SentryTouch); + SetThink(&CMSentry::Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + +void CMSentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_MP5, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + +int CMSentry::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + { + SetThink( &CMSentry::Deploy ); + SetUse( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + } + + pev->health -= flDamage; + return 1; +} + + +void CMSentry::SentryTouch( edict_t *pOther ) +{ + if ( pOther && (UTIL_IsPlayer( pOther ) || (pOther->v.flags & FL_MONSTER)) ) + { + UTIL_TakeDamage(pOther, VARS(pOther), VARS(pOther), 0, 0 ); + } +} + + +void CMSentry::Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink( &CMSentry::SentryDeath); + SUB_UseTargets( this->edict(), USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + CMBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CMSentry::SentryDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + SetTurretAnim(TURRET_ANIM_DIE); + + pev->solid = SOLID_NOT; + pev->angles.y = UTIL_AngleMod( pev->angles.y + RANDOM_LONG( 0, 2 ) * 120 ); + + EyeOn( ); + } + + EyeOff( ); + + Vector vecSrc, vecAng; + GetAttachment( 1, vecSrc, vecAng ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.y + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 15 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 8 ) > gpGlobals->time) + { + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + SUB_StartFadeOut(); + } +} +