//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Maestro & BriJee better version // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "ai_basenpc.h" #include "ai_hull.h" #include "ai_senses.h" #include "ai_memory.h" #include "soundent.h" #include "smoke_trail.h" #include "weapon_rpg.h" #include "gib.h" #include "ndebugoverlay.h" #include "IEffects.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "ammodef.h" #include "hl2_shareddefs.h" #include "explode.h" #include "effect_dispatch_data.h" #include "te_effect_dispatch.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define MD_FULLAMMO 50 #define MD_BC_YAW 1 #define MD_BC_PITCH 1 #define MD_AP_LGUN 2 #define MD_AP_RGUN 1 #define MD_GIB_COUNT 4 //#define MD_GIB_MODEL "models/gibs/missile_defense_gibs.mdl" #define MD_YAW_SPEED 48 #define MD_PITCH_SPEED 48 //------------------------------------ // Spawnflags //------------------------------------ #define SF_MISSILEDEFENSE_BULLETDMG (1 << 16) //Can be damaged with bullets? N on def //========================================================= //========================================================= class CNPC_MissileDefense : public CAI_BaseNPC { DECLARE_CLASS(CNPC_MissileDefense, CAI_BaseNPC); DECLARE_DATADESC(); public: CNPC_MissileDefense(void) { }; void Precache(void); void Spawn(void); Class_T Classify(void) { return CLASS_NONE; } int GetSoundInterests(void) { return SOUND_NONE; } float MaxYawSpeed(void) { return 90.f; } void RunAI(void); void FireCannons(void); void AimGun(void); void EnemyShootPosition(CBaseEntity* pEnemy, Vector *vPosition); void Event_Killed(const CTakeDamageInfo &info); int OnTakeDamage_Alive(const CTakeDamageInfo &info); void Explode(const Vector &vecExplosionPos); void Gib(); void GetGunAim(Vector *vecAim); void TurretTurnOn(void); void DoMuzzleFlash(void); ~CNPC_MissileDefense(); Vector m_vGunAng; int m_iAmmoLoaded; float m_flReloadedTime; string_t m_sTurretModel; string_t m_sGibModel; string_t m_sFireSound; string_t m_sRotateSound; string_t m_sReloadSound; int m_nStartOn; int m_nHealth; // ---------------- // Inputs // ---------------- void InputTurnOn(inputdata_t &inputdata); void InputTurnOff(inputdata_t &inputdata); }; LINK_ENTITY_TO_CLASS(npc_missiledefense, CNPC_MissileDefense); ConVar sk_missiledefense_health("sk_missiledefense_health", "100"); //========================================================= //========================================================= BEGIN_DATADESC(CNPC_MissileDefense) DEFINE_FIELD(m_iAmmoLoaded, FIELD_INTEGER), DEFINE_FIELD(m_flReloadedTime, FIELD_TIME), DEFINE_FIELD(m_vGunAng, FIELD_VECTOR), DEFINE_KEYFIELD(m_sTurretModel, FIELD_STRING, "TurretModel"), DEFINE_KEYFIELD(m_sGibModel, FIELD_STRING, "GibModel"), DEFINE_KEYFIELD(m_sFireSound, FIELD_STRING, "FireSound"), DEFINE_KEYFIELD(m_sRotateSound, FIELD_STRING, "RotateSound"), DEFINE_KEYFIELD(m_sReloadSound, FIELD_STRING, "ReloadSound"), DEFINE_KEYFIELD(m_nHealth, FIELD_INTEGER, "Health"), DEFINE_INPUT(m_nStartOn, FIELD_INTEGER, "StartOn"), DEFINE_FIELD(m_nStartOn, FIELD_BOOLEAN), //Starts on? DEFINE_INPUTFUNC(FIELD_VOID, "TurnOn", InputTurnOn), DEFINE_INPUTFUNC(FIELD_VOID, "TurnOff", InputTurnOff), END_DATADESC() // =================== // Input Functions // =================== //-------------------------------------------------------------- // Purpose: Enables npc //-------------------------------------------------------------- void CNPC_MissileDefense::InputTurnOn(inputdata_t &inputdata) { m_nStartOn = 1; SetThink(&CNPC_MissileDefense::CallNPCThink); SetNextThink(gpGlobals->curtime); } //-------------------------------------------------------------- // Purpose: Disables npc //-------------------------------------------------------------- void CNPC_MissileDefense::InputTurnOff(inputdata_t &inputdata) { m_nStartOn = 0; SetThink(NULL); } //-------------------------------------------------------------- // Purpose: Precache models and sounds that are going to be used //-------------------------------------------------------------- void CNPC_MissileDefense::Precache(void) { PrecacheModel("models/missile_defense.mdl"); // BriJee: Def model PrecacheModel(STRING(m_sTurretModel)); PrecacheModel(STRING(m_sGibModel)); PrecacheScriptSound(STRING(m_sFireSound)); PrecacheScriptSound(STRING(m_sRotateSound)); PrecacheScriptSound(STRING(m_sReloadSound)); } //--------------------------------------------------------- // Purpose: Spawns the npc //--------------------------------------------------------- void CNPC_MissileDefense::Spawn(void) { Precache(); //Gets gib model defined by the mapper char *szModel = (char *)STRING(m_sTurretModel); if (!GetModel()) // BriJee: No custom model? Use default { SetModel("models/missile_defense.mdl"); } else SetModel(szModel); UTIL_SetSize(this, Vector(-36, -36, 0), Vector(36, 36, 64)); SetSolid(SOLID_BBOX); SetMoveType(MOVETYPE_NONE); m_takedamage = DAMAGE_YES; SetBloodColor(DONT_BLEED); //Gets health defined by the mapper, if not set, get the cvar one if (!m_nHealth) { m_iHealth = sk_missiledefense_health.GetFloat(); } else { m_iHealth = m_nHealth; } m_flFieldOfView = VIEW_FIELD_FULL; //0.4f m_NPCState = NPC_STATE_ALERT; CapabilitiesClear(); CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK1); // Hate missiles AddClassRelationship(CLASS_MISSILE, D_HT, 5); m_spawnflags |= SF_NPC_LONG_RANGE; m_flReloadedTime = gpGlobals->curtime; InitBoneControllers(); NPCInit(); SetBoneController(MD_BC_YAW, 0); //10 SetBoneController(MD_BC_PITCH, 0); SetBodygroup(1, 0); SetBodygroup(2, 0); SetBodygroup(3, 0); SetBodygroup(4, 0); } //------------------------------------------------------------------------------ // Purpose : Main AI work // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MissileDefense::RunAI(void) { //If Starts On option isn't activated, disable AI if (m_nStartOn == 0) { SetThink(NULL); } // If my enemy is dead clear the memory and reset m_hEnemy if ((GetEnemy() != NULL) && (GetEnemy()->IsAlive() == false)) { ClearEnemyMemory(); SetEnemy(NULL); } if (GetEnemy() == NULL) { // We have to refresh our memories before finding enemies, so // dead enemies are cleared out before new ones are added. GetEnemies()->RefreshMemories(); GetSenses()->Look(4092); SetEnemy(BestEnemy()); if (GetEnemy() != NULL) { m_iAmmoLoaded = MD_FULLAMMO; m_flReloadedTime = gpGlobals->curtime; } } if (m_iAmmoLoaded < 1 && gpGlobals->curtime > m_flReloadedTime) { m_iAmmoLoaded = MD_FULLAMMO; } AimGun(); FireCannons(); SetNextThink(gpGlobals->curtime + 0.05); } //--------------------------------------------------------- // Purpose: Starts aiming the gun //--------------------------------------------------------- void CNPC_MissileDefense::GetGunAim(Vector *vecAim) { Vector vecPos; QAngle vecAng; GetAttachment(MD_AP_LGUN, vecPos, vecAng); vecAng.x = GetLocalAngles().x + GetBoneController(MD_BC_PITCH); vecAng.z = 0; vecAng.y = GetLocalAngles().y + GetBoneController(MD_BC_YAW); Vector vecForward; AngleVectors(vecAng, &vecForward); *vecAim = vecForward; } #define NOISE 0.035f #define MD_ATTN_CANNON 0.4 //------------------------------------------------------------------------------ // Purpose : Open fire on the rocket // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MissileDefense::FireCannons(void) { // ---------------------------------------------- // Make sure I have an enemy // ---------------------------------------------- if (GetEnemy() == NULL) { return; } // ---------------------------------------------- // Make sure I have ammo // ---------------------------------------------- if (m_iAmmoLoaded < 1) { return; } // ---------------------------------------------- // Make sure gun it pointing in right direction // // Disabled, so it makes work fine the turret // ---------------------------------------------- /*Vector vGunDir; GetGunAim( &vGunDir ); Vector vTargetPos; EnemyShootPosition(GetEnemy(),&vTargetPos); Vector vTargetDir = vTargetPos - GetAbsOrigin(); VectorNormalize( vTargetDir ); float fDotPr = DotProduct( vGunDir, vTargetDir ); if (fDotPr < 0.95) { return; }*/ // ----------------------------------------------------------------------------------------- // Check line of sight // // For some unknown reason, with two or more turrets it makes them to do not fire. Disabled. // ----------------------------------------------------------------------------------------- /*trace_t tr; AI_TraceLine( GetEnemy()->EyePosition(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); if (tr.fraction < 1.0) { return; } */ Vector vecRight; Vector vecDir; Vector vecCenter; AngleVectors(GetLocalAngles(), NULL, &vecRight, NULL); vecCenter = WorldSpaceCenter(); if (GetEnemy() == NULL) { return; } bool fSound = false; if (random->RandomInt(0, 3) == 0) { fSound = true; } //Plays the fire sound char *sFireSound = (char *)STRING(m_sFireSound); EmitSound(sFireSound); DoMuzzleFlash(); Vector vecGun; QAngle vecAng; GetAttachment(MD_AP_LGUN, vecGun, vecAng); Vector vecTarget; EnemyShootPosition(GetEnemy(), &vecTarget); vecDir = vecTarget - vecCenter; VectorNormalize(vecDir); vecDir.x += random->RandomFloat(-NOISE, NOISE); vecDir.y += random->RandomFloat(-NOISE, NOISE); Vector vecStart = vecGun + vecDir * 110; Vector vecEnd = vecGun + vecDir * 4096; UTIL_Tracer(vecStart, vecEnd, 0, TRACER_DONT_USE_ATTACHMENT, 3000 + random->RandomFloat(0, 2000), fSound); vecDir = vecTarget - vecCenter; VectorNormalize(vecDir); vecDir.x += random->RandomFloat(-NOISE, NOISE); vecDir.y += random->RandomFloat(-NOISE, NOISE); vecDir.z += random->RandomFloat(-NOISE, NOISE); GetAttachment(MD_AP_RGUN, vecGun, vecAng); vecStart = vecGun + vecDir * 110; vecEnd = vecGun + vecDir * 4096; UTIL_Tracer(vecStart, vecEnd, 0, TRACER_DONT_USE_ATTACHMENT, 3000 + random->RandomFloat(0, 2000)); m_iAmmoLoaded -= 2; if (m_iAmmoLoaded < 1) { // Incite a reload. char *sReloadSound = (char *)STRING(m_sReloadSound); EmitSound(sReloadSound); m_flReloadedTime = gpGlobals->curtime + 0.3; return; } // Do damage to the missile based on distance. // if < 1, make damage 0. float flDist = (GetEnemy()->GetLocalOrigin() - vecGun).Length(); float flDamage; flDamage = 4000 - flDist; flDamage /= 1000.0; if (flDamage > 0) { if (flDist <= 1500) { flDamage *= 2; } CTakeDamageInfo info(this, this, flDamage, DMG_MISSILEDEFENSE); CalculateBulletDamageForce(&info, GetAmmoDef()->Index("SMG1"), vecDir, GetEnemy()->GetAbsOrigin()); GetEnemy()->TakeDamage(info); } } //------------------------------------------------------------------------------ // Purpose : Takes damage only from blasts and bullets // Input : // Output : //------------------------------------------------------------------------------ int CNPC_MissileDefense::OnTakeDamage_Alive(const CTakeDamageInfo &info) { // Only take blast damage if (info.GetDamageType() & DMG_BLAST) { return BaseClass::OnTakeDamage_Alive(info); } // // Bullets can damage it if their flag is checked // else if (info.GetDamageType() & DMG_BULLET) { if (HasSpawnFlags(SF_MISSILEDEFENSE_BULLETDMG)) { return BaseClass::OnTakeDamage_Alive(info); } else { return 0; } } else { return 0; } } //------------------------------------------------------------------------------ // Purpose : Stuff to do when gets killed // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MissileDefense::Event_Killed(const CTakeDamageInfo &info) { //Set on fire //Ignite( 60, false ); //Paint me black //Scorch( 8, 50 ); char *sRotateSound = (char *)STRING(m_sRotateSound); StopSound(sRotateSound); CTakeDamageInfo dmgInfo = info; Explode(dmgInfo.GetDamagePosition()); Gib(); } //------------------------------------------------------------------------------ // Purpose : Makes an explosion // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MissileDefense::Explode(const Vector &vecExplosionPos) { //Explodes! ExplosionCreate(vecExplosionPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_NOFIREBALLSMOKE, false, 0); UTIL_ScreenShake(vecExplosionPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START); } //------------------------------------------------------------------------------ // Purpose : Gibs and some more effects when death // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MissileDefense::Gib(void) { //Gets gib model defined by the mapper char *szModelGib = (char *)STRING(m_sGibModel); // Sparks for (int i = 0; i < 4; i++) { Vector sparkPos = GetAbsOrigin(); sparkPos.x += random->RandomFloat(-12, 12); sparkPos.y += random->RandomFloat(-12, 12); sparkPos.z += random->RandomFloat(-12, 12); g_pEffects->Sparks(sparkPos); } // Smoke UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10); // Light CBroadcastRecipientFilter filter; te->DynamicLight(filter, 0.0, &GetAbsOrigin(), 255, 180, 100, 0, 100, 0.1, 0); // Remove top parts SetBodygroup(1, 1); SetBodygroup(2, 1); SetBodygroup(3, 1); SetBodygroup(4, 1); m_takedamage = 0; SetThink(NULL); // Throw manhackgibs CGib::SpawnSpecificGibs(this, MD_GIB_COUNT, 300, 500, szModelGib); } //------------------------------------------------------------------------------ // Purpose : Add a little prediction into my enemy aim position // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MissileDefense::EnemyShootPosition(CBaseEntity* pEnemy, Vector *vPosition) { // This should never happen, but just in case if (!pEnemy) { return; } *vPosition = pEnemy->GetAbsOrigin(); // Add prediction but prevents us from flipping around as enemy approaches us float flDist = (pEnemy->GetAbsOrigin() - GetAbsOrigin()).Length(); Vector vPredVel = pEnemy->GetSmoothedVelocity() * 0.5; if (flDist > vPredVel.Length()) { *vPosition += vPredVel; } } //------------------------------------------------------------------------------ // Purpose : Moves the turret to aim the rocket // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MissileDefense::AimGun(void) { if (GetEnemy() == NULL) { char *sRotateSound = (char *)STRING(m_sRotateSound); StopSound(sRotateSound); return; } Vector forward, right, up; AngleVectors(GetLocalAngles(), &forward, &right, &up); // Get gun attachment points Vector vBasePos; QAngle vBaseAng; GetAttachment(MD_AP_LGUN, vBasePos, vBaseAng); Vector vTargetPos; EnemyShootPosition(GetEnemy(), &vTargetPos); Vector vTargetDir = vTargetPos - vBasePos; VectorNormalize(vTargetDir); Vector vecOut; vecOut.x = DotProduct(forward, vTargetDir); vecOut.y = -DotProduct(right, vTargetDir); vecOut.z = DotProduct(up, vTargetDir); QAngle angles; VectorAngles(vecOut, angles); //Pitch for the cannons if (angles.x != m_vGunAng.x){ float flDir = m_vGunAng.x > angles.x ? 1 : -1; angles.x += 0.1 * MD_PITCH_SPEED * flDir; SetBoneController(1, angles.x - m_vGunAng.x); } //Yaw for the cannons if (angles.y != m_vGunAng.y){ float flDir = m_vGunAng.y > angles.y ? 1 : -1; float flDist = fabs(m_vGunAng.y - angles.y); if (flDist > 180){ flDist = 360 - flDist; flDir = -flDir; } angles.y += 0.1 * MD_YAW_SPEED * flDir; if (angles.y < 0) angles.y += 360; else if (angles.y >= 360) angles.y -= 360; if (flDist < (0.05 * MD_YAW_SPEED)) angles.y = m_vGunAng.y; SetBoneController(0, angles.y - m_vGunAng.y); } //Plays movement sound if (angles.x != m_vGunAng.x || angles.y != m_vGunAng.y) { //EmitSound( "NPC_FloorTurret.Alarm" ); char *sRotateSound = (char *)STRING(m_sRotateSound); EmitSound(sRotateSound); } else { //StopSound( "NPC_FloorTurret.Alarm" ); char *sRotateSound = (char *)STRING(m_sRotateSound); StopSound(sRotateSound); } } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ CNPC_MissileDefense::~CNPC_MissileDefense(void) { char *sRotateSound = (char *)STRING(m_sRotateSound); StopSound(sRotateSound); } //----------------------------------------------------------------------------- // Purpose: Puts a muzzleflash on the cannons //----------------------------------------------------------------------------- void CNPC_MissileDefense::DoMuzzleFlash(void) { CEffectData data, data2; data.m_nEntIndex = entindex(); data2.m_nEntIndex = entindex(); data.m_nAttachmentIndex = LookupAttachment("R_Gun_AtchPnt"); data2.m_nAttachmentIndex = LookupAttachment("L_Gun_AtchPnt"); data.m_flScale = 1.0f; data2.m_flScale = 1.0f; DispatchEffect("ChopperMuzzleFlash", data); DispatchEffect("ChopperMuzzleFlash", data2); }