/*** * * 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 #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "weapons.h" #include "nodes.h" #include "effects.h" #define N_SCALE 15 #define N_SPHERES 20 class CNihilanth : public CBaseMonster { public: int Save( CSave &save ); int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; void Spawn( void ); void Precache( void ); void UpdateOnRemove(); int Classify( void ) { return CLASS_ALIEN_MILITARY; }; int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } void Killed( entvars_t *pevAttacker, int iGib ); void GibMonster( void ); void SetObjectCollisionBox( void ) { pev->absmin = pev->origin + Vector( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE ); pev->absmax = pev->origin + Vector( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE ); } void HandleAnimEvent( MonsterEvent_t *pEvent ); void EXPORT StartupThink( void ); void EXPORT HuntThink( void ); void EXPORT CrashTouch( CBaseEntity *pOther ); void EXPORT DyingThink( void ); void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void EXPORT NullThink( void ); void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void FloatSequence( void ); void NextActivity( void ); void Flight( void ); BOOL AbsorbSphere( void ); BOOL EmitSphere( void ); void TargetSphere( USE_TYPE useType, float value ); CBaseEntity *RandomTargetname( const char *szName ); void ShootBalls( void ); void MakeFriend( Vector vecPos ); int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); void PainSound( void ); void DeathSound( void ); static const char *pAttackSounds[]; // vocalization: play sometimes when he launches an attack static const char *pBallSounds[]; // the sound of the lightening ball launch static const char *pShootSounds[]; // grunting vocalization: play sometimes when he launches an attack static const char *pRechargeSounds[]; // vocalization: play when he recharges static const char *pLaughSounds[]; // vocalization: play sometimes when hit and still has lots of health static const char *pPainSounds[]; // vocalization: play sometimes when hit and has much less health and no more chargers static const char *pDeathSounds[]; // vocalization: play as he dies // x_teleattack1.wav the looping sound of the teleport attack ball. float m_flForce; float m_flNextPainSound; Vector m_velocity; Vector m_avelocity; Vector m_vecTarget; Vector m_posTarget; Vector m_vecDesired; Vector m_posDesired; float m_flMinZ; float m_flMaxZ; Vector m_vecGoal; float m_flLastSeen; float m_flPrevSeen; int m_irritation; int m_iLevel; int m_iTeleport; EHANDLE m_hRecharger; EHANDLE m_hSphere[N_SPHERES]; int m_iActiveSpheres; float m_flAdj; CSprite *m_pBall; char m_szRechargerTarget[64]; char m_szDrawUse[64]; char m_szTeleportUse[64]; char m_szTeleportTouch[64]; char m_szDeadUse[64]; char m_szDeadTouch[64]; float m_flShootEnd; float m_flShootTime; EHANDLE m_hFriend[3]; }; LINK_ENTITY_TO_CLASS( monster_nihilanth, CNihilanth ) TYPEDESCRIPTION CNihilanth::m_SaveData[] = { DEFINE_FIELD( CNihilanth, m_flForce, FIELD_FLOAT ), DEFINE_FIELD( CNihilanth, m_flNextPainSound, FIELD_TIME ), DEFINE_FIELD( CNihilanth, m_velocity, FIELD_VECTOR ), DEFINE_FIELD( CNihilanth, m_avelocity, FIELD_VECTOR ), DEFINE_FIELD( CNihilanth, m_vecTarget, FIELD_VECTOR ), DEFINE_FIELD( CNihilanth, m_posTarget, FIELD_POSITION_VECTOR ), DEFINE_FIELD( CNihilanth, m_vecDesired, FIELD_VECTOR ), DEFINE_FIELD( CNihilanth, m_posDesired, FIELD_POSITION_VECTOR ), DEFINE_FIELD( CNihilanth, m_flMinZ, FIELD_FLOAT ), DEFINE_FIELD( CNihilanth, m_flMaxZ, FIELD_FLOAT ), DEFINE_FIELD( CNihilanth, m_vecGoal, FIELD_VECTOR ), DEFINE_FIELD( CNihilanth, m_flLastSeen, FIELD_TIME ), DEFINE_FIELD( CNihilanth, m_flPrevSeen, FIELD_TIME ), DEFINE_FIELD( CNihilanth, m_irritation, FIELD_INTEGER ), DEFINE_FIELD( CNihilanth, m_iLevel, FIELD_INTEGER ), DEFINE_FIELD( CNihilanth, m_iTeleport, FIELD_INTEGER ), DEFINE_FIELD( CNihilanth, m_hRecharger, FIELD_EHANDLE ), DEFINE_ARRAY( CNihilanth, m_hSphere, FIELD_EHANDLE, N_SPHERES ), DEFINE_FIELD( CNihilanth, m_iActiveSpheres, FIELD_INTEGER ), DEFINE_FIELD( CNihilanth, m_flAdj, FIELD_FLOAT ), DEFINE_FIELD( CNihilanth, m_pBall, FIELD_CLASSPTR ), DEFINE_ARRAY( CNihilanth, m_szRechargerTarget, FIELD_CHARACTER, 64 ), DEFINE_ARRAY( CNihilanth, m_szDrawUse, FIELD_CHARACTER, 64 ), DEFINE_ARRAY( CNihilanth, m_szTeleportUse, FIELD_CHARACTER, 64 ), DEFINE_ARRAY( CNihilanth, m_szTeleportTouch, FIELD_CHARACTER, 64 ), DEFINE_ARRAY( CNihilanth, m_szDeadUse, FIELD_CHARACTER, 64 ), DEFINE_ARRAY( CNihilanth, m_szDeadTouch, FIELD_CHARACTER, 64 ), DEFINE_FIELD( CNihilanth, m_flShootEnd, FIELD_TIME ), DEFINE_FIELD( CNihilanth, m_flShootTime, FIELD_TIME ), DEFINE_ARRAY( CNihilanth, m_hFriend, FIELD_EHANDLE, 3 ), }; IMPLEMENT_SAVERESTORE( CNihilanth, CBaseMonster ) class CNihilanthHVR : public CBaseMonster { public: int Save( CSave &save ); int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; void Spawn( void ); void Precache( void ); void CircleInit( CBaseEntity *pTarget ); void AbsorbInit( void ); void TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ); void GreenBallInit( void ); void ZapInit( CBaseEntity *pEnemy ); void EXPORT HoverThink( void ); BOOL CircleTarget( Vector vecTarget ); void EXPORT DissipateThink( void ); void EXPORT ZapThink( void ); void EXPORT TeleportThink( void ); void EXPORT TeleportTouch( CBaseEntity *pOther ); void EXPORT RemoveTouch( CBaseEntity *pOther ); void EXPORT BounceTouch( CBaseEntity *pOther ); void EXPORT ZapTouch( CBaseEntity *pOther ); CBaseEntity *RandomClassname( const char *szName ); // void EXPORT SphereUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void MovetoTarget( Vector vecTarget ); virtual void Crawl( void ); void Zap( void ); void Teleport( void ); float m_flIdealVel; Vector m_vecIdeal; CNihilanth *m_pNihilanth; EHANDLE m_hTouch; int m_nFrames; }; LINK_ENTITY_TO_CLASS( nihilanth_energy_ball, CNihilanthHVR ) TYPEDESCRIPTION CNihilanthHVR::m_SaveData[] = { DEFINE_FIELD( CNihilanthHVR, m_flIdealVel, FIELD_FLOAT ), DEFINE_FIELD( CNihilanthHVR, m_vecIdeal, FIELD_VECTOR ), DEFINE_FIELD( CNihilanthHVR, m_pNihilanth, FIELD_CLASSPTR ), DEFINE_FIELD( CNihilanthHVR, m_hTouch, FIELD_EHANDLE ), DEFINE_FIELD( CNihilanthHVR, m_nFrames, FIELD_INTEGER ), }; IMPLEMENT_SAVERESTORE( CNihilanthHVR, CBaseMonster ) //========================================================= // Nihilanth, final Boss monster //========================================================= const char *CNihilanth::pAttackSounds[] = { "X/x_attack1.wav", "X/x_attack2.wav", "X/x_attack3.wav", }; const char *CNihilanth::pBallSounds[] = { "X/x_ballattack1.wav", }; const char *CNihilanth::pShootSounds[] = { "X/x_shoot1.wav", }; const char *CNihilanth::pRechargeSounds[] = { "X/x_recharge1.wav", "X/x_recharge2.wav", "X/x_recharge3.wav", }; const char *CNihilanth::pLaughSounds[] = { "X/x_laugh1.wav", "X/x_laugh2.wav", }; const char *CNihilanth::pPainSounds[] = { "X/x_pain1.wav", "X/x_pain2.wav", }; const char *CNihilanth::pDeathSounds[] = { "X/x_die1.wav", }; void CNihilanth::Spawn( void ) { Precache(); // motor pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; SET_MODEL( edict(), "models/nihilanth.mdl" ); // UTIL_SetSize(pev, Vector( -300, -300, 0), Vector(300, 300, 512)); UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); UTIL_SetOrigin( pev, pev->origin ); pev->flags |= FL_MONSTER; pev->takedamage = DAMAGE_AIM; pev->health = gSkillData.nihilanthHealth; pev->view_ofs = Vector( 0, 0, 300 ); m_flFieldOfView = -1; // 360 degrees pev->sequence = 0; ResetSequenceInfo(); InitBoneControllers(); SetThink( &CNihilanth::StartupThink ); pev->nextthink = gpGlobals->time + 0.1f; m_vecDesired = Vector( 1, 0, 0 ); m_posDesired = Vector( pev->origin.x, pev->origin.y, 512 ); m_iLevel = 1; m_iTeleport = 1; if( m_szRechargerTarget[0] == '\0' ) strcpy( m_szRechargerTarget, "n_recharger" ); if( m_szDrawUse[0] == '\0' ) strcpy( m_szDrawUse, "n_draw" ); if( m_szTeleportUse[0] == '\0' ) strcpy( m_szTeleportUse, "n_leaving" ); if( m_szTeleportTouch[0] == '\0' ) strcpy( m_szTeleportTouch, "n_teleport" ); if( m_szDeadUse[0] == '\0' ) strcpy( m_szDeadUse, "n_dead" ); if( m_szDeadTouch[0] == '\0' ) strcpy( m_szDeadTouch, "n_ending" ); // near death /* m_iTeleport = 10; m_iLevel = 10; m_irritation = 2; pev->health = 100; */ } void CNihilanth::Precache( void ) { PRECACHE_MODEL( "models/nihilanth.mdl" ); // PRECACHE_MODEL( "sprites/lgtning.spr" ); UTIL_PrecacheOther( "nihilanth_energy_ball" ); UTIL_PrecacheOther( "monster_alien_controller" ); UTIL_PrecacheOther( "monster_alien_slave" ); PRECACHE_SOUND_ARRAY( pAttackSounds ); PRECACHE_SOUND_ARRAY( pBallSounds ); PRECACHE_SOUND_ARRAY( pShootSounds ); PRECACHE_SOUND_ARRAY( pRechargeSounds ); PRECACHE_SOUND_ARRAY( pLaughSounds ); PRECACHE_SOUND_ARRAY( pPainSounds ); PRECACHE_SOUND_ARRAY( pDeathSounds ); PRECACHE_SOUND( "debris/beamstart7.wav" ); } void CNihilanth::UpdateOnRemove() { CBaseEntity::UpdateOnRemove(); if( m_pBall ) { UTIL_Remove( m_pBall ); m_pBall = 0; } for( int i = 0; i < N_SPHERES; i++ ) { if( CBaseEntity* pSphere = (CBaseEntity *)m_hSphere[i] ) { UTIL_Remove( pSphere ); m_hSphere[i] = 0; } } } void CNihilanth::PainSound( void ) { if( m_flNextPainSound > gpGlobals->time ) return; m_flNextPainSound = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); if( pev->health > gSkillData.nihilanthHealth / 2 ) { EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pLaughSounds ), 1.0, 0.2 ); } else if( m_irritation >= 2 ) { EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pPainSounds ), 1.0, 0.2 ); } } void CNihilanth::DeathSound( void ) { EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pDeathSounds ), 1.0, 0.1 ); } void CNihilanth::NullThink( void ) { StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.5f; } void CNihilanth::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { SetThink( &CNihilanth::HuntThink ); pev->nextthink = gpGlobals->time + 0.1f; SetUse( &CNihilanth::CommandUse ); } void CNihilanth::StartupThink( void ) { m_irritation = 0; m_flAdj = 512; CBaseEntity *pEntity; pEntity = UTIL_FindEntityByTargetname( NULL, "n_min" ); if( pEntity ) m_flMinZ = pEntity->pev->origin.z; else m_flMinZ = -4096; pEntity = UTIL_FindEntityByTargetname( NULL, "n_max" ); if( pEntity ) m_flMaxZ = pEntity->pev->origin.z; else m_flMaxZ = 4096; m_hRecharger = this; for( int i = 0; i < N_SPHERES; i++ ) { EmitSphere(); } m_hRecharger = NULL; SetThink( &CNihilanth::HuntThink ); SetUse( &CNihilanth::CommandUse ); pev->nextthink = gpGlobals->time + 0.1f; } void CNihilanth::Killed( entvars_t *pevAttacker, int iGib ) { CBaseMonster::Killed( pevAttacker, iGib ); } void CNihilanth::DyingThink( void ) { pev->nextthink = gpGlobals->time + 0.1f; DispatchAnimEvents(); StudioFrameAdvance(); if( pev->deadflag == DEAD_NO ) { DeathSound(); pev->deadflag = DEAD_DYING; m_posDesired.z = m_flMaxZ; } if( pev->deadflag == DEAD_DYING ) { Flight(); if( fabs( pev->origin.z - m_flMaxZ ) < 16 ) { pev->velocity = Vector( 0, 0, 0 ); FireTargets( m_szDeadUse, this, this, USE_ON, 1.0 ); pev->deadflag = DEAD_DEAD; } } if( m_fSequenceFinished ) { pev->avelocity.y += RANDOM_FLOAT( -100, 100 ); if( pev->avelocity.y < -100 ) pev->avelocity.y = -100; if( pev->avelocity.y > 100 ) pev->avelocity.y = 100; pev->sequence = LookupSequence( "die1" ); } if( m_pBall ) { if( m_pBall->pev->renderamt > 0 ) { m_pBall->pev->renderamt = Q_max( 0, m_pBall->pev->renderamt - 2 ); } else { UTIL_Remove( m_pBall ); m_pBall = NULL; } } Vector vecDir, vecSrc, vecAngles; UTIL_MakeAimVectors( pev->angles ); int iAttachment = RANDOM_LONG( 1, 4 ); do { vecDir = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ); } while( DotProduct( vecDir, vecDir ) > 1.0f ); switch( RANDOM_LONG( 1, 4 ) ) { case 1: // head vecDir.z = fabs( vecDir.z ) * 0.5f; vecDir = vecDir + 2 * gpGlobals->v_up; break; case 2: // eyes if( DotProduct( vecDir, gpGlobals->v_forward ) < 0 ) vecDir = vecDir * -1; vecDir = vecDir + 2 * gpGlobals->v_forward; break; case 3: // left hand if( DotProduct( vecDir, gpGlobals->v_right ) > 0 ) vecDir = vecDir * -1; vecDir = vecDir - 2 * gpGlobals->v_right; break; case 4: // right hand if( DotProduct( vecDir, gpGlobals->v_right ) < 0 ) vecDir = vecDir * -1; vecDir = vecDir + 2 * gpGlobals->v_right; break; } GetAttachment( iAttachment - 1, vecSrc, vecAngles ); TraceResult tr; UTIL_TraceLine( vecSrc, vecSrc + vecDir * 4096, ignore_monsters, ENT( pev ), &tr ); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTPOINT ); WRITE_SHORT( entindex() + 0x1000 * iAttachment ); WRITE_COORD( tr.vecEndPos.x); WRITE_COORD( tr.vecEndPos.y); WRITE_COORD( tr.vecEndPos.z); WRITE_SHORT( g_sModelIndexLaser ); WRITE_BYTE( 0 ); // frame start WRITE_BYTE( 10 ); // framerate WRITE_BYTE( 5 ); // life WRITE_BYTE( 100 ); // width WRITE_BYTE( 120 ); // noise WRITE_BYTE( 64 ); // r, g, b WRITE_BYTE( 128 ); // r, g, b WRITE_BYTE( 255); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 10 ); // speed MESSAGE_END(); GetAttachment( 0, vecSrc, vecAngles ); CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); pEntity->pev->velocity = Vector( RANDOM_FLOAT( -0.7f, 0.7f ), RANDOM_FLOAT( -0.7f, 0.7f ), 1.0f ) * 600.0f; pEntity->GreenBallInit(); return; } void CNihilanth::CrashTouch( CBaseEntity *pOther ) { // only crash if we hit something solid if( pOther->pev->solid == SOLID_BSP ) { SetTouch( NULL ); pev->nextthink = gpGlobals->time; } } void CNihilanth::GibMonster( void ) { // EMIT_SOUND_DYN( edict(), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200 ); } void CNihilanth::FloatSequence( void ) { if( m_irritation >= 2 ) { pev->sequence = LookupSequence( "float_open" ); } else if( m_avelocity.y > 30 ) { pev->sequence = LookupSequence( "walk_r" ); } else if( m_avelocity.y < -30 ) { pev->sequence = LookupSequence( "walk_l" ); } else if( m_velocity.z > 30 ) { pev->sequence = LookupSequence( "walk_u" ); } else if( m_velocity.z < -30 ) { pev->sequence = LookupSequence( "walk_d" ); } else { pev->sequence = LookupSequence( "float" ); } } void CNihilanth::ShootBalls( void ) { if( m_flShootEnd > gpGlobals->time ) { Vector vecHand, vecAngle; while( m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time ) { if( m_hEnemy != 0 ) { Vector vecSrc, vecDir; CNihilanthHVR *pEntity; GetAttachment( 2, vecHand, vecAngle ); vecSrc = vecHand + pev->velocity * ( m_flShootTime - gpGlobals->time ); // vecDir = ( m_posTarget - vecSrc ).Normalize(); vecDir = ( m_posTarget - pev->origin ).Normalize(); vecSrc = vecSrc + vecDir * ( gpGlobals->time - m_flShootTime ); pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); pEntity->pev->velocity = vecDir * 200.0f; pEntity->ZapInit( m_hEnemy ); GetAttachment( 3, vecHand, vecAngle ); vecSrc = vecHand + pev->velocity * ( m_flShootTime - gpGlobals->time ); // vecDir = ( m_posTarget - vecSrc ).Normalize(); vecDir = ( m_posTarget - pev->origin ).Normalize(); vecSrc = vecSrc + vecDir * ( gpGlobals->time - m_flShootTime ); pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); pEntity->pev->velocity = vecDir * 200.0f; pEntity->ZapInit( m_hEnemy ); } m_flShootTime += 0.2f; } } } void CNihilanth::MakeFriend( Vector vecStart ) { int i; for( i = 0; i < 3; i++ ) { if( m_hFriend[i] != 0 && !m_hFriend[i]->IsAlive() ) { if( pev->rendermode == kRenderNormal ) // don't do it if they are already fading m_hFriend[i]->MyMonsterPointer()->FadeMonster(); m_hFriend[i] = NULL; } if( m_hFriend[i] == 0 ) { if( RANDOM_LONG( 0, 1 ) == 0 ) { int iNode = WorldGraph.FindNearestNode( vecStart, bits_NODE_AIR ); if( iNode != NO_NODE ) { CNode &node = WorldGraph.Node( iNode ); TraceResult tr; UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 32 ), node.m_vecOrigin + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, NULL, &tr ); if( tr.fStartSolid == 0 ) m_hFriend[i] = Create( "monster_alien_controller", node.m_vecOrigin, pev->angles ); } } else { int iNode = WorldGraph.FindNearestNode( vecStart, bits_NODE_LAND | bits_NODE_WATER ); if( iNode != NO_NODE ) { CNode &node = WorldGraph.Node( iNode ); TraceResult tr; UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 36 ), node.m_vecOrigin + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, NULL, &tr ); if( tr.fStartSolid == 0 ) m_hFriend[i] = Create( "monster_alien_slave", node.m_vecOrigin, pev->angles ); } } if( m_hFriend[i] != 0 ) { EMIT_SOUND( m_hFriend[i]->edict(), CHAN_WEAPON, "debris/beamstart7.wav", 1.0, ATTN_NORM ); } return; } } } void CNihilanth::NextActivity() { UTIL_MakeAimVectors( pev->angles ); if( m_irritation >= 2 ) { if( m_pBall == NULL ) { m_pBall = CSprite::SpriteCreate( "sprites/tele1.spr", pev->origin, TRUE ); if( m_pBall ) { m_pBall->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); m_pBall->SetAttachment( edict(), 1 ); m_pBall->SetScale( 4.0f ); m_pBall->pev->framerate = 10.0f; m_pBall->TurnOn(); } } if( m_pBall ) { MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x1000 ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( 256 ); // radius WRITE_BYTE( 255 ); // R WRITE_BYTE( 192 ); // G WRITE_BYTE( 64 ); // B WRITE_BYTE( 200 ); // life * 10 WRITE_COORD( 0 ); // decay MESSAGE_END(); } } if( ( pev->health < gSkillData.nihilanthHealth / 2 || m_iActiveSpheres < N_SPHERES / 2 ) && m_hRecharger == 0 && m_iLevel <= 9 ) { char szName[128]; CBaseEntity *pEnt = NULL; CBaseEntity *pRecharger = NULL; float flDist = 8192; sprintf( szName, "%s%d", m_szRechargerTarget, m_iLevel ); while( ( pEnt = UTIL_FindEntityByTargetname( pEnt, szName ) ) != NULL ) { float flLocal = (pEnt->pev->origin - pev->origin ).Length(); if( flLocal < flDist ) { flDist = flLocal; pRecharger = pEnt; } } if( pRecharger ) { m_hRecharger = pRecharger; m_posDesired = Vector( pev->origin.x, pev->origin.y, pRecharger->pev->origin.z ); m_vecDesired = ( pRecharger->pev->origin - m_posDesired ).Normalize(); m_vecDesired.z = 0; m_vecDesired = m_vecDesired.Normalize(); } else { m_hRecharger = NULL; ALERT( at_aiconsole, "nihilanth can't find %s\n", szName ); m_iLevel++; if( m_iLevel > 9 ) m_irritation = 2; } } float flDist = ( m_posDesired - pev->origin ).Length(); float flDot = DotProduct( m_vecDesired, gpGlobals->v_forward ); if( m_hRecharger != 0 ) { // at we at power up yet? if( flDist < 128.0f ) { int iseq = LookupSequence( "recharge" ); if( iseq != pev->sequence ) { char szText[128]; sprintf( szText, "%s%d", m_szDrawUse, m_iLevel ); FireTargets( szText, this, this, USE_ON, 1.0 ); ALERT( at_console, "fireing %s\n", szText ); } pev->sequence = LookupSequence( "recharge" ); } else { FloatSequence(); } return; } if( m_hEnemy != 0 && !m_hEnemy->IsAlive() ) { m_hEnemy = 0; } if( m_flLastSeen + 15 < gpGlobals->time ) { m_hEnemy = 0; } if( m_hEnemy == 0 ) { Look( 4096 ); m_hEnemy = BestVisibleEnemy(); } if( m_hEnemy != 0 && m_irritation != 0 ) { if( m_flLastSeen + 5 > gpGlobals->time && flDist < 256 && flDot > 0 ) { if( m_irritation >= 2 && pev->health < gSkillData.nihilanthHealth / 2.0f ) { pev->sequence = LookupSequence( "attack1_open" ); } else { if( RANDOM_LONG( 0, 1 ) == 0 ) { pev->sequence = LookupSequence( "attack1" ); // zap } else { char szText[128]; sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); if( pTrigger != NULL || pTouch != NULL ) { pev->sequence = LookupSequence( "attack2" ); // teleport } else { m_iTeleport++; pev->sequence = LookupSequence( "attack1" ); // zap } } } return; } } FloatSequence(); } void CNihilanth::HuntThink( void ) { pev->nextthink = gpGlobals->time + 0.1f; DispatchAnimEvents(); StudioFrameAdvance(); ShootBalls(); // if dead, force cancelation of current animation if( pev->health <= 0 ) { SetThink( &CNihilanth::DyingThink ); m_fSequenceFinished = TRUE; return; } // ALERT( at_console, "health %.0f\n", pev->health ); // if damaged, try to abosorb some spheres if( pev->health < gSkillData.nihilanthHealth && AbsorbSphere() ) { pev->health += gSkillData.nihilanthHealth / N_SPHERES; } // get new sequence if( m_fSequenceFinished ) { // if ( !m_fSequenceLoops ) pev->frame = 0; NextActivity(); ResetSequenceInfo(); pev->framerate = 2.0f - 1.0f * ( pev->health / gSkillData.nihilanthHealth ); } // look for current enemy if( m_hEnemy != 0 && m_hRecharger == 0 ) { if( FVisible( m_hEnemy ) ) { if( m_flLastSeen < gpGlobals->time - 5.0f ) m_flPrevSeen = gpGlobals->time; m_flLastSeen = gpGlobals->time; m_posTarget = m_hEnemy->pev->origin; m_vecTarget = ( m_posTarget - pev->origin ).Normalize(); m_vecDesired = m_vecTarget; m_posDesired = Vector( pev->origin.x, pev->origin.y, m_posTarget.z + m_flAdj ); } else { m_flAdj = Q_min( m_flAdj + 10, 1000 ); } } // don't go too high if( m_posDesired.z > m_flMaxZ ) m_posDesired.z = m_flMaxZ; // don't go too low if( m_posDesired.z < m_flMinZ ) m_posDesired.z = m_flMinZ; Flight(); } void CNihilanth::Flight( void ) { // estimate where I'll be facing in one seconds UTIL_MakeAimVectors( pev->angles + m_avelocity ); // Vector vecEst1 = pev->origin + m_velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); if( flSide < 0 ) { if( m_avelocity.y < 180 ) { m_avelocity.y += 6; // 9 * ( 3.0 / 2.0 ); } } else { if( m_avelocity.y > -180 ) { m_avelocity.y -= 6; // 9 * ( 3.0 / 2.0 ); } } m_avelocity.y *= 0.98f; // estimate where I'll be in two seconds Vector vecEst = pev->origin + m_velocity * 2.0 + gpGlobals->v_up * m_flForce * 20; // add immediate force UTIL_MakeAimVectors( pev->angles ); m_velocity.x += gpGlobals->v_up.x * m_flForce; m_velocity.y += gpGlobals->v_up.y * m_flForce; m_velocity.z += gpGlobals->v_up.z * m_flForce; /*float flSpeed = m_velocity.Length(); float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( m_velocity.x, m_velocity.y, 0 ) ); if( flDir < 0 ) flSpeed = -flSpeed;*/ //float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); // sideways drag m_velocity.x = m_velocity.x * ( 1.0f - fabs( gpGlobals->v_right.x ) * 0.05f ); m_velocity.y = m_velocity.y * ( 1.0f - fabs( gpGlobals->v_right.y ) * 0.05f ); m_velocity.z = m_velocity.z * ( 1.0f - fabs( gpGlobals->v_right.z ) * 0.05f ); // general drag m_velocity = m_velocity * 0.995f; // apply power to stay correct height if( m_flForce < 100 && vecEst.z < m_posDesired.z ) { m_flForce += 10; } else if( m_flForce > -100 && vecEst.z > m_posDesired.z ) { if( vecEst.z > m_posDesired.z ) m_flForce -= 10; } UTIL_SetOrigin( pev, pev->origin + m_velocity * 0.1f ); pev->angles = pev->angles + m_avelocity * 0.1f; // ALERT( at_console, "%5.0f %5.0f : %4.0f : %3.0f : %2.0f\n", m_posDesired.z, pev->origin.z, m_velocity.z, m_avelocity.y, m_flForce ); } BOOL CNihilanth::AbsorbSphere( void ) { for( int i = 0; i < N_SPHERES; i++ ) { if( m_hSphere[i] != 0 ) { CNihilanthHVR *pSphere = (CNihilanthHVR *)( (CBaseEntity *)m_hSphere[i] ); pSphere->AbsorbInit(); m_hSphere[i] = NULL; m_iActiveSpheres--; return TRUE; } } return FALSE; } BOOL CNihilanth::EmitSphere( void ) { m_iActiveSpheres = 0; int empty = 0; for( int i = 0; i < N_SPHERES; i++ ) { if( m_hSphere[i] != 0 ) { m_iActiveSpheres++; } else { empty = i; } } if( m_iActiveSpheres >= N_SPHERES ) return FALSE; Vector vecSrc = m_hRecharger->pev->origin; CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); pEntity->pev->velocity = pev->origin - vecSrc; pEntity->CircleInit( this ); m_hSphere[empty] = pEntity; return TRUE; } void CNihilanth::TargetSphere( USE_TYPE useType, float value ) { int i; CBaseMonster *pSphere; for( i = 0; i < N_SPHERES; i++ ) { if( m_hSphere[i] != 0 ) { pSphere = m_hSphere[i]->MyMonsterPointer(); if( pSphere->m_hEnemy == 0 ) break; } } if( i == N_SPHERES ) { return; } Vector vecSrc, vecAngles; GetAttachment( 2, vecSrc, vecAngles ); UTIL_SetOrigin( pSphere->pev, vecSrc ); pSphere->Use( this, this, useType, value ); pSphere->pev->velocity = m_vecDesired * RANDOM_FLOAT( 50, 100 ) + Vector( RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ) ); } void CNihilanth::HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case 1: // shoot break; case 2: // zen if( m_hEnemy != 0 ) { if( RANDOM_LONG( 0, 4 ) == 0 ) EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x3000 ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( 256 ); // radius WRITE_BYTE( 128 ); // R WRITE_BYTE( 128 ); // G WRITE_BYTE( 255 ); // B WRITE_BYTE( 10 ); // life * 10 WRITE_COORD( 128 ); // decay MESSAGE_END(); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x4000 ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( 256 ); // radius WRITE_BYTE( 128 ); // R WRITE_BYTE( 128 ); // G WRITE_BYTE( 255 ); // B WRITE_BYTE( 10 ); // life * 10 WRITE_COORD( 128 ); // decay MESSAGE_END(); m_flShootTime = gpGlobals->time; m_flShootEnd = gpGlobals->time + 1.0f; } break; case 3: // prayer if( m_hEnemy != 0 ) { char szText[128]; sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); if( pTrigger != NULL || pTouch != NULL ) { EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); Vector vecSrc, vecAngles; GetAttachment( 2, vecSrc, vecAngles ); CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); pEntity->pev->velocity = pev->origin - vecSrc; pEntity->TeleportInit( this, m_hEnemy, pTrigger, pTouch ); } else { m_iTeleport++; // unexpected failure EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); ALERT( at_aiconsole, "nihilanth can't target %s\n", szText ); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x3000 ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( 256 ); // radius WRITE_BYTE( 128 ); // R WRITE_BYTE( 128 ); // G WRITE_BYTE( 255 ); // B WRITE_BYTE( 10 ); // life * 10 WRITE_COORD( 128 ); // decay MESSAGE_END(); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x4000 ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( 256 ); // radius WRITE_BYTE( 128 ); // R WRITE_BYTE( 128 ); // G WRITE_BYTE( 255 ); // B WRITE_BYTE( 10 ); // life * 10 WRITE_COORD( 128 ); // decay MESSAGE_END(); m_flShootTime = gpGlobals->time; m_flShootEnd = gpGlobals->time + 1.0f; } } break; case 4: // get a sphere { if( m_hRecharger != 0 ) { if( !EmitSphere() ) { m_hRecharger = NULL; } } } break; case 5: // start up sphere machine { EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pRechargeSounds ), 1.0, 0.2 ); } break; case 6: if( m_hEnemy != 0 ) { Vector vecSrc, vecAngles; GetAttachment( 2, vecSrc, vecAngles ); CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); pEntity->pev->velocity = pev->origin - vecSrc; pEntity->ZapInit( m_hEnemy ); } break; case 7: /* Vector vecSrc, vecAngles; GetAttachment( 0, vecSrc, vecAngles ); CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); pEntity->pev->velocity = Vector( RANDOM_FLOAT( -0.7f, 0.7f ), RANDOM_FLOAT( -0.7f, 0.7f ), 1.0f ) * 600.0f; pEntity->GreenBallInit(); */ break; } } void CNihilanth::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { switch( useType ) { case USE_OFF: { CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, m_szDeadTouch ); if( pTouch ) { if( m_hEnemy != 0 ) { pTouch->Touch( m_hEnemy ); } // if the player is using "notarget", the ending sequence won't fire unless we catch it here else { CBaseEntity *pEntity = UTIL_FindEntityByClassname( NULL, "player" ); if( pEntity != NULL && pEntity->IsAlive() ) { pTouch->Touch( pEntity ); } } } } break; case USE_ON: if( m_irritation == 0 ) { m_irritation = 1; } break; case USE_SET: break; case USE_TOGGLE: break; } } int CNihilanth::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) { if( pevInflictor->owner == edict() ) return 0; if( flDamage >= pev->health ) { pev->health = 1; if( m_irritation != 3 ) return 0; } PainSound(); pev->health -= flDamage; return 0; } void CNihilanth::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) { if( m_irritation == 3 ) m_irritation = 2; if( m_irritation == 2 && ptr->iHitgroup == 2 && flDamage > 2 ) m_irritation = 3; if( m_irritation != 3 ) { Vector vecBlood = ( ptr->vecEndPos - pev->origin ).Normalize(); UTIL_BloodStream( ptr->vecEndPos, vecBlood, BloodColor(), flDamage + ( 100 - 100 * ( pev->health / gSkillData.nihilanthHealth ) ) ); } // SpawnBlood( ptr->vecEndPos, BloodColor(), flDamage * 5.0 );// a little surface blood. AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); } CBaseEntity *CNihilanth::RandomTargetname( const char *szName ) { int total = 0; CBaseEntity *pEntity = NULL; CBaseEntity *pNewEntity = NULL; while( ( pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName ) ) != NULL ) { total++; if( RANDOM_LONG( 0, total - 1 ) < 1 ) pEntity = pNewEntity; } return pEntity; } //========================================================= // Controller bouncy ball attack //========================================================= void CNihilanthHVR::Spawn( void ) { Precache(); pev->rendermode = kRenderTransAdd; pev->renderamt = 255; pev->scale = 3.0f; } void CNihilanthHVR::Precache( void ) { PRECACHE_MODEL( "sprites/flare6.spr" ); PRECACHE_MODEL( "sprites/nhth1.spr" ); PRECACHE_MODEL( "sprites/exit1.spr" ); PRECACHE_MODEL( "sprites/tele1.spr" ); PRECACHE_MODEL( "sprites/animglow01.spr" ); PRECACHE_MODEL( "sprites/xspark4.spr" ); PRECACHE_MODEL( "sprites/muzzleflash3.spr" ); PRECACHE_SOUND( "debris/zap4.wav" ); PRECACHE_SOUND( "weapons/electro4.wav" ); PRECACHE_SOUND( "x/x_teleattack1.wav" ); } void CNihilanthHVR::CircleInit( CBaseEntity *pTarget ) { pev->movetype = MOVETYPE_NOCLIP; pev->solid = SOLID_NOT; // SET_MODEL( edict(), "sprites/flare6.spr" ); // pev->scale = 3.0; // SET_MODEL( edict(), "sprites/xspark4.spr" ); SET_MODEL( edict(), "sprites/muzzleflash3.spr" ); pev->rendercolor.x = 255; pev->rendercolor.y = 224; pev->rendercolor.z = 192; pev->scale = 2.0f; m_nFrames = 1; pev->renderamt = 255; UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); UTIL_SetOrigin( pev, pev->origin ); SetThink( &CNihilanthHVR::HoverThink ); SetTouch( &CNihilanthHVR::BounceTouch ); pev->nextthink = gpGlobals->time + 0.1f; m_hTargetEnt = pTarget; } CBaseEntity *CNihilanthHVR::RandomClassname( const char *szName ) { int total = 0; CBaseEntity *pEntity = NULL; CBaseEntity *pNewEntity = NULL; while( ( pNewEntity = UTIL_FindEntityByClassname( pNewEntity, szName ) ) != NULL ) { total++; if( RANDOM_LONG( 0, total - 1 ) < 1 ) pEntity = pNewEntity; } return pEntity; } void CNihilanthHVR::HoverThink( void ) { pev->nextthink = gpGlobals->time + 0.1f; if( m_hTargetEnt != 0 ) { CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 16 * N_SCALE ) ); } else { UTIL_Remove( this ); } if( RANDOM_LONG( 0, 99 ) < 5 ) { /* CBaseEntity *pOther = RandomClassname( STRING( pev->classname ) ); if( pOther && pOther != this ) { MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTS ); WRITE_SHORT( this->entindex() ); WRITE_SHORT( pOther->entindex() ); WRITE_SHORT( g_sModelIndexLaser ); WRITE_BYTE( 0 ); // framestart WRITE_BYTE( 0 ); // framerate WRITE_BYTE( 10 ); // life WRITE_BYTE( 80 ); // width WRITE_BYTE( 80 ); // noise WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 128 ); // r, g, b WRITE_BYTE( 64 ); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 30 ); // speed MESSAGE_END(); } */ /* MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTS ); WRITE_SHORT( this->entindex() ); WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); WRITE_SHORT( g_sModelIndexLaser ); WRITE_BYTE( 0 ); // framestart WRITE_BYTE( 0 ); // framerate WRITE_BYTE( 10 ); // life WRITE_BYTE( 80 ); // width WRITE_BYTE( 80 ); // noise WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 128 ); // r, g, b WRITE_BYTE( 64 ); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 30 ); // speed MESSAGE_END(); */ } pev->frame = ( (int)pev->frame + 1 ) % m_nFrames; } void CNihilanthHVR::ZapInit( CBaseEntity *pEnemy ) { pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; SET_MODEL( edict(), "sprites/nhth1.spr" ); pev->rendercolor.x = 255; pev->rendercolor.y = 255; pev->rendercolor.z = 255; pev->scale = 2.0f; pev->velocity = ( pEnemy->pev->origin - pev->origin ).Normalize() * 200.0f; m_hEnemy = pEnemy; SetThink( &CNihilanthHVR::ZapThink ); SetTouch( &CNihilanthHVR::ZapTouch ); pev->nextthink = gpGlobals->time + 0.1f; EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 ); } void CNihilanthHVR::ZapThink( void ) { pev->nextthink = gpGlobals->time + 0.05f; // check world boundaries if( m_hEnemy == 0 || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096 ) { SetTouch( NULL ); UTIL_Remove( this ); return; } if( pev->velocity.Length() < 2000 ) { pev->velocity = pev->velocity * 1.2f; } // MovetoTarget( m_hEnemy->Center() ); if( ( m_hEnemy->Center() - pev->origin ).Length() < 256 ) { TraceResult tr; UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, edict(), &tr ); CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); if( pEntity != NULL && pEntity->pev->takedamage ) { ClearMultiDamage(); pEntity->TraceAttack( pev, gSkillData.nihilanthZap, pev->velocity, &tr, DMG_SHOCK ); ApplyMultiDamage( pev, pev ); } MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTPOINT ); WRITE_SHORT( entindex() ); WRITE_COORD( tr.vecEndPos.x ); WRITE_COORD( tr.vecEndPos.y ); WRITE_COORD( tr.vecEndPos.z ); WRITE_SHORT( g_sModelIndexLaser ); WRITE_BYTE( 0 ); // frame start WRITE_BYTE( 10 ); // framerate WRITE_BYTE( 3 ); // life WRITE_BYTE( 20 ); // width WRITE_BYTE( 20 ); // noise WRITE_BYTE( 64 ); // r, g, b WRITE_BYTE( 196 ); // r, g, b WRITE_BYTE( 255); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 10 ); // speed MESSAGE_END(); UTIL_EmitAmbientSound( edict(), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); SetTouch( NULL ); UTIL_Remove( this ); pev->nextthink = gpGlobals->time + 0.2f; return; } pev->frame = (int)( pev->frame + 1 ) % 11; MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex( ) ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( 128 ); // radius WRITE_BYTE( 128 ); // R WRITE_BYTE( 128 ); // G WRITE_BYTE( 255 ); // B WRITE_BYTE( 10 ); // life * 10 WRITE_COORD( 128 ); // decay MESSAGE_END(); // Crawl(); } void CNihilanthHVR::ZapTouch( CBaseEntity *pOther ) { UTIL_EmitAmbientSound( edict(), pev->origin, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, RANDOM_LONG( 90, 95 ) ); RadiusDamage( pev, pev, 50, CLASS_NONE, DMG_SHOCK ); pev->velocity = pev->velocity * 0; /* for( int i = 0; i < 10; i++ ) { Crawl(); } */ SetTouch( NULL ); UTIL_Remove( this ); pev->nextthink = gpGlobals->time + 0.2f; } void CNihilanthHVR::TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ) { pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; pev->rendercolor.x = 255; pev->rendercolor.y = 255; pev->rendercolor.z = 255; pev->velocity.z *= 0.2f; SET_MODEL( edict(), "sprites/exit1.spr" ); m_pNihilanth = pOwner; m_hEnemy = pEnemy; m_hTargetEnt = pTarget; m_hTouch = pTouch; SetThink( &CNihilanthHVR::TeleportThink ); SetTouch( &CNihilanthHVR::TeleportTouch ); pev->nextthink = gpGlobals->time + 0.1f; EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "x/x_teleattack1.wav", 1, 0.2, 0, 100 ); } void CNihilanthHVR::GreenBallInit() { pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; pev->rendercolor.x = 255; pev->rendercolor.y = 255; pev->rendercolor.z = 255; pev->scale = 1.0f; SET_MODEL( edict(), "sprites/exit1.spr" ); SetTouch( &CNihilanthHVR::RemoveTouch ); } void CNihilanthHVR::TeleportThink( void ) { pev->nextthink = gpGlobals->time + 0.1f; // check world boundaries if( m_hEnemy == 0 || !m_hEnemy->IsAlive() || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096 ) { STOP_SOUND( edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); UTIL_Remove( this ); return; } if( ( m_hEnemy->Center() - pev->origin).Length() < 128 ) { STOP_SOUND( edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); UTIL_Remove( this ); if( m_hTargetEnt != 0 ) m_hTargetEnt->Use( m_hEnemy, m_hEnemy, USE_ON, 1.0 ); if( m_hTouch != 0 && m_hEnemy != 0 ) m_hTouch->Touch( m_hEnemy ); } else { MovetoTarget( m_hEnemy->Center() ); } MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( 256 ); // radius WRITE_BYTE( 0 ); // R WRITE_BYTE( 255 ); // G WRITE_BYTE( 0 ); // B WRITE_BYTE( 10 ); // life * 10 WRITE_COORD( 256 ); // decay MESSAGE_END(); pev->frame = (int)( pev->frame + 1 ) % 20; } void CNihilanthHVR::AbsorbInit( void ) { SetThink( &CNihilanthHVR::DissipateThink ); pev->renderamt = 255; MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTS ); WRITE_SHORT( this->entindex() ); WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); WRITE_SHORT( g_sModelIndexLaser ); WRITE_BYTE( 0 ); // framestart WRITE_BYTE( 0 ); // framerate WRITE_BYTE( 50 ); // life WRITE_BYTE( 80 ); // width WRITE_BYTE( 80 ); // noise WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 128 ); // r, g, b WRITE_BYTE( 64 ); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 30 ); // speed MESSAGE_END(); } void CNihilanthHVR::TeleportTouch( CBaseEntity *pOther ) { CBaseEntity *pEnemy = m_hEnemy; if( pOther == pEnemy ) { if( m_hTargetEnt != 0 ) m_hTargetEnt->Use( pEnemy, pEnemy, USE_ON, 1.0 ); if( m_hTouch != 0 && pEnemy != NULL ) m_hTouch->Touch( pEnemy ); } else { m_pNihilanth->MakeFriend( pev->origin ); } SetTouch( NULL ); STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); UTIL_Remove( this ); } void CNihilanthHVR::DissipateThink( void ) { pev->nextthink = gpGlobals->time + 0.1f; if( pev->scale > 5.0f ) UTIL_Remove( this ); pev->renderamt -= 2; pev->scale += 0.1f; if( m_hTargetEnt != 0 ) { CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 4096 ) ); } else { UTIL_Remove( this ); } MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() ); // entity, attachment WRITE_COORD( pev->origin.x ); // origin WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_COORD( pev->renderamt ); // radius WRITE_BYTE( 255 ); // R WRITE_BYTE( 192 ); // G WRITE_BYTE( 64 ); // B WRITE_BYTE( 2 ); // life * 10 WRITE_COORD( 0 ); // decay MESSAGE_END(); } BOOL CNihilanthHVR::CircleTarget( Vector vecTarget ) { BOOL fClose = FALSE; Vector vecDest = vecTarget; Vector vecEst = pev->origin + pev->velocity * 0.5; Vector vecSrc = pev->origin; vecDest.z = 0; vecEst.z = 0; vecSrc.z = 0; float d1 = ( vecDest - vecSrc ).Length() - 24 * N_SCALE; float d2 = ( vecDest - vecEst ).Length() - 24 * N_SCALE; if( m_vecIdeal == Vector( 0, 0, 0 ) ) { m_vecIdeal = pev->velocity; } if( d1 < 0 && d2 <= d1 ) { // ALERT( at_console, "too close\n" ); m_vecIdeal = m_vecIdeal - ( vecDest - vecSrc ).Normalize() * 50; } else if( d1 > 0 && d2 >= d1 ) { // ALERT( at_console, "too far\n" ); m_vecIdeal = m_vecIdeal + ( vecDest - vecSrc ).Normalize() * 50; } pev->avelocity.z = d1 * 20; if( d1 < 32 ) { fClose = TRUE; } m_vecIdeal = m_vecIdeal + Vector( RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 )); m_vecIdeal = Vector( m_vecIdeal.x, m_vecIdeal.y, 0 ).Normalize() * 200 /* + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize() * 32 */ + Vector( 0, 0, m_vecIdeal.z ); // m_vecIdeal = m_vecIdeal + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize() * 2; // move up/down d1 = vecTarget.z - pev->origin.z; if( d1 > 0 && m_vecIdeal.z < 200 ) m_vecIdeal.z += 20; else if( d1 < 0 && m_vecIdeal.z > -200 ) m_vecIdeal.z -= 20; pev->velocity = m_vecIdeal; // ALERT( at_console, "%.0f %.0f %.0f\n", m_vecIdeal.x, m_vecIdeal.y, m_vecIdeal.z ); return fClose; } void CNihilanthHVR::MovetoTarget( Vector vecTarget ) { if( m_vecIdeal == Vector( 0, 0, 0 ) ) { m_vecIdeal = pev->velocity; } // accelerate float flSpeed = m_vecIdeal.Length(); if( flSpeed > 300 ) { m_vecIdeal = m_vecIdeal.Normalize() * 300; } m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 300; pev->velocity = m_vecIdeal; } void CNihilanthHVR::Crawl( void ) { Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize(); Vector vecPnt = pev->origin + pev->velocity * 0.2 + vecAim * 128; MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTPOINT ); WRITE_SHORT( entindex() ); WRITE_COORD( vecPnt.x ); WRITE_COORD( vecPnt.y ); WRITE_COORD( vecPnt.z ); WRITE_SHORT( g_sModelIndexLaser ); WRITE_BYTE( 0 ); // frame start WRITE_BYTE( 10 ); // framerate WRITE_BYTE( 3 ); // life WRITE_BYTE( 20 ); // width WRITE_BYTE( 80 ); // noise WRITE_BYTE( 64 ); // r, g, b WRITE_BYTE( 128 ); // r, g, b WRITE_BYTE( 255); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 10 ); // speed MESSAGE_END(); } void CNihilanthHVR::RemoveTouch( CBaseEntity *pOther ) { STOP_SOUND( edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); UTIL_Remove( this ); } void CNihilanthHVR::BounceTouch( CBaseEntity *pOther ) { Vector vecDir = m_vecIdeal.Normalize(); TraceResult tr = UTIL_GetGlobalTrace(); float n = -DotProduct( tr.vecPlaneNormal, vecDir ); vecDir = 2.0f * tr.vecPlaneNormal * n + vecDir; m_vecIdeal = vecDir * m_vecIdeal.Length(); } #endif