mirror of https://github.com/FWGS/hlsdk-xash3d
1193 lines
30 KiB
C++
1193 lines
30 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
|
*
|
|
* This product contains software technology licensed from Id
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
* This source code contains proprietary and confidential information of
|
|
* Valve LLC and its suppliers. Access to this code is restricted to
|
|
* persons who have executed a written SDK license with Valve. Any access,
|
|
* use or distribution of this code by or to any unlicensed person is illegal.
|
|
*
|
|
****/
|
|
#if !OEM_BUILD && !HLDEMO_BUILD
|
|
|
|
//=========================================================
|
|
// monster template
|
|
//=========================================================
|
|
|
|
#include "extdll.h"
|
|
#include "util.h"
|
|
#include "cbase.h"
|
|
#include "monsters.h"
|
|
#include "schedule.h"
|
|
#include "decals.h"
|
|
#include "weapons.h"
|
|
#include "game.h"
|
|
|
|
#define SF_INFOBM_RUN 0x0001
|
|
#define SF_INFOBM_WAIT 0x0002
|
|
|
|
// AI Nodes for Big Momma
|
|
class CInfoBM : public CPointEntity
|
|
{
|
|
public:
|
|
void Spawn( void );
|
|
void KeyValue( KeyValueData* pkvd );
|
|
|
|
// name in pev->targetname
|
|
// next in pev->target
|
|
// radius in pev->scale
|
|
// health in pev->health
|
|
// Reach target in pev->message
|
|
// Reach delay in pev->speed
|
|
// Reach sequence in pev->netname
|
|
|
|
virtual int Save( CSave &save );
|
|
virtual int Restore( CRestore &restore );
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
|
|
string_t m_preSequence;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( info_bigmomma, CInfoBM )
|
|
|
|
TYPEDESCRIPTION CInfoBM::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD( CInfoBM, m_preSequence, FIELD_STRING ),
|
|
};
|
|
|
|
IMPLEMENT_SAVERESTORE( CInfoBM, CPointEntity )
|
|
|
|
void CInfoBM::Spawn( void )
|
|
{
|
|
}
|
|
|
|
void CInfoBM::KeyValue( KeyValueData* pkvd )
|
|
{
|
|
if( FStrEq( pkvd->szKeyName, "radius" ) )
|
|
{
|
|
pev->scale = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if( FStrEq( pkvd->szKeyName, "reachdelay" ) )
|
|
{
|
|
pev->speed = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if( FStrEq( pkvd->szKeyName, "reachtarget" ) )
|
|
{
|
|
pev->message = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if( FStrEq( pkvd->szKeyName, "reachsequence" ) )
|
|
{
|
|
pev->netname = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if( FStrEq( pkvd->szKeyName, "presequence" ) )
|
|
{
|
|
m_preSequence = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
CPointEntity::KeyValue( pkvd );
|
|
}
|
|
|
|
//=========================================================
|
|
// Mortar shot entity
|
|
//=========================================================
|
|
class CBMortar : public CBaseEntity
|
|
{
|
|
public:
|
|
void Spawn( void );
|
|
|
|
static CBMortar *Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity );
|
|
void Touch( CBaseEntity *pOther );
|
|
void EXPORT Animate( void );
|
|
|
|
virtual int Save( CSave &save );
|
|
virtual int Restore( CRestore &restore );
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
|
|
int m_maxFrame;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( bmortar, CBMortar )
|
|
|
|
TYPEDESCRIPTION CBMortar::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD( CBMortar, m_maxFrame, FIELD_INTEGER ),
|
|
};
|
|
|
|
IMPLEMENT_SAVERESTORE( CBMortar, CBaseEntity )
|
|
|
|
//=========================================================
|
|
// Monster's Anim Events Go Here
|
|
//=========================================================
|
|
#define BIG_AE_STEP1 1 // Footstep left
|
|
#define BIG_AE_STEP2 2 // Footstep right
|
|
#define BIG_AE_STEP3 3 // Footstep back left
|
|
#define BIG_AE_STEP4 4 // Footstep back right
|
|
#define BIG_AE_SACK 5 // Sack slosh
|
|
#define BIG_AE_DEATHSOUND 6 // Death sound
|
|
|
|
#define BIG_AE_MELEE_ATTACKBR 8 // Leg attack
|
|
#define BIG_AE_MELEE_ATTACKBL 9 // Leg attack
|
|
#define BIG_AE_MELEE_ATTACK1 10 // Leg attack
|
|
#define BIG_AE_MORTAR_ATTACK1 11 // Launch a mortar
|
|
#define BIG_AE_LAY_CRAB 12 // Lay a headcrab
|
|
#define BIG_AE_JUMP_FORWARD 13 // Jump up and forward
|
|
#define BIG_AE_SCREAM 14 // alert sound
|
|
#define BIG_AE_PAIN_SOUND 15 // pain sound
|
|
#define BIG_AE_ATTACK_SOUND 16 // attack sound
|
|
#define BIG_AE_BIRTH_SOUND 17 // birth sound
|
|
#define BIG_AE_EARLY_TARGET 50 // Fire target early
|
|
|
|
// User defined conditions
|
|
#define bits_COND_NODE_SEQUENCE ( bits_COND_SPECIAL1 ) // pev->netname contains the name of a sequence to play
|
|
|
|
// Attack distance constants
|
|
#define BIG_ATTACKDIST 170.0f
|
|
#define BIG_MORTARDIST 800.0f
|
|
#define BIG_MAXCHILDREN 20 // Max # of live headcrab children
|
|
|
|
#define bits_MEMORY_CHILDPAIR ( bits_MEMORY_CUSTOM1 )
|
|
#define bits_MEMORY_ADVANCE_NODE ( bits_MEMORY_CUSTOM2 )
|
|
#define bits_MEMORY_COMPLETED_NODE ( bits_MEMORY_CUSTOM3 )
|
|
#define bits_MEMORY_FIRED_NODE ( bits_MEMORY_CUSTOM4 )
|
|
|
|
int gSpitSprite, gSpitDebrisSprite;
|
|
Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight );
|
|
void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count );
|
|
|
|
// UNDONE:
|
|
//
|
|
#define BIG_CHILDCLASS "monster_babycrab"
|
|
|
|
class CBigMomma : public CBaseMonster
|
|
{
|
|
public:
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
void KeyValue( KeyValueData *pkvd );
|
|
void Activate( void );
|
|
int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );
|
|
|
|
void RunTask( Task_t *pTask );
|
|
void StartTask( Task_t *pTask );
|
|
Schedule_t *GetSchedule( void );
|
|
Schedule_t *GetScheduleOfType( int Type );
|
|
void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType );
|
|
|
|
void NodeStart( int iszNextNode );
|
|
void NodeReach( void );
|
|
BOOL ShouldGoToNode( void );
|
|
|
|
void SetYawSpeed( void );
|
|
int Classify( void );
|
|
void HandleAnimEvent( MonsterEvent_t *pEvent );
|
|
void LayHeadcrab( void );
|
|
|
|
int GetNodeSequence( void )
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
if( pTarget )
|
|
{
|
|
return pTarget->pev->netname; // netname holds node sequence
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GetNodePresequence( void )
|
|
{
|
|
CInfoBM *pTarget = (CInfoBM *)(CBaseEntity *)m_hTargetEnt;
|
|
if( pTarget )
|
|
{
|
|
return pTarget->m_preSequence;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
float GetNodeDelay( void )
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
if( pTarget )
|
|
{
|
|
return pTarget->pev->speed; // Speed holds node delay
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
float GetNodeRange( void )
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
if( pTarget )
|
|
{
|
|
return pTarget->pev->scale; // Scale holds node delay
|
|
}
|
|
return 1e6;
|
|
}
|
|
|
|
float GetNodeYaw( void )
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
if( pTarget )
|
|
{
|
|
if( pTarget->pev->angles.y != 0 )
|
|
return pTarget->pev->angles.y;
|
|
}
|
|
return pev->angles.y;
|
|
}
|
|
|
|
// Restart the crab count on each new level
|
|
void OverrideReset( void )
|
|
{
|
|
m_crabCount = 0;
|
|
}
|
|
|
|
void DeathNotice( entvars_t *pevChild );
|
|
|
|
BOOL CanLayCrab( void )
|
|
{
|
|
if( m_crabTime < gpGlobals->time && m_crabCount < BIG_MAXCHILDREN )
|
|
{
|
|
// Don't spawn crabs inside each other
|
|
Vector mins = pev->origin - Vector( 32.0f, 32.0f, 0.0f );
|
|
Vector maxs = pev->origin + Vector( 32.0f, 32.0f, 0.0f );
|
|
|
|
CBaseEntity *pList[2];
|
|
int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_MONSTER );
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
if( pList[i] != this ) // Don't hurt yourself!
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void LaunchMortar( void );
|
|
|
|
void SetObjectCollisionBox( void )
|
|
{
|
|
pev->absmin = pev->origin + Vector( -95.0f, -95.0f, 0.0f );
|
|
pev->absmax = pev->origin + Vector( 95.0f, 95.0f, 190.0f );
|
|
}
|
|
|
|
BOOL CheckMeleeAttack1( float flDot, float flDist ); // Slash
|
|
BOOL CheckMeleeAttack2( float flDot, float flDist ); // Lay a crab
|
|
BOOL CheckRangeAttack1( float flDot, float flDist ); // Mortar launch
|
|
|
|
virtual int Save( CSave &save );
|
|
virtual int Restore( CRestore &restore );
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
|
|
static const char *pChildDieSounds[];
|
|
static const char *pSackSounds[];
|
|
static const char *pDeathSounds[];
|
|
static const char *pAttackSounds[];
|
|
static const char *pAttackHitSounds[];
|
|
static const char *pBirthSounds[];
|
|
static const char *pAlertSounds[];
|
|
static const char *pPainSounds[];
|
|
static const char *pFootSounds[];
|
|
|
|
CUSTOM_SCHEDULES
|
|
|
|
private:
|
|
float m_nodeTime;
|
|
float m_crabTime;
|
|
float m_mortarTime;
|
|
float m_painSoundTime;
|
|
int m_crabCount;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( monster_bigmomma, CBigMomma )
|
|
|
|
TYPEDESCRIPTION CBigMomma::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD( CBigMomma, m_nodeTime, FIELD_TIME ),
|
|
DEFINE_FIELD( CBigMomma, m_crabTime, FIELD_TIME ),
|
|
DEFINE_FIELD( CBigMomma, m_mortarTime, FIELD_TIME ),
|
|
DEFINE_FIELD( CBigMomma, m_painSoundTime, FIELD_TIME ),
|
|
DEFINE_FIELD( CBigMomma, m_crabCount, FIELD_INTEGER ),
|
|
};
|
|
|
|
IMPLEMENT_SAVERESTORE( CBigMomma, CBaseMonster )
|
|
|
|
const char *CBigMomma::pChildDieSounds[] =
|
|
{
|
|
"gonarch/gon_childdie1.wav",
|
|
"gonarch/gon_childdie2.wav",
|
|
"gonarch/gon_childdie3.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pSackSounds[] =
|
|
{
|
|
"gonarch/gon_sack1.wav",
|
|
"gonarch/gon_sack2.wav",
|
|
"gonarch/gon_sack3.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pDeathSounds[] =
|
|
{
|
|
"gonarch/gon_die1.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pAttackSounds[] =
|
|
{
|
|
"gonarch/gon_attack1.wav",
|
|
"gonarch/gon_attack2.wav",
|
|
"gonarch/gon_attack3.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pAttackHitSounds[] =
|
|
{
|
|
"zombie/claw_strike1.wav",
|
|
"zombie/claw_strike2.wav",
|
|
"zombie/claw_strike3.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pBirthSounds[] =
|
|
{
|
|
"gonarch/gon_birth1.wav",
|
|
"gonarch/gon_birth2.wav",
|
|
"gonarch/gon_birth3.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pAlertSounds[] =
|
|
{
|
|
"gonarch/gon_alert1.wav",
|
|
"gonarch/gon_alert2.wav",
|
|
"gonarch/gon_alert3.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pPainSounds[] =
|
|
{
|
|
"gonarch/gon_pain2.wav",
|
|
"gonarch/gon_pain4.wav",
|
|
"gonarch/gon_pain5.wav",
|
|
};
|
|
|
|
const char *CBigMomma::pFootSounds[] =
|
|
{
|
|
"gonarch/gon_step1.wav",
|
|
"gonarch/gon_step2.wav",
|
|
"gonarch/gon_step3.wav",
|
|
};
|
|
|
|
void CBigMomma::KeyValue( KeyValueData *pkvd )
|
|
{
|
|
#if 0
|
|
if( FStrEq( pkvd->szKeyName, "volume" ) )
|
|
{
|
|
m_volume = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
#endif
|
|
CBaseMonster::KeyValue( pkvd );
|
|
}
|
|
|
|
//=========================================================
|
|
// Classify - indicates this monster's place in the
|
|
// relationship table.
|
|
//=========================================================
|
|
int CBigMomma::Classify( void )
|
|
{
|
|
return CLASS_ALIEN_MONSTER;
|
|
}
|
|
|
|
//=========================================================
|
|
// SetYawSpeed - allows each sequence to have a different
|
|
// turn rate associated with it.
|
|
//=========================================================
|
|
void CBigMomma::SetYawSpeed( void )
|
|
{
|
|
int ys;
|
|
|
|
switch( m_Activity )
|
|
{
|
|
case ACT_IDLE:
|
|
ys = 100;
|
|
break;
|
|
default:
|
|
ys = 90;
|
|
break;
|
|
}
|
|
pev->yaw_speed = ys;
|
|
}
|
|
|
|
//=========================================================
|
|
// HandleAnimEvent - catches the monster-specific messages
|
|
// that occur when tagged animation frames are played.
|
|
//
|
|
// Returns number of events handled, 0 if none.
|
|
//=========================================================
|
|
void CBigMomma::HandleAnimEvent( MonsterEvent_t *pEvent )
|
|
{
|
|
switch( pEvent->event )
|
|
{
|
|
case BIG_AE_MELEE_ATTACKBR:
|
|
case BIG_AE_MELEE_ATTACKBL:
|
|
case BIG_AE_MELEE_ATTACK1:
|
|
{
|
|
Vector forward, right;
|
|
|
|
UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL );
|
|
|
|
Vector center = pev->origin + forward * 128.0f;
|
|
Vector mins = center - Vector( 64.0f, 64.0f, 0.0f );
|
|
Vector maxs = center + Vector( 64.0f, 64.0f, 64.0f );
|
|
|
|
CBaseEntity *pList[8];
|
|
int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_MONSTER | FL_CLIENT );
|
|
CBaseEntity *pHurt = NULL;
|
|
|
|
for( int i = 0; i < count && !pHurt; i++ )
|
|
{
|
|
if( pList[i] != this )
|
|
{
|
|
if( pList[i]->pev->owner != edict() )
|
|
pHurt = pList[i];
|
|
}
|
|
}
|
|
|
|
if( pHurt )
|
|
{
|
|
pHurt->TakeDamage( pev, pev, gSkillData.bigmommaDmgSlash, DMG_CRUSH | DMG_SLASH );
|
|
pHurt->pev->punchangle.x = 15.0f;
|
|
switch( pEvent->event )
|
|
{
|
|
case BIG_AE_MELEE_ATTACKBR:
|
|
pHurt->pev->velocity = pHurt->pev->velocity + ( forward * 150.0f ) + Vector( 0.0f, 0.0f, 250.0f ) - ( right * 200.0f );
|
|
break;
|
|
case BIG_AE_MELEE_ATTACKBL:
|
|
pHurt->pev->velocity = pHurt->pev->velocity + ( forward * 150.0f ) + Vector( 0.0f, 0.0f, 250.0f ) + ( right * 200.0f );
|
|
break;
|
|
case BIG_AE_MELEE_ATTACK1:
|
|
pHurt->pev->velocity = pHurt->pev->velocity + ( forward * 220.0f ) + Vector( 0.0f, 0.0f, 200.0f );
|
|
break;
|
|
}
|
|
|
|
pHurt->pev->flags &= ~FL_ONGROUND;
|
|
EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pAttackHitSounds ), 1.0f, ATTN_NORM, 0, 100 + RANDOM_LONG( -5, 5 ) );
|
|
}
|
|
}
|
|
break;
|
|
case BIG_AE_SCREAM:
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds );
|
|
break;
|
|
case BIG_AE_PAIN_SOUND:
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds );
|
|
break;
|
|
case BIG_AE_ATTACK_SOUND:
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackSounds );
|
|
break;
|
|
case BIG_AE_BIRTH_SOUND:
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pBirthSounds );
|
|
break;
|
|
case BIG_AE_SACK:
|
|
if( RANDOM_LONG( 0, 100 ) < 30 )
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pSackSounds );
|
|
break;
|
|
case BIG_AE_DEATHSOUND:
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds );
|
|
break;
|
|
case BIG_AE_STEP1: // Footstep left
|
|
case BIG_AE_STEP3: // Footstep back left
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_ITEM, pFootSounds );
|
|
break;
|
|
case BIG_AE_STEP4: // Footstep back right
|
|
case BIG_AE_STEP2: // Footstep right
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pFootSounds );
|
|
break;
|
|
case BIG_AE_MORTAR_ATTACK1:
|
|
LaunchMortar();
|
|
break;
|
|
case BIG_AE_LAY_CRAB:
|
|
LayHeadcrab();
|
|
break;
|
|
case BIG_AE_JUMP_FORWARD:
|
|
ClearBits( pev->flags, FL_ONGROUND );
|
|
|
|
UTIL_SetOrigin( pev, pev->origin + Vector( 0.0f, 0.0f, 1.0f ) );// take him off ground so engine doesn't instantly reset onground
|
|
UTIL_MakeVectors( pev->angles );
|
|
|
|
pev->velocity = gpGlobals->v_forward * 200.0f + gpGlobals->v_up * 500.0f;
|
|
break;
|
|
case BIG_AE_EARLY_TARGET:
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
if( pTarget && pTarget->pev->message )
|
|
FireTargets( STRING( pTarget->pev->message ), this, this, USE_TOGGLE, 0 );
|
|
Remember( bits_MEMORY_FIRED_NODE );
|
|
}
|
|
break;
|
|
default:
|
|
CBaseMonster::HandleAnimEvent( pEvent );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CBigMomma::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType )
|
|
{
|
|
if( ptr->iHitgroup != 1 )
|
|
{
|
|
// didn't hit the sack?
|
|
if( pev->dmgtime != gpGlobals->time || ( RANDOM_LONG( 0, 10 ) < 1 ) )
|
|
{
|
|
UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1.0f, 2.0f ) );
|
|
pev->dmgtime = gpGlobals->time;
|
|
}
|
|
|
|
flDamage = 0.1f;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
|
|
}
|
|
else if( gpGlobals->time > m_painSoundTime )
|
|
{
|
|
m_painSoundTime = gpGlobals->time + RANDOM_LONG( 1, 3 );
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds );
|
|
}
|
|
|
|
CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType );
|
|
}
|
|
|
|
int CBigMomma::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
|
|
{
|
|
// Don't take any acid damage -- BigMomma's mortar is acid
|
|
if( bitsDamageType & DMG_ACID )
|
|
flDamage = 0.0f;
|
|
|
|
if( !HasMemory( bits_MEMORY_PATH_FINISHED ) )
|
|
{
|
|
if( pev->health <= flDamage )
|
|
{
|
|
pev->health = flDamage + 1;
|
|
Remember( bits_MEMORY_ADVANCE_NODE | bits_MEMORY_COMPLETED_NODE );
|
|
ALERT( at_aiconsole, "BM: Finished node health!!!\n" );
|
|
}
|
|
}
|
|
|
|
return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
|
|
}
|
|
|
|
void CBigMomma::LayHeadcrab( void )
|
|
{
|
|
CBaseEntity *pChild = CBaseEntity::Create( BIG_CHILDCLASS, pev->origin, pev->angles, edict() );
|
|
|
|
pChild->pev->spawnflags |= SF_MONSTER_FALL_TO_GROUND;
|
|
|
|
// Is this the second crab in a pair?
|
|
if( HasMemory( bits_MEMORY_CHILDPAIR ) )
|
|
{
|
|
m_crabTime = gpGlobals->time + RANDOM_FLOAT( 5.0f, 10.0f );
|
|
Forget( bits_MEMORY_CHILDPAIR );
|
|
}
|
|
else
|
|
{
|
|
m_crabTime = gpGlobals->time + RANDOM_FLOAT( 0.5f, 2.5f );
|
|
Remember( bits_MEMORY_CHILDPAIR );
|
|
}
|
|
|
|
TraceResult tr;
|
|
UTIL_TraceLine( pev->origin, pev->origin - Vector( 0.0f, 0.0f, 100.0f ), ignore_monsters, edict(), &tr );
|
|
UTIL_DecalTrace( &tr, DECAL_MOMMABIRTH );
|
|
|
|
EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBirthSounds ), 1.0f, ATTN_NORM, 0, 100 + RANDOM_LONG( -5, 5 ) );
|
|
m_crabCount++;
|
|
}
|
|
|
|
void CBigMomma::DeathNotice( entvars_t *pevChild )
|
|
{
|
|
if( m_crabCount > 0 ) // Some babies may cross a transition, but we reset the count then
|
|
m_crabCount--;
|
|
if( IsAlive() )
|
|
{
|
|
// Make the "my baby's dead" noise!
|
|
EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pChildDieSounds );
|
|
}
|
|
}
|
|
|
|
void CBigMomma::LaunchMortar( void )
|
|
{
|
|
m_mortarTime = gpGlobals->time + RANDOM_FLOAT( 2.0f, 15.0f );
|
|
|
|
Vector startPos = pev->origin;
|
|
startPos.z += 180.0f;
|
|
|
|
EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pSackSounds ), 1.0f, ATTN_NORM, 0, 100 + RANDOM_LONG( -5, 5 ) );
|
|
CBMortar *pBomb = CBMortar::Shoot( edict(), startPos, pev->movedir );
|
|
pBomb->pev->gravity = 1.0f;
|
|
MortarSpray( startPos, Vector( 0.0f, 0.0f, 1.0f ), gSpitSprite, 24 );
|
|
}
|
|
|
|
//=========================================================
|
|
// Spawn
|
|
//=========================================================
|
|
void CBigMomma::Spawn()
|
|
{
|
|
Precache();
|
|
|
|
SET_MODEL( ENT( pev ), "models/big_mom.mdl" );
|
|
UTIL_SetSize( pev, Vector( -32.0f, -32.0f, 0.0f ), Vector( 32.0f, 32.0f, 64.0f ) );
|
|
|
|
pev->solid = SOLID_SLIDEBOX;
|
|
pev->movetype = MOVETYPE_STEP;
|
|
m_bloodColor = BLOOD_COLOR_GREEN;
|
|
pev->health = 150.0f * gSkillData.bigmommaHealthFactor;
|
|
pev->view_ofs = Vector( 0.0f, 0.0f, 128.0f );// position of the eyes relative to monster's origin.
|
|
m_flFieldOfView = 0.3f;// indicates the width of this monster's forward view cone ( as a dotproduct result )
|
|
m_MonsterState = MONSTERSTATE_NONE;
|
|
|
|
MonsterInit();
|
|
}
|
|
|
|
//=========================================================
|
|
// Precache - precaches all resources this monster needs
|
|
//=========================================================
|
|
void CBigMomma::Precache()
|
|
{
|
|
PRECACHE_MODEL( "models/big_mom.mdl" );
|
|
|
|
PRECACHE_SOUND_ARRAY( pChildDieSounds );
|
|
PRECACHE_SOUND_ARRAY( pSackSounds );
|
|
PRECACHE_SOUND_ARRAY( pDeathSounds );
|
|
PRECACHE_SOUND_ARRAY( pAttackSounds );
|
|
PRECACHE_SOUND_ARRAY( pAttackHitSounds );
|
|
PRECACHE_SOUND_ARRAY( pBirthSounds );
|
|
PRECACHE_SOUND_ARRAY( pAlertSounds );
|
|
PRECACHE_SOUND_ARRAY( pPainSounds );
|
|
PRECACHE_SOUND_ARRAY( pFootSounds );
|
|
|
|
UTIL_PrecacheOther( BIG_CHILDCLASS );
|
|
|
|
// TEMP: Squid
|
|
PRECACHE_MODEL( "sprites/mommaspit.spr" );// spit projectile.
|
|
gSpitSprite = PRECACHE_MODEL( "sprites/mommaspout.spr" );// client side spittle.
|
|
gSpitDebrisSprite = PRECACHE_MODEL( "sprites/mommablob.spr" );
|
|
|
|
PRECACHE_SOUND( "bullchicken/bc_acid1.wav" );
|
|
PRECACHE_SOUND( "bullchicken/bc_spithit1.wav" );
|
|
PRECACHE_SOUND( "bullchicken/bc_spithit2.wav" );
|
|
}
|
|
|
|
void CBigMomma::Activate( void )
|
|
{
|
|
if( m_hTargetEnt == 0 )
|
|
Remember( bits_MEMORY_ADVANCE_NODE ); // Start 'er up
|
|
}
|
|
|
|
void CBigMomma::NodeStart( int iszNextNode )
|
|
{
|
|
pev->netname = iszNextNode;
|
|
|
|
CBaseEntity *pTarget = NULL;
|
|
|
|
if( pev->netname )
|
|
{
|
|
edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->netname ) );
|
|
|
|
if( !FNullEnt( pentTarget ) )
|
|
pTarget = Instance( pentTarget );
|
|
}
|
|
|
|
if( !pTarget )
|
|
{
|
|
ALERT( at_aiconsole, "BM: Finished the path!!\n" );
|
|
Remember( bits_MEMORY_PATH_FINISHED );
|
|
return;
|
|
}
|
|
Remember( bits_MEMORY_ON_PATH );
|
|
m_hTargetEnt = pTarget;
|
|
}
|
|
|
|
void CBigMomma::NodeReach( void )
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
|
|
Forget( bits_MEMORY_ADVANCE_NODE );
|
|
|
|
if( !pTarget )
|
|
return;
|
|
|
|
if( pTarget->pev->health )
|
|
pev->max_health = pev->health = pTarget->pev->health * gSkillData.bigmommaHealthFactor;
|
|
|
|
if( !HasMemory( bits_MEMORY_FIRED_NODE ) )
|
|
{
|
|
if( pTarget->pev->message )
|
|
FireTargets( STRING( pTarget->pev->message ), this, this, USE_TOGGLE, 0 );
|
|
}
|
|
Forget( bits_MEMORY_FIRED_NODE );
|
|
|
|
pev->netname = pTarget->pev->target;
|
|
if( pTarget->pev->health == 0 )
|
|
Remember( bits_MEMORY_ADVANCE_NODE ); // Move on if no health at this node
|
|
}
|
|
|
|
// Slash
|
|
BOOL CBigMomma::CheckMeleeAttack1( float flDot, float flDist )
|
|
{
|
|
if( flDot >= 0.7f )
|
|
{
|
|
if( flDist <= BIG_ATTACKDIST )
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Lay a crab
|
|
BOOL CBigMomma::CheckMeleeAttack2( float flDot, float flDist )
|
|
{
|
|
return CanLayCrab();
|
|
}
|
|
|
|
// Mortar launch
|
|
BOOL CBigMomma::CheckRangeAttack1( float flDot, float flDist )
|
|
{
|
|
if( flDist <= BIG_MORTARDIST && m_mortarTime < gpGlobals->time )
|
|
{
|
|
CBaseEntity *pEnemy = m_hEnemy;
|
|
|
|
if( pEnemy )
|
|
{
|
|
Vector startPos = pev->origin;
|
|
startPos.z += 180.0f;
|
|
pev->movedir = VecCheckSplatToss( pev, startPos, pEnemy->BodyTarget( pev->origin ), RANDOM_FLOAT( 150.0f, 500.0f ) );
|
|
if( pev->movedir != g_vecZero )
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//=========================================================
|
|
// AI Schedules Specific to this monster
|
|
//=========================================================
|
|
enum
|
|
{
|
|
SCHED_BIG_NODE = LAST_COMMON_SCHEDULE + 1,
|
|
SCHED_NODE_FAIL
|
|
};
|
|
|
|
enum
|
|
{
|
|
TASK_MOVE_TO_NODE_RANGE = LAST_COMMON_TASK + 1, // Move within node range
|
|
TASK_FIND_NODE, // Find my next node
|
|
TASK_PLAY_NODE_PRESEQUENCE, // Play node pre-script
|
|
TASK_PLAY_NODE_SEQUENCE, // Play node script
|
|
TASK_PROCESS_NODE, // Fire targets, etc.
|
|
TASK_WAIT_NODE, // Wait at the node
|
|
TASK_NODE_DELAY, // Delay walking toward node for a bit. You've failed to get there
|
|
TASK_NODE_YAW // Get the best facing direction for this node
|
|
};
|
|
|
|
Task_t tlBigNode[] =
|
|
{
|
|
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_NODE_FAIL },
|
|
{ TASK_STOP_MOVING, (float)0 },
|
|
{ TASK_FIND_NODE, (float)0 }, // Find my next node
|
|
{ TASK_PLAY_NODE_PRESEQUENCE, (float)0 }, // Play the pre-approach sequence if any
|
|
{ TASK_MOVE_TO_NODE_RANGE, (float)0 }, // Move within node range
|
|
{ TASK_STOP_MOVING, (float)0 },
|
|
{ TASK_NODE_YAW, (float)0 },
|
|
{ TASK_FACE_IDEAL, (float)0 },
|
|
{ TASK_WAIT_NODE, (float)0 }, // Wait for node delay
|
|
{ TASK_PLAY_NODE_SEQUENCE, (float)0 }, // Play the sequence if one exists
|
|
{ TASK_PROCESS_NODE, (float)0 }, // Fire targets, etc.
|
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
|
|
};
|
|
|
|
Schedule_t slBigNode[] =
|
|
{
|
|
{
|
|
tlBigNode,
|
|
ARRAYSIZE( tlBigNode ),
|
|
0,
|
|
0,
|
|
"Big Node"
|
|
},
|
|
};
|
|
|
|
Task_t tlNodeFail[] =
|
|
{
|
|
{ TASK_NODE_DELAY, (float)10 }, // Try to do something else for 10 seconds
|
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
|
|
};
|
|
|
|
Schedule_t slNodeFail[] =
|
|
{
|
|
{
|
|
tlNodeFail,
|
|
ARRAYSIZE( tlNodeFail ),
|
|
0,
|
|
0,
|
|
"NodeFail"
|
|
},
|
|
};
|
|
|
|
DEFINE_CUSTOM_SCHEDULES( CBigMomma )
|
|
{
|
|
slBigNode,
|
|
slNodeFail,
|
|
};
|
|
|
|
IMPLEMENT_CUSTOM_SCHEDULES( CBigMomma, CBaseMonster )
|
|
|
|
Schedule_t *CBigMomma::GetScheduleOfType( int Type )
|
|
{
|
|
switch( Type )
|
|
{
|
|
case SCHED_BIG_NODE:
|
|
return slBigNode;
|
|
break;
|
|
case SCHED_NODE_FAIL:
|
|
return slNodeFail;
|
|
break;
|
|
}
|
|
|
|
return CBaseMonster::GetScheduleOfType( Type );
|
|
}
|
|
|
|
BOOL CBigMomma::ShouldGoToNode( void )
|
|
{
|
|
if( HasMemory( bits_MEMORY_ADVANCE_NODE ) )
|
|
{
|
|
if( m_nodeTime < gpGlobals->time )
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
Schedule_t *CBigMomma::GetSchedule( void )
|
|
{
|
|
if( ShouldGoToNode() )
|
|
{
|
|
return GetScheduleOfType( SCHED_BIG_NODE );
|
|
}
|
|
|
|
return CBaseMonster::GetSchedule();
|
|
}
|
|
|
|
void CBigMomma::StartTask( Task_t *pTask )
|
|
{
|
|
switch( pTask->iTask )
|
|
{
|
|
case TASK_FIND_NODE:
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
if( !HasMemory( bits_MEMORY_ADVANCE_NODE ) )
|
|
{
|
|
if( pTarget )
|
|
pev->netname = m_hTargetEnt->pev->target;
|
|
}
|
|
NodeStart( pev->netname );
|
|
TaskComplete();
|
|
ALERT( at_aiconsole, "BM: Found node %s\n", STRING( pev->netname ) );
|
|
}
|
|
break;
|
|
case TASK_NODE_DELAY:
|
|
m_nodeTime = gpGlobals->time + pTask->flData;
|
|
TaskComplete();
|
|
ALERT( at_aiconsole, "BM: FAIL! Delay %.2f\n", (double)pTask->flData );
|
|
break;
|
|
case TASK_PROCESS_NODE:
|
|
ALERT( at_aiconsole, "BM: Reached node %s\n", STRING( pev->netname ) );
|
|
NodeReach();
|
|
TaskComplete();
|
|
break;
|
|
case TASK_PLAY_NODE_PRESEQUENCE:
|
|
case TASK_PLAY_NODE_SEQUENCE:
|
|
{
|
|
int sequence;
|
|
if( pTask->iTask == TASK_PLAY_NODE_SEQUENCE )
|
|
sequence = GetNodeSequence();
|
|
else
|
|
sequence = GetNodePresequence();
|
|
|
|
ALERT( at_aiconsole, "BM: Playing node sequence %s\n", STRING( sequence ) );
|
|
if( sequence )
|
|
{
|
|
sequence = LookupSequence( STRING( sequence ) );
|
|
if( sequence != -1 )
|
|
{
|
|
pev->sequence = sequence;
|
|
pev->frame = 0;
|
|
ResetSequenceInfo();
|
|
ALERT( at_aiconsole, "BM: Sequence %s\n", STRING( GetNodeSequence() ) );
|
|
return;
|
|
}
|
|
}
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
case TASK_NODE_YAW:
|
|
pev->ideal_yaw = GetNodeYaw();
|
|
TaskComplete();
|
|
break;
|
|
case TASK_WAIT_NODE:
|
|
m_flWaitFinished = gpGlobals->time + GetNodeDelay();
|
|
if( m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT )
|
|
ALERT( at_aiconsole, "BM: Wait at node %s forever\n", STRING( pev->netname ) );
|
|
else
|
|
ALERT( at_aiconsole, "BM: Wait at node %s for %.2f\n", STRING( pev->netname ), (double)GetNodeDelay() );
|
|
break;
|
|
|
|
|
|
case TASK_MOVE_TO_NODE_RANGE:
|
|
{
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
if( !pTarget )
|
|
TaskFail();
|
|
else
|
|
{
|
|
if( ( pTarget->pev->origin - pev->origin ).Length() < GetNodeRange() )
|
|
TaskComplete();
|
|
else
|
|
{
|
|
Activity act = ACT_WALK;
|
|
if( pTarget->pev->spawnflags & SF_INFOBM_RUN )
|
|
act = ACT_RUN;
|
|
|
|
m_vecMoveGoal = pTarget->pev->origin;
|
|
if( !MoveToTarget( act, 2 ) )
|
|
{
|
|
TaskFail();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ALERT( at_aiconsole, "BM: Moving to node %s\n", STRING( pev->netname ) );
|
|
break;
|
|
case TASK_MELEE_ATTACK1:
|
|
// Play an attack sound here
|
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, ATTN_NORM, 0, PITCH_NORM );
|
|
CBaseMonster::StartTask( pTask );
|
|
break;
|
|
default:
|
|
CBaseMonster::StartTask( pTask );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// RunTask
|
|
//=========================================================
|
|
void CBigMomma::RunTask( Task_t *pTask )
|
|
{
|
|
switch( pTask->iTask )
|
|
{
|
|
case TASK_MOVE_TO_NODE_RANGE:
|
|
{
|
|
float distance;
|
|
|
|
if( m_hTargetEnt == 0 )
|
|
TaskFail();
|
|
else
|
|
{
|
|
distance = ( m_vecMoveGoal - pev->origin ).Length2D();
|
|
// Set the appropriate activity based on an overlapping range
|
|
// overlap the range to prevent oscillation
|
|
if( (distance < GetNodeRange() ) || MovementIsComplete() )
|
|
{
|
|
ALERT( at_aiconsole, "BM: Reached node!\n" );
|
|
TaskComplete();
|
|
RouteClear(); // Stop moving
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TASK_WAIT_NODE:
|
|
if( m_hTargetEnt != 0 && ( m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT ) )
|
|
return;
|
|
|
|
if( gpGlobals->time > m_flWaitFinished )
|
|
{
|
|
TaskComplete();
|
|
ALERT( at_aiconsole, "BM: The WAIT is over!\n" );
|
|
}
|
|
break;
|
|
case TASK_PLAY_NODE_PRESEQUENCE:
|
|
case TASK_PLAY_NODE_SEQUENCE:
|
|
if( m_fSequenceFinished )
|
|
{
|
|
m_Activity = ACT_RESET;
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
default:
|
|
CBaseMonster::RunTask( pTask );
|
|
break;
|
|
}
|
|
}
|
|
|
|
Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight )
|
|
{
|
|
TraceResult tr;
|
|
Vector vecMidPoint;// halfway point between Spot1 and Spot2
|
|
Vector vecApex;// highest point
|
|
Vector vecScale;
|
|
Vector vecGrenadeVel;
|
|
Vector vecTemp;
|
|
float flGravity = Q_max( g_psv_gravity->value, 0.1f );
|
|
|
|
// calculate the midpoint and apex of the 'triangle'
|
|
vecMidPoint = vecSpot1 + ( vecSpot2 - vecSpot1 ) * 0.5f;
|
|
UTIL_TraceLine( vecMidPoint, vecMidPoint + Vector( 0.0f, 0.0f, maxHeight ), ignore_monsters, ENT( pev ), &tr );
|
|
vecApex = tr.vecEndPos;
|
|
|
|
UTIL_TraceLine( vecSpot1, vecApex, dont_ignore_monsters, ENT( pev ), &tr );
|
|
if( tr.flFraction != 1.0f )
|
|
{
|
|
// fail!
|
|
return g_vecZero;
|
|
}
|
|
|
|
// Don't worry about actually hitting the target, this won't hurt us!
|
|
|
|
// TODO: Need another way to calculate height because current calculation is completely wrong
|
|
// and there posible crash.
|
|
|
|
// How high should the grenade travel (subtract 15 so the grenade doesn't hit the ceiling)?
|
|
float height = vecApex.z - vecSpot1.z - 15.0f;
|
|
// How fast does the grenade need to travel to reach that height given gravity?
|
|
float speed = sqrt( 2.0f * flGravity * height );
|
|
|
|
// How much time does it take to get there?
|
|
float time = speed / flGravity;
|
|
vecGrenadeVel = vecSpot2 - vecSpot1;
|
|
vecGrenadeVel.z = 0.0f;
|
|
|
|
// Travel half the distance to the target in that time (apex is at the midpoint)
|
|
vecGrenadeVel = vecGrenadeVel * ( 0.5f / time );
|
|
// Speed to offset gravity at the desired height
|
|
vecGrenadeVel.z = speed;
|
|
|
|
return vecGrenadeVel;
|
|
}
|
|
|
|
// ---------------------------------
|
|
//
|
|
// Mortar
|
|
//
|
|
// ---------------------------------
|
|
void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count )
|
|
{
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position );
|
|
WRITE_BYTE( TE_SPRITE_SPRAY );
|
|
WRITE_COORD( position.x ); // pos
|
|
WRITE_COORD( position.y );
|
|
WRITE_COORD( position.z );
|
|
WRITE_COORD( direction.x ); // dir
|
|
WRITE_COORD( direction.y );
|
|
WRITE_COORD( direction.z );
|
|
WRITE_SHORT( spriteModel ); // model
|
|
WRITE_BYTE ( count ); // count
|
|
WRITE_BYTE ( 130 ); // speed
|
|
WRITE_BYTE ( 80 ); // noise ( client will divide by 100 )
|
|
MESSAGE_END();
|
|
}
|
|
|
|
// UNDONE: right now this is pretty much a copy of the squid spit with minor changes to the way it does damage
|
|
void CBMortar::Spawn( void )
|
|
{
|
|
pev->movetype = MOVETYPE_TOSS;
|
|
pev->classname = MAKE_STRING( "bmortar" );
|
|
|
|
pev->solid = SOLID_BBOX;
|
|
pev->rendermode = kRenderTransAlpha;
|
|
pev->renderamt = 255;
|
|
|
|
SET_MODEL( ENT( pev ), "sprites/mommaspit.spr" );
|
|
pev->frame = 0;
|
|
pev->scale = 0.5f;
|
|
|
|
UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) );
|
|
|
|
m_maxFrame = MODEL_FRAMES( pev->modelindex ) - 1;
|
|
pev->dmgtime = gpGlobals->time + 0.4f;
|
|
}
|
|
|
|
void CBMortar::Animate( void )
|
|
{
|
|
pev->nextthink = gpGlobals->time + 0.1f;
|
|
|
|
if( gpGlobals->time > pev->dmgtime )
|
|
{
|
|
pev->dmgtime = gpGlobals->time + 0.2f;
|
|
MortarSpray( pev->origin, -pev->velocity.Normalize(), gSpitSprite, 3 );
|
|
}
|
|
if( pev->frame++ )
|
|
{
|
|
if( pev->frame > m_maxFrame )
|
|
{
|
|
pev->frame = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
CBMortar *CBMortar::Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity )
|
|
{
|
|
CBMortar *pSpit = GetClassPtr( (CBMortar *)NULL );
|
|
pSpit->Spawn();
|
|
|
|
UTIL_SetOrigin( pSpit->pev, vecStart );
|
|
pSpit->pev->velocity = vecVelocity;
|
|
pSpit->pev->owner = pOwner;
|
|
pSpit->pev->scale = 2.5f;
|
|
pSpit->SetThink( &CBMortar::Animate );
|
|
pSpit->pev->nextthink = gpGlobals->time + 0.1f;
|
|
|
|
return pSpit;
|
|
}
|
|
|
|
void CBMortar::Touch( CBaseEntity *pOther )
|
|
{
|
|
TraceResult tr;
|
|
int iPitch;
|
|
|
|
// splat sound
|
|
iPitch = RANDOM_FLOAT( 90, 110 );
|
|
|
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch );
|
|
|
|
switch( RANDOM_LONG( 0, 1 ) )
|
|
{
|
|
case 0:
|
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch );
|
|
break;
|
|
case 1:
|
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch );
|
|
break;
|
|
}
|
|
|
|
if( pOther->IsBSPModel() )
|
|
{
|
|
|
|
// make a splat on the wall
|
|
UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10.0f, dont_ignore_monsters, ENT( pev ), &tr );
|
|
UTIL_DecalTrace( &tr, DECAL_MOMMASPLAT );
|
|
}
|
|
else
|
|
{
|
|
tr.vecEndPos = pev->origin;
|
|
tr.vecPlaneNormal = -1.0f * pev->velocity.Normalize();
|
|
}
|
|
|
|
// make some flecks
|
|
MortarSpray( tr.vecEndPos, tr.vecPlaneNormal, gSpitSprite, 24 );
|
|
|
|
entvars_t *pevOwner = NULL;
|
|
if( pev->owner )
|
|
pevOwner = VARS(pev->owner);
|
|
|
|
RadiusDamage( pev->origin, pev, pevOwner, gSkillData.bigmommaDmgBlast, gSkillData.bigmommaRadiusBlast, CLASS_NONE, DMG_ACID );
|
|
UTIL_Remove( this );
|
|
}
|
|
#endif
|