//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Bullseyes act as targets for other NPC's to attack and to trigger // events // // $Workfile: $ // $Date: $ // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "beam_shared.h" #include "Sprite.h" #include "ai_default.h" #include "ai_task.h" #include "ai_schedule.h" #include "ai_node.h" #include "ai_hull.h" #include "ai_hint.h" #include "ai_memory.h" #include "ai_route.h" #include "ai_motor.h" #include "hl1_npc_gargantua.h" #include "soundent.h" #include "game.h" #include "npcevent.h" #include "entitylist.h" #include "activitylist.h" #include "animation.h" #include "basecombatweapon.h" #include "IEffects.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "ammodef.h" #include "shake.h" #include "decals.h" #include "particle_smokegrenade.h" #include "gib.h" #include "func_break.h" #include "hl1_shareddefs.h" extern short g_sModelIndexFireball; int gGargGibModel; //========================================================= // Gargantua Monster //========================================================= #define GARG_ATTACKDIST 120.0f // Garg animation events #define GARG_AE_SLASH_LEFT 1 //#define GARG_AE_BEAM_ATTACK_RIGHT 2 // No longer used #define GARG_AE_LEFT_FOOT 3 #define GARG_AE_RIGHT_FOOT 4 #define GARG_AE_STOMP 5 #define GARG_AE_BREATHE 6 // Gargantua is immune to any damage but this #define GARG_DAMAGE ( DMG_ENERGYBEAM | DMG_CRUSH | DMG_MISSILEDEFENSE | DMG_BLAST ) #define GARG_EYE_SPRITE_NAME "sprites/gargeye1.vmt" #define GARG_BEAM_SPRITE_NAME "sprites/xbeam3.vmt" #define GARG_BEAM_SPRITE2 "sprites/xbeam3.vmt" #define GARG_STOMP_SPRITE_NAME "sprites/gargeye1.vmt" #define GARG_FLAME_LENGTH 330 #define GARG_GIB_MODEL "models/metalplategibs.mdl" #define STOMP_SPRITE_COUNT 10 #define ATTACH_EYE 1 ConVar sk_gargantua_health ( "sk_gargantua_health", "800" ); ConVar sk_gargantua_dmg_slash( "sk_gargantua_dmg_slash", "10" ); ConVar sk_gargantua_dmg_fire ( "sk_gargantua_dmg_fire", "3" ); ConVar sk_gargantua_dmg_stomp( "sk_gargantua_dmg_stomp", "50" ); enum { TASK_SOUND_ATTACK = LAST_SHARED_TASK, TASK_FLAME_SWEEP, }; enum { SCHED_GARG_FLAME = LAST_SHARED_SCHEDULE, SCHED_GARG_SWIPE, SCHED_GARG_CHASE_ENEMY, SCHED_GARG_CHASE_ENEMY_FAILED, }; LINK_ENTITY_TO_CLASS( monster_gargantua, CNPC_Gargantua ); BEGIN_DATADESC( CNPC_Gargantua ) DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ), DEFINE_FIELD( m_eyeBrightness, FIELD_INTEGER ), DEFINE_FIELD( m_seeTime, FIELD_TIME ), DEFINE_FIELD( m_flameTime, FIELD_TIME ), DEFINE_FIELD( m_streakTime, FIELD_TIME ), DEFINE_ARRAY( m_pFlame, FIELD_CLASSPTR, 4 ), DEFINE_FIELD( m_flameX, FIELD_FLOAT ), DEFINE_FIELD( m_flameY, FIELD_FLOAT ), DEFINE_FIELD( m_flDmgTime, FIELD_TIME ), DEFINE_FIELD( m_painSoundTime, FIELD_TIME ), END_DATADESC() static void MoveToGround( Vector *position, CBaseEntity *ignore, const Vector &mins, const Vector &maxs ) { trace_t tr; // Find point on floor where enemy would stand at chasePosition Vector floor = *position; floor.z -= 1024; UTIL_TraceHull( *position, floor, mins, maxs, MASK_NPCSOLID, ignore, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction < 1 ) { position->z = tr.endpos.z; } } class CStomp : public CBaseEntity { DECLARE_CLASS( CStomp, CBaseEntity ); public: DECLARE_DATADESC(); virtual void Precache(); void Spawn( void ); void Think( void ); static CStomp *StompCreate( Vector &origin, Vector &end, float speed, CBaseEntity* pOwner ); private: Vector m_vecMoveDir; float m_flScale; float m_flSpeed; unsigned int m_uiFramerate; float m_flDmgTime; CBaseEntity* m_pOwner; // UNDONE: re-use this sprite list instead of creating new ones all the time // CSprite *m_pSprites[ STOMP_SPRITE_COUNT ]; }; BEGIN_DATADESC(CStomp) DEFINE_FIELD( m_vecMoveDir, FIELD_VECTOR ), DEFINE_FIELD( m_flScale, FIELD_FLOAT ), DEFINE_FIELD( m_flSpeed, FIELD_FLOAT ), DEFINE_FIELD( m_uiFramerate, FIELD_INTEGER ), DEFINE_FIELD( m_flDmgTime, FIELD_TIME ), DEFINE_FIELD( m_pOwner, FIELD_CLASSPTR ), END_DATADESC() LINK_ENTITY_TO_CLASS( garg_stomp, CStomp ); CStomp *CStomp::StompCreate( Vector &origin, Vector &end, float speed, CBaseEntity* pOwner ) { CStomp *pStomp = (CStomp*)CreateEntityByName( "garg_stomp" ); pStomp->SetAbsOrigin( origin ); Vector dir = (end - origin); // pStomp->m_flScale = dir.Length(); pStomp->m_flScale = 2048; pStomp->m_vecMoveDir = dir; VectorNormalize( pStomp->m_vecMoveDir ); pStomp->m_flSpeed = speed; pStomp->m_pOwner = pOwner; pStomp->Spawn(); return pStomp; } void CStomp::Precache() { BaseClass::Precache(); PrecacheScriptSound( "Garg.Stomp" ); PrecacheModel( GARG_STOMP_SPRITE_NAME ); } void CStomp::Spawn( void ) { Precache(); SetNextThink( gpGlobals->curtime ); SetClassname( "garg_stomp" ); m_flDmgTime = gpGlobals->curtime; m_uiFramerate = 30; // pev->rendermode = kRenderTransTexture; // SetBrightness( 0 ); CPASAttenuationFilter filter( this ); EmitSound( filter, entindex(), "Garg.Stomp" ); } #define STOMP_INTERVAL 0.025 void CStomp::Think( void ) { trace_t tr; SetNextThink( gpGlobals->curtime + 0.1 ); // Do damage for this frame Vector vecStart = GetAbsOrigin(); vecStart.z += 30; Vector vecEnd = vecStart + (m_vecMoveDir * m_flSpeed * gpGlobals->frametime); UTIL_TraceHull( vecStart, vecEnd, Vector(-32, -32, -32), Vector(32, 32, 32), MASK_SOLID, m_pOwner, COLLISION_GROUP_NONE, &tr ); // NDebugOverlay::Line( vecStart, vecEnd, 0, 255, 0, false, 10.0f ); if ( tr.m_pEnt ) { CBaseEntity *pEntity = tr.m_pEnt; CTakeDamageInfo info( this, this, 50, DMG_SONIC ); CalculateMeleeDamageForce( &info, m_vecMoveDir, tr.endpos ); pEntity->TakeDamage( info ); } // Accelerate the effect m_flSpeed += (gpGlobals->frametime) * m_uiFramerate; m_uiFramerate += (gpGlobals->frametime) * 2000; // Move and spawn trails if ( gpGlobals->curtime - m_flDmgTime > 0.2f ) { m_flDmgTime = gpGlobals->curtime - 0.2f; } while ( gpGlobals->curtime - m_flDmgTime > STOMP_INTERVAL ) { SetAbsOrigin( GetAbsOrigin() + m_vecMoveDir * m_flSpeed * STOMP_INTERVAL ); for ( int i = 0; i < 2; i++ ) { CSprite *pSprite = CSprite::SpriteCreate( GARG_STOMP_SPRITE_NAME, GetAbsOrigin(), TRUE ); if ( pSprite ) { UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector(0,0,500), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); pSprite->SetAbsOrigin( tr.endpos ); // pSprite->pev->velocity = Vector(RandomFloat(-200,200),RandomFloat(-200,200),175); pSprite->SetNextThink( gpGlobals->curtime + 0.3 ); pSprite->SetThink( &CSprite::SUB_Remove ); pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxFadeFast ); } g_pEffects->EnergySplash( tr.endpos, tr.plane.normal ); } m_flDmgTime += STOMP_INTERVAL; // Scale has the "life" of this effect m_flScale -= STOMP_INTERVAL * m_flSpeed; if ( m_flScale <= 0 ) { // Life has run out UTIL_Remove(this); CPASAttenuationFilter filter( this ); StopSound( entindex(), CHAN_STATIC, "Garg.Stomp" ); } } } //========================================================================= // Gargantua //========================================================================= //========================================================= // Spawn //========================================================= void CNPC_Gargantua::Spawn() { Precache( ); SetModel( "models/garg.mdl" ); SetNavType(NAV_GROUND); SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetMoveType( MOVETYPE_STEP ); Vector vecSurroundingMins( -80, -80, 0 ); Vector vecSurroundingMaxs( 80, 80, 214 ); CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs ); m_bloodColor = BLOOD_COLOR_GREEN; m_iHealth = sk_gargantua_health.GetFloat(); SetViewOffset( Vector ( 0, 0, 96 ) );// taken from mdl file m_flFieldOfView = -0.2;// width of forward view cone ( as a dotproduct result ) m_NPCState = NPC_STATE_NONE; CapabilitiesAdd( bits_CAP_MOVE_GROUND ); CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 ); SetHullType( HULL_LARGE ); SetHullSizeNormal(); m_pEyeGlow = CSprite::SpriteCreate( GARG_EYE_SPRITE_NAME, GetAbsOrigin(), FALSE ); m_pEyeGlow->SetTransparency( kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation ); m_pEyeGlow->SetAttachment( this, 1 ); EyeOff(); m_seeTime = gpGlobals->curtime + 5; m_flameTime = gpGlobals->curtime + 2; NPCInit(); BaseClass::Spawn(); // Give garg a healthy free knowledge. GetEnemies()->SetFreeKnowledgeDuration( 59.0f ); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CNPC_Gargantua::Precache() { PrecacheModel("models/garg.mdl"); PrecacheModel( GARG_EYE_SPRITE_NAME ); PrecacheModel( GARG_BEAM_SPRITE_NAME ); PrecacheModel( GARG_BEAM_SPRITE2 ); //gStompSprite = PRECACHE_MODEL( GARG_STOMP_SPRITE_NAME ); gGargGibModel = PrecacheModel( GARG_GIB_MODEL ); PrecacheScriptSound( "Garg.AttackHit" ); PrecacheScriptSound( "Garg.AttackMiss" ); PrecacheScriptSound( "Garg.Footstep" ); PrecacheScriptSound( "Garg.Breath" ); PrecacheScriptSound( "Garg.Attack" ); PrecacheScriptSound( "Garg.Pain" ); PrecacheScriptSound( "Garg.BeamAttackOn" ); PrecacheScriptSound( "Garg.BeamAttackRun" ); PrecacheScriptSound( "Garg.BeamAttackOff" ); PrecacheScriptSound( "Garg.StompSound" ); } Class_T CNPC_Gargantua::Classify ( void ) { return CLASS_ALIEN_MONSTER; } void CNPC_Gargantua::PrescheduleThink( void ) { if ( !HasCondition( COND_SEE_ENEMY ) ) { m_seeTime = gpGlobals->curtime + 5; EyeOff(); } else { EyeOn( 200 ); } EyeUpdate(); } float CNPC_Gargantua::MaxYawSpeed ( void ) { float ys = 60; switch ( GetActivity() ) { case ACT_IDLE: ys = 60; break; case ACT_TURN_LEFT: case ACT_TURN_RIGHT: ys = 180; break; case ACT_WALK: case ACT_RUN: ys = 60; break; default: ys = 60; break; } return ys; } int CNPC_Gargantua::MeleeAttack1Conditions( float flDot, float flDist ) { if (flDot >= 0.7) { if ( flDist <= GARG_ATTACKDIST ) { return COND_CAN_MELEE_ATTACK1; } } return COND_NONE; } // Flame thrower madness! int CNPC_Gargantua::MeleeAttack2Conditions( float flDot, float flDist ) { if ( gpGlobals->curtime > m_flameTime ) { if ( flDot >= 0.8 ) { if ( flDist > GARG_ATTACKDIST ) { if ( flDist <= GARG_FLAME_LENGTH ) return COND_CAN_MELEE_ATTACK2; } } } return COND_NONE; } //========================================================= // CheckRangeAttack1 // flDot is the cos of the angle of the cone within which // the attack can occur. //========================================================= // // Stomp attack // //========================================================= int CNPC_Gargantua::RangeAttack1Conditions( float flDot, float flDist ) { if ( gpGlobals->curtime > m_seeTime ) { if ( flDot >= 0.7 ) { if ( flDist > GARG_ATTACKDIST ) { return COND_CAN_RANGE_ATTACK1; } } } return COND_NONE; } //========================================================= // CheckTraceHullAttack - expects a length to trace, amount // of damage to do, and damage type. Returns a pointer to // the damaged entity in case the monster wishes to do // other stuff to the victim (punchangle, etc) // Used for many contact-range melee attacks. Bites, claws, etc. // Overridden for Gargantua because his swing starts lower as // a percentage of his height (otherwise he swings over the // players head) //========================================================= CBaseEntity* CNPC_Gargantua::GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType) { trace_t tr; Vector vForward, vUp; AngleVectors( GetAbsAngles(), &vForward, NULL, &vUp ); Vector vecStart = GetAbsOrigin(); vecStart.z += 64; Vector vecEnd = vecStart + ( vForward * flDist) - ( vUp * flDist * 0.3); //UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); UTIL_TraceEntity( this, GetAbsOrigin(), vecEnd, MASK_SOLID, &tr ); if ( tr.m_pEnt ) { CBaseEntity *pEntity = tr.m_pEnt; if ( iDamage > 0 ) { CTakeDamageInfo info( this, this, iDamage, iDmgType ); CalculateMeleeDamageForce( &info, vForward, tr.endpos ); pEntity->TakeDamage( info ); } return pEntity; } return NULL; } void CNPC_Gargantua::HandleAnimEvent( animevent_t *pEvent ) { CPASAttenuationFilter filter( this ); switch( pEvent->event ) { case GARG_AE_SLASH_LEFT: { // HACKHACK!!! CBaseEntity *pHurt = GargantuaCheckTraceHullAttack( GARG_ATTACKDIST + 10.0, sk_gargantua_dmg_slash.GetFloat(), DMG_SLASH ); if (pHurt) { if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) ) { pHurt->ViewPunch( QAngle( -30, -30, 30 ) ); Vector vRight; AngleVectors( GetAbsAngles(), NULL, &vRight, NULL ); pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - vRight * 100 ); } EmitSound( filter, entindex(), "Garg.AttackHit" ); } else // Play a random attack miss sound { EmitSound( filter, entindex(),"Garg.AttackMiss" ); } } break; case GARG_AE_RIGHT_FOOT: case GARG_AE_LEFT_FOOT: UTIL_ScreenShake( GetAbsOrigin(), 4.0, 3.0, 1.0, 1500, SHAKE_START ); EmitSound( filter, entindex(), "Garg.Footstep" ); break; case GARG_AE_STOMP: StompAttack(); m_seeTime = gpGlobals->curtime + 12; break; case GARG_AE_BREATHE: EmitSound( filter, entindex(), "Garg.Breath" ); break; default: BaseClass::HandleAnimEvent(pEvent); break; } } int CNPC_Gargantua::TranslateSchedule( int scheduleType ) { //TEMP TEMP if ( FlameIsOn() ) FlameDestroy(); switch( scheduleType ) { case SCHED_MELEE_ATTACK2: return SCHED_GARG_FLAME; case SCHED_MELEE_ATTACK1: return SCHED_GARG_SWIPE; case SCHED_CHASE_ENEMY: return SCHED_GARG_CHASE_ENEMY; case SCHED_CHASE_ENEMY_FAILED: return SCHED_GARG_CHASE_ENEMY_FAILED; case SCHED_ALERT_STAND: return SCHED_CHASE_ENEMY; break; } return BaseClass::TranslateSchedule( scheduleType ); } void CNPC_Gargantua::StartTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_FLAME_SWEEP: //TEMP TEMP FlameCreate(); m_flWaitFinished = gpGlobals->curtime + pTask->flTaskData; m_flameTime = gpGlobals->curtime + 6; m_flameX = 0; m_flameY = 0; break; case TASK_SOUND_ATTACK: if ( random->RandomInt(0,100) < 30 ) { CPASAttenuationFilter filter( this ); EmitSound( filter, entindex(), "Garg.Attack" ); } TaskComplete(); break; case TASK_DIE: m_flWaitFinished = gpGlobals->curtime + 1.6; DeathEffect(); // FALL THROUGH default: BaseClass::StartTask( pTask ); break; } } bool CNPC_Gargantua::ShouldGib( const CTakeDamageInfo &info ) { return false; } //========================================================= // RunTask //========================================================= void CNPC_Gargantua::RunTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_DIE: if ( gpGlobals->curtime > m_flWaitFinished ) { //TEMP TEMP m_nRenderFX = kRenderFxExplode; SetRenderColor( 255, 0, 0 , 255 ); StopAnimation(); SetNextThink( gpGlobals->curtime + 0.15 ); SetThink( &CBaseEntity::SUB_Remove ); int i; int parts = modelinfo->GetModelFrameCount( modelinfo->GetModel( gGargGibModel ) ); for ( i = 0; i < 10; i++ ) { CGib *pGib = CREATE_ENTITY( CGib, "gib" ); pGib->Spawn( GARG_GIB_MODEL); int bodyPart = 0; if ( parts > 1 ) bodyPart = random->RandomInt( 0, parts-1 ); pGib->SetBodygroup( 0, bodyPart ); pGib->SetBloodColor( BLOOD_COLOR_YELLOW ); pGib->m_material = matNone; pGib->SetAbsOrigin( GetAbsOrigin() ); pGib->SetAbsVelocity( UTIL_RandomBloodVector() * random->RandomFloat( 300, 500 ) ); pGib->SetNextThink( gpGlobals->curtime + 1.25 ); pGib->SetThink( &CBaseEntity::SUB_FadeOut ); } Vector vecSize = Vector( 200, 200, 128 ); CPVSFilter filter( GetAbsOrigin() ); te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, vecSize, vec3_origin, gGargGibModel, 200, 50, 3.0, BREAK_FLESH ); return; } else BaseClass::RunTask( pTask ); break; case TASK_FLAME_SWEEP: if ( gpGlobals->curtime > m_flWaitFinished ) { //TEMP TEMP FlameDestroy(); TaskComplete(); FlameControls( 0, 0 ); SetBoneController( 0, 0 ); SetBoneController( 1, 0 ); } else { bool cancel = false; QAngle angles = QAngle( 0, 0, 0 ); //TEMP TEMP FlameUpdate(); CBaseEntity *pEnemy = GetEnemy(); if ( pEnemy ) { Vector org = GetAbsOrigin(); org.z += 64; Vector dir = pEnemy->BodyTarget(org) - org; VectorAngles( dir, angles ); angles.x = -angles.x; angles.y -= GetAbsAngles().y; if ( dir.Length() > 400 ) cancel = true; } if ( fabs(angles.y) > 60 ) cancel = true; if ( cancel ) { m_flWaitFinished -= 0.5; m_flameTime -= 0.5; } //TEMP TEMP //FlameControls( angles.x + 2 * sin(gpGlobals->curtime*8), angles.y + 28 * sin(gpGlobals->curtime*8.5) ); FlameControls( angles.x, angles.y ); } break; default: BaseClass::RunTask( pTask ); break; } } void CNPC_Gargantua::FlameCreate( void ) { int i; Vector posGun; QAngle angleGun; trace_t trace; Vector vForward; AngleVectors( GetAbsAngles(), &vForward ); for ( i = 0; i < 4; i++ ) { if ( i < 2 ) m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE_NAME, 24.0 ); else m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE2, 14.0 ); if ( m_pFlame[i] ) { int attach = i%2; // attachment is 0 based in GetAttachment GetAttachment( attach+1, posGun, angleGun ); Vector vecEnd = ( vForward * GARG_FLAME_LENGTH) + posGun; //UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &trace ); UTIL_TraceLine ( posGun, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace); // NDebugOverlay::Line( posGun, vecEnd, 255, 255, 255, false, 10.0f ); m_pFlame[i]->PointEntInit( trace.endpos, this ); if ( i < 2 ) m_pFlame[i]->SetColor( 255, 130, 90 ); else m_pFlame[i]->SetColor( 0, 120, 255 ); m_pFlame[i]->SetBrightness( 190 ); m_pFlame[i]->SetBeamFlags( FBEAM_SHADEIN ); m_pFlame[i]->SetScrollRate( 20 ); // attachment is 1 based in SetEndAttachment m_pFlame[i]->SetEndAttachment( attach + 2 ); CSoundEnt::InsertSound( SOUND_COMBAT, posGun, 384, 0.3 ); } } CPASAttenuationFilter filter4( this ); EmitSound( filter4, entindex(), "Garg.BeamAttackOn" ); EmitSound( filter4, entindex(), "Garg.BeamAttackRun" ); } void CNPC_Gargantua::FlameControls( float angleX, float angleY ) { if ( angleY < -180 ) angleY += 360; else if ( angleY > 180 ) angleY -= 360; if ( angleY < -45 ) angleY = -45; else if ( angleY > 45 ) angleY = 45; m_flameX = UTIL_ApproachAngle( angleX, m_flameX, 4 ); m_flameY = UTIL_ApproachAngle( angleY, m_flameY, 8 ); SetBoneController( 0, m_flameY ); SetBoneController( 1, m_flameX ); } void CNPC_Gargantua::FlameUpdate( void ) { int i; static float offset[2] = { 60, -60 }; trace_t trace; Vector vecStart; QAngle angleGun; BOOL streaks = FALSE; Vector vForward; for ( i = 0; i < 2; i++ ) { if ( m_pFlame[i] ) { QAngle vecAim = GetAbsAngles(); vecAim.x += -m_flameX; vecAim.y += m_flameY; AngleVectors( vecAim, &vForward ); GetAttachment( i + 2, vecStart, angleGun ); Vector vecEnd = vecStart + ( vForward * GARG_FLAME_LENGTH); // - offset[i] * gpGlobals->v_right; UTIL_TraceLine ( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace); m_pFlame[i]->SetStartPos( trace.endpos ); m_pFlame[i+2]->SetStartPos( (vecStart * 0.6) + (trace.endpos * 0.4) ); if ( trace.fraction != 1.0 && gpGlobals->curtime > m_streakTime ) { g_pEffects->Sparks( trace.endpos, 1, 1, &trace.plane.normal ); streaks = TRUE; UTIL_DecalTrace( &trace, "SmallScorch" ); } // RadiusDamage( trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN ); FlameDamage( vecStart, trace.endpos, this, this, sk_gargantua_dmg_fire.GetFloat(), CLASS_ALIEN_MONSTER, DMG_BURN ); CBroadcastRecipientFilter filter; GetAttachment(i + 2, vecStart, angleGun); te->DynamicLight( filter, 0.0, &vecStart, 255, 0, 0, 0, 48, 0.2, 150 ); } } if ( streaks ) m_streakTime = gpGlobals->curtime; } void CNPC_Gargantua::FlameDamage( Vector vecStart, Vector vecEnd, CBaseEntity *pevInflictor, CBaseEntity *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { CBaseEntity *pEntity = NULL; trace_t tr; float flAdjustedDamage; Vector vecSpot; Vector vecMid = (vecStart + vecEnd) * 0.5; // float searchRadius = (vecStart - vecMid).Length(); float searchRadius = GARG_FLAME_LENGTH; float maxDamageRadius = searchRadius / 2.0; Vector vecAim = (vecEnd - vecStart); VectorNormalize( vecAim ); // iterate on all entities in the vicinity. while ((pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius )) != NULL) { if ( pEntity->m_takedamage != DAMAGE_NO ) { // UNDONE: this should check a damage mask, not an ignore if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) {// houndeyes don't hurt other houndeyes with their attack continue; } vecSpot = pEntity->BodyTarget( vecMid ); float dist = DotProduct( vecAim, vecSpot - vecMid ); if (dist > searchRadius) dist = searchRadius; else if (dist < -searchRadius) dist = searchRadius; Vector vecSrc = vecMid + dist * vecAim; UTIL_TraceLine ( vecStart, vecSpot, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); // NDebugOverlay::Line( vecStart, vecSpot, 0, 255, 0, false, 10.0f ); if ( tr.fraction == 1.0 || tr.m_pEnt == pEntity ) {// the explosion can 'see' this entity, so hurt them! // decrease damage for an ent that's farther from the flame. dist = ( vecSrc - tr.endpos ).Length(); if (dist > maxDamageRadius) { flAdjustedDamage = flDamage - (dist - maxDamageRadius) * 0.4; if (flAdjustedDamage <= 0) continue; } else { flAdjustedDamage = flDamage; } // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); if (tr.fraction != 1.0) { ClearMultiDamage( ); Vector vDir = (tr.endpos - vecSrc); VectorNormalize( vDir ); CTakeDamageInfo info( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); CalculateMeleeDamageForce( &info, vDir, tr.endpos ); pEntity->DispatchTraceAttack( info, vDir, &tr ); ApplyMultiDamage(); } else { pEntity->TakeDamage( CTakeDamageInfo( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ) ); } } } } } void CNPC_Gargantua::FlameDestroy( void ) { int i; CPASAttenuationFilter filter4( this ); EmitSound( filter4, entindex(), "Garg.BeamAttackOff" ); for ( i = 0; i < 4; i++ ) { if ( m_pFlame[i] ) { UTIL_Remove( m_pFlame[i] ); m_pFlame[i] = NULL; } } } void CNPC_Gargantua::EyeOn( int level ) { m_eyeBrightness = level; } void CNPC_Gargantua::EyeOff( void ) { m_eyeBrightness = 0; } void CNPC_Gargantua::EyeUpdate( void ) { if ( m_pEyeGlow ) { m_pEyeGlow->SetBrightness( UTIL_Approach( m_eyeBrightness, m_pEyeGlow->GetBrightness(), 26 ), 0.5f ); if ( m_pEyeGlow->GetBrightness() == 0 ) { m_pEyeGlow->AddEffects( EF_NODRAW ); } else { m_pEyeGlow->RemoveEffects( EF_NODRAW ); } } } void CNPC_Gargantua::StompAttack( void ) { trace_t trace; Vector vecForward; AngleVectors(GetAbsAngles(), &vecForward ); Vector vecStart = GetAbsOrigin() + Vector(0,0,60) + 35 * vecForward; CBaseEntity* pPlayer = GetEnemy(); if ( !pPlayer ) return; Vector vecAim = pPlayer->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize( vecAim ); Vector vecEnd = (vecAim * 1024) + vecStart; UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); // NDebugOverlay::Line( vecStart, vecEnd, 255, 0, 0, false, 10.0f ); CStomp::StompCreate( vecStart, trace.endpos, 0, this ); UTIL_ScreenShake( GetAbsOrigin(), 12.0, 100.0, 2.0, 1000, SHAKE_START ); CPASAttenuationFilter filter( this ); EmitSound( filter, entindex(), "Garg.StompSound" ); UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector(0,0,20), MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); if ( trace.fraction < 1.0 ) { UTIL_DecalTrace( &trace, "SmallScorch" ); } } void CNPC_Gargantua::DeathEffect( void ) { int i; Vector vForward; AngleVectors( GetAbsAngles(), &vForward ); Vector deathPos = GetAbsOrigin() + vForward * 100; Vector position = GetAbsOrigin(); position.z += 32; CPASFilter filter( GetAbsOrigin() ); for ( i = 0; i < 7; i++) { te->Explosion( filter, i * 0.2, &GetAbsOrigin(), g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 ); position.z += 15; } UTIL_Smoke(GetAbsOrigin(),random->RandomInt(10, 15), 10); UTIL_ScreenShake( GetAbsOrigin(), 25.0, 100.0, 5.0, 1000, SHAKE_START ); } void CNPC_Gargantua::Event_Killed( const CTakeDamageInfo &info ) { EyeOff(); UTIL_Remove( m_pEyeGlow ); m_pEyeGlow = NULL; BaseClass::Event_Killed( info ); m_takedamage = DAMAGE_NO; } void CNPC_Gargantua::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { CTakeDamageInfo subInfo = info; if ( !IsAlive() ) { BaseClass::TraceAttack( subInfo, vecDir, ptr, pAccumulator ); return; } // UNDONE: Hit group specific damage? if ( subInfo.GetDamageType() & ( GARG_DAMAGE | DMG_BLAST ) ) { if ( m_painSoundTime < gpGlobals->curtime ) { CPASAttenuationFilter filter( this ); EmitSound( filter, entindex(), "Garg.Pain" ); m_painSoundTime = gpGlobals->curtime + random->RandomFloat( 2.5, 4 ); } } int bitsDamageType = subInfo.GetDamageType(); bitsDamageType &= GARG_DAMAGE; subInfo.SetDamageType( bitsDamageType ); if ( subInfo.GetDamageType() == 0 ) { if ( m_flDmgTime != gpGlobals->curtime || (random->RandomInt( 0, 100 ) < 20) ) { g_pEffects->Ricochet(ptr->endpos, -vecDir ); m_flDmgTime = gpGlobals->curtime; } subInfo.SetDamage( 0 ); } BaseClass::TraceAttack( subInfo, vecDir, ptr, pAccumulator ); } int CNPC_Gargantua::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { if( GetState() == NPC_STATE_SCRIPT ) { // Invulnerable while scripted. This fixes the problem where garg wouldn't // explode in C2A1 because for some reason he wouldn't die while scripted, he'd // only freeze in place. Now he's just immune until he gets to the script and stops. return 0; } CTakeDamageInfo subInfo = info; float flDamage = subInfo.GetDamage(); if ( IsAlive() ) { if ( !(subInfo.GetDamageType() & GARG_DAMAGE) ) { flDamage *= 0.01; subInfo.SetDamage( flDamage ); } if ( subInfo.GetDamageType() & DMG_BLAST ) { SetCondition( COND_LIGHT_DAMAGE ); } } return BaseClass::OnTakeDamage_Alive( subInfo ); } AI_BEGIN_CUSTOM_NPC( monster_gargantua, CNPC_Gargantua ) DECLARE_TASK ( TASK_SOUND_ATTACK ) DECLARE_TASK ( TASK_FLAME_SWEEP ) //========================================================= // > SCHED_GARG_FLAME //========================================================= DEFINE_SCHEDULE ( SCHED_GARG_FLAME, " Tasks" " TASK_STOP_MOVING 0" " TASK_FACE_ENEMY 0" " TASK_SOUND_ATTACK 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_MELEE_ATTACK2" " TASK_FLAME_SWEEP 4.5" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" ) //========================================================= // > SCHED_GARG_SWIPE //========================================================= DEFINE_SCHEDULE ( SCHED_GARG_SWIPE, " Tasks" " TASK_STOP_MOVING 0" " TASK_FACE_ENEMY 0" " TASK_MELEE_ATTACK1 0" " " " Interrupts" " COND_CAN_MELEE_ATTACK2" ) DEFINE_SCHEDULE ( SCHED_GARG_CHASE_ENEMY, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GARG_CHASE_ENEMY_FAILED" " TASK_GET_CHASE_PATH_TO_ENEMY 300" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_FACE_ENEMY 0" "" " Interrupts" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_ENEMY_UNREACHABLE" " COND_CAN_RANGE_ATTACK1" " COND_CAN_MELEE_ATTACK1" " COND_CAN_RANGE_ATTACK2" " COND_CAN_MELEE_ATTACK2" " COND_TOO_CLOSE_TO_ATTACK" " COND_LOST_ENEMY" ); DEFINE_SCHEDULE ( SCHED_GARG_CHASE_ENEMY_FAILED, " Tasks" " TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 2 seconds trying to build a path if stuck " TASK_GET_PATH_TO_RANDOM_NODE 180" " TASK_WALK_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" "" " Interrupts" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_CAN_RANGE_ATTACK1" " COND_CAN_MELEE_ATTACK1" " COND_CAN_RANGE_ATTACK2" " COND_CAN_MELEE_ATTACK2" ); AI_END_CUSTOM_NPC()