diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl1/hl1_npc_gargantua.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/hl1/hl1_npc_gargantua.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_gargantua.cpp | 1174 |
1 files changed, 1174 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_gargantua.cpp b/game/server/hl1/hl1_npc_gargantua.cpp new file mode 100644 index 0000000..c5d8b2c --- /dev/null +++ b/game/server/hl1/hl1_npc_gargantua.cpp @@ -0,0 +1,1174 @@ +//========= 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() |