You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1174 lines
28 KiB
1174 lines
28 KiB
//========= 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()
|
|
|