/*** * * 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 !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) //========================================================= // CONTROLLER //========================================================= #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "effects.h" #include "schedule.h" #include "weapons.h" #include "squadmonster.h" //========================================================= // Monster's Anim Events Go Here //========================================================= #define CONTROLLER_AE_HEAD_OPEN 1 #define CONTROLLER_AE_BALL_SHOOT 2 #define CONTROLLER_AE_SMALL_SHOOT 3 #define CONTROLLER_AE_POWERUP_FULL 4 #define CONTROLLER_AE_POWERUP_HALF 5 #define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs class CController : public CSquadMonster { public: virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; void Spawn( void ); void Precache( void ); void UpdateOnRemove(); void SetYawSpeed( void ); int Classify( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); void RunAI( void ); BOOL CheckRangeAttack1( float flDot, float flDist ); // balls BOOL CheckRangeAttack2( float flDot, float flDist ); // head BOOL CheckMeleeAttack1( float flDot, float flDist ); // block, throw Schedule_t *GetSchedule( void ); Schedule_t *GetScheduleOfType( int Type ); void StartTask( Task_t *pTask ); void RunTask( Task_t *pTask ); CUSTOM_SCHEDULES void Stop( void ); void Move( float flInterval ); int CheckLocalMove( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ); void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); void SetActivity( Activity NewActivity ); BOOL ShouldAdvanceRoute( float flWaypointDist ); int LookupFloat(); float m_flNextFlinch; float m_flShootTime; float m_flShootEnd; void PainSound( void ); void AlertSound( void ); void IdleSound( void ); void AttackSound( void ); void DeathSound( void ); static const char *pAttackSounds[]; static const char *pIdleSounds[]; static const char *pAlertSounds[]; static const char *pPainSounds[]; static const char *pDeathSounds[]; int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); void Killed( entvars_t *pevAttacker, int iGib ); void GibMonster( void ); CSprite *m_pBall[2]; // hand balls int m_iBall[2]; // how bright it should be float m_iBallTime[2]; // when it should be that color int m_iBallCurrent[2]; // current brightness Vector m_vecEstVelocity; Vector m_velocity; int m_fInCombat; }; LINK_ENTITY_TO_CLASS( monster_alien_controller, CController ) TYPEDESCRIPTION CController::m_SaveData[] = { DEFINE_ARRAY( CController, m_pBall, FIELD_CLASSPTR, 2 ), DEFINE_ARRAY( CController, m_iBall, FIELD_INTEGER, 2 ), DEFINE_ARRAY( CController, m_iBallTime, FIELD_TIME, 2 ), DEFINE_ARRAY( CController, m_iBallCurrent, FIELD_INTEGER, 2 ), DEFINE_FIELD( CController, m_vecEstVelocity, FIELD_VECTOR ), }; IMPLEMENT_SAVERESTORE( CController, CSquadMonster ) const char *CController::pAttackSounds[] = { "controller/con_attack1.wav", "controller/con_attack2.wav", "controller/con_attack3.wav", }; const char *CController::pIdleSounds[] = { "controller/con_idle1.wav", "controller/con_idle2.wav", "controller/con_idle3.wav", "controller/con_idle4.wav", "controller/con_idle5.wav", }; const char *CController::pAlertSounds[] = { "controller/con_alert1.wav", "controller/con_alert2.wav", "controller/con_alert3.wav", }; const char *CController::pPainSounds[] = { "controller/con_pain1.wav", "controller/con_pain2.wav", "controller/con_pain3.wav", }; const char *CController::pDeathSounds[] = { "controller/con_die1.wav", "controller/con_die2.wav", }; //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CController::Classify( void ) { return CLASS_ALIEN_MILITARY; } //========================================================= // SetYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= void CController::SetYawSpeed( void ) { int ys; ys = 120; #if 0 switch ( m_Activity ) { } #endif pev->yaw_speed = ys; } int CController::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { // HACK HACK -- until we fix this. if( IsAlive() ) PainSound(); return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } void CController::Killed( entvars_t *pevAttacker, int iGib ) { // shut off balls /* m_iBall[0] = 0; m_iBallTime[0] = gpGlobals->time + 4.0; m_iBall[1] = 0; m_iBallTime[1] = gpGlobals->time + 4.0; */ // fade balls if( m_pBall[0] ) { m_pBall[0]->SUB_StartFadeOut(); m_pBall[0] = NULL; } if( m_pBall[1] ) { m_pBall[1]->SUB_StartFadeOut(); m_pBall[1] = NULL; } CSquadMonster::Killed( pevAttacker, iGib ); } void CController::GibMonster( void ) { // delete balls if( m_pBall[0] ) { UTIL_Remove( m_pBall[0] ); m_pBall[0] = NULL; } if( m_pBall[1] ) { UTIL_Remove( m_pBall[1] ); m_pBall[1] = NULL; } CSquadMonster::GibMonster(); } void CController::PainSound( void ) { if( RANDOM_LONG( 0, 5 ) < 2 ) EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); } void CController::AlertSound( void ) { EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); } void CController::IdleSound( void ) { EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pIdleSounds ); } void CController::AttackSound( void ) { EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAttackSounds ); } void CController::DeathSound( void ) { EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. //========================================================= void CController::HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case CONTROLLER_AE_HEAD_OPEN: { Vector vecStart, angleGun; GetAttachment( 0, vecStart, angleGun ); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x1000 ); // entity, attachment WRITE_COORD( vecStart.x ); // origin WRITE_COORD( vecStart.y ); WRITE_COORD( vecStart.z ); WRITE_COORD( 1 ); // radius WRITE_BYTE( 255 ); // R WRITE_BYTE( 192 ); // G WRITE_BYTE( 64 ); // B WRITE_BYTE( 20 ); // life * 10 WRITE_COORD( -32 ); // decay MESSAGE_END(); m_iBall[0] = 192; m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; m_iBall[1] = 255; m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; } break; case CONTROLLER_AE_BALL_SHOOT: { Vector vecStart, angleGun; GetAttachment( 0, vecStart, angleGun ); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x1000 ); // entity, attachment WRITE_COORD( 0 ); // origin WRITE_COORD( 0 ); WRITE_COORD( 0 ); WRITE_COORD( 32 ); // radius WRITE_BYTE( 255 ); // R WRITE_BYTE( 192 ); // G WRITE_BYTE( 64 ); // B WRITE_BYTE( 10 ); // life * 10 WRITE_COORD( 32 ); // decay MESSAGE_END(); CBaseMonster *pBall = (CBaseMonster*)Create( "controller_head_ball", vecStart, pev->angles, edict() ); pBall->pev->velocity = Vector( 0, 0, 32 ); pBall->m_hEnemy = m_hEnemy; m_iBall[0] = 0; m_iBall[1] = 0; } break; case CONTROLLER_AE_SMALL_SHOOT: { AttackSound(); m_flShootTime = gpGlobals->time; m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0; } break; case CONTROLLER_AE_POWERUP_FULL: { m_iBall[0] = 255; m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; m_iBall[1] = 255; m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; } break; case CONTROLLER_AE_POWERUP_HALF: { m_iBall[0] = 192; m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; m_iBall[1] = 192; m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; } break; default: CBaseMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CController::Spawn() { Precache(); SET_MODEL( ENT( pev ), "models/controller.mdl" ); UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_FLY; pev->flags |= FL_FLY; if( FBitSet( pev->spawnflags, SF_MONSTER_REDBLOOD ) ) m_bloodColor = BLOOD_COLOR_RED; else m_bloodColor = BLOOD_COLOR_GREEN; pev->health = gSkillData.controllerHealth; pev->view_ofs = Vector( 0, 0, -2 );// position of the eyes relative to monster's origin. m_flFieldOfView = VIEW_FIELD_FULL;// 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 CController::Precache() { PRECACHE_MODEL( "models/controller.mdl" ); PRECACHE_SOUND_ARRAY( pAttackSounds ); PRECACHE_SOUND_ARRAY( pIdleSounds ); PRECACHE_SOUND_ARRAY( pAlertSounds ); PRECACHE_SOUND_ARRAY( pPainSounds ); PRECACHE_SOUND_ARRAY( pDeathSounds ); PRECACHE_MODEL( "sprites/xspark4.spr" ); UTIL_PrecacheOther( "controller_energy_ball" ); UTIL_PrecacheOther( "controller_head_ball" ); } void CController::UpdateOnRemove() { CBaseEntity::UpdateOnRemove(); if( m_pBall[0] ) { UTIL_Remove( m_pBall[0] ); m_pBall[0] = 0; } if( m_pBall[1] ) { UTIL_Remove( m_pBall[1] ); m_pBall[1] = 0; } } //========================================================= // AI Schedules Specific to this monster //========================================================= // Chase enemy schedule Task_t tlControllerChaseEnemy[] = { { TASK_GET_PATH_TO_ENEMY, (float)128 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, }; Schedule_t slControllerChaseEnemy[] = { { tlControllerChaseEnemy, ARRAYSIZE( tlControllerChaseEnemy ), bits_COND_NEW_ENEMY | bits_COND_TASK_FAILED, 0, "ControllerChaseEnemy" }, }; Task_t tlControllerStrafe[] = { { TASK_WAIT, (float)0.2 }, { TASK_GET_PATH_TO_ENEMY, (float)128 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_WAIT, (float)1 }, }; Schedule_t slControllerStrafe[] = { { tlControllerStrafe, ARRAYSIZE( tlControllerStrafe ), bits_COND_NEW_ENEMY, 0, "ControllerStrafe" }, }; Task_t tlControllerTakeCover[] = { { TASK_WAIT, (float)0.2 }, { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_WAIT, (float)1 }, }; Schedule_t slControllerTakeCover[] = { { tlControllerTakeCover, ARRAYSIZE( tlControllerTakeCover ), bits_COND_NEW_ENEMY, 0, "ControllerTakeCover" }, }; Task_t tlControllerFail[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT, (float)2 }, { TASK_WAIT_PVS, (float)0 }, }; Schedule_t slControllerFail[] = { { tlControllerFail, ARRAYSIZE( tlControllerFail ), 0, 0, "ControllerFail" }, }; DEFINE_CUSTOM_SCHEDULES( CController ) { slControllerChaseEnemy, slControllerStrafe, slControllerTakeCover, slControllerFail, }; IMPLEMENT_CUSTOM_SCHEDULES( CController, CSquadMonster ) //========================================================= // StartTask //========================================================= void CController::StartTask( Task_t *pTask ) { switch( pTask->iTask ) { case TASK_RANGE_ATTACK1: CSquadMonster::StartTask( pTask ); break; case TASK_GET_PATH_TO_ENEMY_LKP: { if( BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, pTask->flData, (m_vecEnemyLKP - pev->origin).Length() + 1024 ) ) { TaskComplete(); } else { // no way to get there =( ALERT( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); TaskFail(); } break; } case TASK_GET_PATH_TO_ENEMY: { CBaseEntity *pEnemy = m_hEnemy; if( pEnemy == NULL ) { TaskFail(); return; } if( BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, pTask->flData, ( pEnemy->pev->origin - pev->origin).Length() + 1024 ) ) { TaskComplete(); } else { // no way to get there =( ALERT( at_aiconsole, "GetPathToEnemy failed!!\n" ); TaskFail(); } break; } default: CSquadMonster::StartTask( pTask ); break; } } Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed ) { Vector vecTo = vecDst - vecSrc; float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed; float b = 0 * DotProduct( vecTo, vecMove ); // why does this work? float c = DotProduct( vecTo, vecTo ); float t; if( a == 0 ) { t = c / ( flSpeed * flSpeed ); } else { t = b * b - 4 * a * c; t = sqrt( t ) / ( 2.0 * a ); float t1 = -b +t; float t2 = -b -t; if( t1 < 0 || t2 < t1 ) t = t2; else t = t1; } // ALERT( at_console, "Intersect %f\n", t ); if( t < 0.1 ) t = 0.1; if( t > 10.0 ) t = 10.0; Vector vecHit = vecTo + vecMove * t; return vecHit.Normalize() * flSpeed; } int CController::LookupFloat() { if( m_velocity.Length() < 32.0 ) { return LookupSequence( "up" ); } UTIL_MakeAimVectors( pev->angles ); float x = DotProduct( gpGlobals->v_forward, m_velocity ); float y = DotProduct( gpGlobals->v_right, m_velocity ); float z = DotProduct( gpGlobals->v_up, m_velocity ); if( fabs( x ) > fabs( y ) && fabs( x ) > fabs( z ) ) { if( x > 0 ) return LookupSequence( "forward" ); else return LookupSequence( "backward" ); } else if( fabs( y ) > fabs( z ) ) { if( y > 0 ) return LookupSequence( "right" ); else return LookupSequence( "left" ); } else { if( z > 0 ) return LookupSequence( "up" ); else return LookupSequence( "down" ); } } //========================================================= // RunTask //========================================================= void CController::RunTask( Task_t *pTask ) { if( m_flShootEnd > gpGlobals->time ) { Vector vecHand, vecAngle; GetAttachment( 2, vecHand, vecAngle ); while( m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time ) { Vector vecSrc = vecHand + pev->velocity * ( m_flShootTime - gpGlobals->time ); Vector vecDir; if( m_hEnemy != 0 ) { if( HasConditions( bits_COND_SEE_ENEMY ) ) { m_vecEstVelocity = m_vecEstVelocity * 0.5 + m_hEnemy->pev->velocity * 0.5; } else { m_vecEstVelocity = m_vecEstVelocity * 0.8; } vecDir = Intersect( vecSrc, m_hEnemy->BodyTarget( pev->origin ), m_vecEstVelocity, gSkillData.controllerSpeedBall ); float delta = 0.03490; // +-2 degree vecDir = vecDir + Vector( RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ) ) * gSkillData.controllerSpeedBall; vecSrc = vecSrc + vecDir * ( gpGlobals->time - m_flShootTime ); CBaseMonster *pBall = (CBaseMonster*)Create( "controller_energy_ball", vecSrc, pev->angles, edict() ); pBall->pev->velocity = vecDir; } m_flShootTime += 0.2; } if( m_flShootTime > m_flShootEnd ) { m_iBall[0] = 64; m_iBallTime[0] = m_flShootEnd; m_iBall[1] = 64; m_iBallTime[1] = m_flShootEnd; m_fInCombat = FALSE; } } switch( pTask->iTask ) { case TASK_WAIT_FOR_MOVEMENT: case TASK_WAIT: case TASK_WAIT_FACE_ENEMY: case TASK_WAIT_PVS: MakeIdealYaw( m_vecEnemyLKP ); ChangeYaw( pev->yaw_speed ); if( m_fSequenceFinished ) { m_fInCombat = FALSE; } CSquadMonster::RunTask( pTask ); if( !m_fInCombat ) { if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) { pev->sequence = LookupActivity( ACT_RANGE_ATTACK1 ); pev->frame = 0; ResetSequenceInfo(); m_fInCombat = TRUE; } else if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) ) { pev->sequence = LookupActivity( ACT_RANGE_ATTACK2 ); pev->frame = 0; ResetSequenceInfo(); m_fInCombat = TRUE; } else { int iFloat = LookupFloat(); if( m_fSequenceFinished || iFloat != pev->sequence ) { pev->sequence = iFloat; pev->frame = 0; ResetSequenceInfo(); } } } break; default: CSquadMonster::RunTask( pTask ); break; } } //========================================================= // GetSchedule - Decides which type of schedule best suits // the monster's current state and conditions. Then calls // monster's member function to get a pointer to a schedule // of the proper type. //========================================================= Schedule_t *CController::GetSchedule( void ) { switch( m_MonsterState ) { case MONSTERSTATE_COMBAT: { // Vector vecTmp = Intersect( Vector( 0, 0, 0 ), Vector( 100, 4, 7 ), Vector( 2, 10, -3 ), 20.0 ); // dead enemy if( HasConditions( bits_COND_LIGHT_DAMAGE ) ) { // m_iFrustration++; } if( HasConditions( bits_COND_HEAVY_DAMAGE ) ) { // m_iFrustration++; } } break; case MONSTERSTATE_IDLE: case MONSTERSTATE_ALERT: break; default: break; } return CSquadMonster::GetSchedule(); } //========================================================= //========================================================= Schedule_t *CController::GetScheduleOfType( int Type ) { // ALERT( at_console, "%d\n", m_iFrustration ); switch( Type ) { case SCHED_CHASE_ENEMY: return slControllerChaseEnemy; case SCHED_RANGE_ATTACK1: return slControllerStrafe; case SCHED_RANGE_ATTACK2: case SCHED_MELEE_ATTACK1: case SCHED_MELEE_ATTACK2: case SCHED_TAKE_COVER_FROM_ENEMY: return slControllerTakeCover; case SCHED_FAIL: return slControllerFail; } return CBaseMonster::GetScheduleOfType( Type ); } //========================================================= // CheckRangeAttack1 - shoot a bigass energy ball out of their head // //========================================================= BOOL CController::CheckRangeAttack1( float flDot, float flDist ) { if( flDot > 0.5 && flDist > 256 && flDist <= 2048 ) { return TRUE; } return FALSE; } BOOL CController::CheckRangeAttack2( float flDot, float flDist ) { if( flDot > 0.5 && flDist > 64 && flDist <= 2048 ) { return TRUE; } return FALSE; } BOOL CController::CheckMeleeAttack1( float flDot, float flDist ) { return FALSE; } void CController::SetActivity( Activity NewActivity ) { CBaseMonster::SetActivity( NewActivity ); switch( m_Activity ) { case ACT_WALK: m_flGroundSpeed = 100; break; default: m_flGroundSpeed = 100; break; } } //========================================================= // RunAI //========================================================= void CController::RunAI( void ) { CBaseMonster::RunAI(); Vector vecStart, angleGun; if( HasMemory( bits_MEMORY_KILLED ) ) return; for( int i = 0; i < 2; i++ ) { if( m_pBall[i] == NULL ) { m_pBall[i] = CSprite::SpriteCreate( "sprites/xspark4.spr", pev->origin, TRUE ); m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); m_pBall[i]->SetAttachment( edict(), ( i + 3 ) ); m_pBall[i]->SetScale( 1.0 ); } float t = m_iBallTime[i] - gpGlobals->time; if( t > 0.1 ) t = 0.1 / t; else t = 1.0; m_iBallCurrent[i] += ( m_iBall[i] - m_iBallCurrent[i] ) * t; m_pBall[i]->SetBrightness( m_iBallCurrent[i] ); GetAttachment( i + 2, vecStart, angleGun ); UTIL_SetOrigin( m_pBall[i]->pev, vecStart ); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex() + 0x1000 * ( i + 3 ) ); // entity, attachment WRITE_COORD( vecStart.x ); // origin WRITE_COORD( vecStart.y ); WRITE_COORD( vecStart.z ); WRITE_COORD( m_iBallCurrent[i] / 8 ); // radius WRITE_BYTE( 255 ); // R WRITE_BYTE( 192 ); // G WRITE_BYTE( 64 ); // B WRITE_BYTE( 5 ); // life * 10 WRITE_COORD( 0 ); // decay MESSAGE_END(); } } extern void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ); void CController::Stop( void ) { m_IdealActivity = GetStoppedActivity(); } #define DIST_TO_CHECK 200 void CController::Move( float flInterval ) { float flWaypointDist; float flCheckDist; float flDist;// how far the lookahead check got before hitting an object. float flMoveDist; Vector vecDir; Vector vecApex; CBaseEntity *pTargetEnt; // Don't move if no valid route if( FRouteClear() ) { ALERT( at_aiconsole, "Tried to move with no route!\n" ); TaskFail(); return; } if( m_flMoveWaitFinished > gpGlobals->time ) return; // Debug, test movement code #if 0 // if( CVAR_GET_FLOAT( "stopmove" ) != 0 ) { if( m_movementGoal == MOVEGOAL_ENEMY ) RouteSimplify( m_hEnemy ); else RouteSimplify( m_hTargetEnt ); FRefreshRoute(); return; } #else // Debug, draw the route // DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); #endif // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer // to that entity for the CheckLocalMove and Triangulate functions. pTargetEnt = NULL; if( m_flGroundSpeed == 0 ) { m_flGroundSpeed = 100; // TaskFail(); // return; } flMoveDist = m_flGroundSpeed * flInterval; do { // local move to waypoint. vecDir = ( m_Route[m_iRouteIndex].vecLocation - pev->origin ).Normalize(); flWaypointDist = ( m_Route[m_iRouteIndex].vecLocation - pev->origin ).Length(); // MakeIdealYaw( m_Route[m_iRouteIndex].vecLocation ); // ChangeYaw( pev->yaw_speed ); // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint if( flWaypointDist < DIST_TO_CHECK ) { flCheckDist = flWaypointDist; } else { flCheckDist = DIST_TO_CHECK; } if( ( m_Route[m_iRouteIndex].iType & ( ~bits_MF_NOT_TO_MASK ) ) == bits_MF_TO_ENEMY ) { // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) pTargetEnt = m_hEnemy; } else if( ( m_Route[m_iRouteIndex].iType & ~bits_MF_NOT_TO_MASK ) == bits_MF_TO_TARGETENT ) { pTargetEnt = m_hTargetEnt; } // !!!BUGBUG - CheckDist should be derived from ground speed. // If this fails, it should be because of some dynamic entity blocking this guy. // We've already checked this path, so we should wait and time out if the entity doesn't move flDist = 0; if( CheckLocalMove( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) { CBaseEntity *pBlocker; // Can't move, stop Stop(); // Blocking entity is in global trace_ent pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); if( pBlocker ) { DispatchBlocked( edict(), pBlocker->edict() ); } if( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) { // Can we still move toward our target? if( flDist < m_flGroundSpeed ) { // Wait for a second m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; //ALERT( at_aiconsole, "Move %s!!!\n", STRING( pBlocker->pev->classname ) ); return; } } else { // try to triangulate around whatever is in the way. if( FTriangulate( pev->origin, m_Route[m_iRouteIndex].vecLocation, flDist, pTargetEnt, &vecApex ) ) { InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); RouteSimplify( pTargetEnt ); } else { ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); Stop(); if( m_moveWaitTime > 0 ) { FRefreshRoute(); m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime * 0.5; } else { TaskFail(); ALERT( at_aiconsole, "Failed to move!\n" ); //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, ( pev->origin + ( vecDir * flCheckDist ) ).z, m_Route[m_iRouteIndex].vecLocation.z ); } return; } } } // UNDONE: this is a hack to quit moving farther than it has looked ahead. if( flCheckDist < flMoveDist ) { MoveExecute( pTargetEnt, vecDir, flCheckDist / m_flGroundSpeed ); // ALERT( at_console, "%.02f\n", flInterval ); AdvanceRoute( flWaypointDist ); flMoveDist -= flCheckDist; } else { MoveExecute( pTargetEnt, vecDir, flMoveDist / m_flGroundSpeed ); if( ShouldAdvanceRoute( flWaypointDist - flMoveDist ) ) { AdvanceRoute( flWaypointDist ); } flMoveDist = 0; } if( MovementIsComplete() ) { Stop(); RouteClear(); } } while( flMoveDist > 0 && flCheckDist > 0 ); // cut corner? if( flWaypointDist < 128 ) { if( m_movementGoal == MOVEGOAL_ENEMY ) RouteSimplify( m_hEnemy ); else RouteSimplify( m_hTargetEnt ); FRefreshRoute(); if( m_flGroundSpeed > 100 ) m_flGroundSpeed -= 40; } else { if( m_flGroundSpeed < 400 ) m_flGroundSpeed += 10; } } BOOL CController::ShouldAdvanceRoute( float flWaypointDist ) { if( flWaypointDist <= 32 ) { return TRUE; } return FALSE; } int CController::CheckLocalMove( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) { TraceResult tr; UTIL_TraceHull( vecStart + Vector( 0, 0, 32 ), vecEnd + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, edict(), &tr ); // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); if( pflDist ) { *pflDist = ( ( tr.vecEndPos - Vector( 0, 0, 32 ) ) - vecStart ).Length();// get the distance. } // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); if( tr.fStartSolid || tr.flFraction < 1.0 ) { if( pTarget && pTarget->edict() == gpGlobals->trace_ent ) return LOCALMOVE_VALID; return LOCALMOVE_INVALID; } return LOCALMOVE_VALID; } void CController::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) { if( m_IdealActivity != m_movementActivity ) m_IdealActivity = m_movementActivity; // ALERT( at_console, "move %.4f %.4f %.4f : %f\n", vecDir.x, vecDir.y, vecDir.z, flInterval ); // float flTotal = m_flGroundSpeed * pev->framerate * flInterval; // UTIL_MoveToOrigin ( ENT( pev ), m_Route[m_iRouteIndex].vecLocation, flTotal, MOVE_STRAFE ); m_velocity = m_velocity * 0.8 + m_flGroundSpeed * vecDir * 0.2; UTIL_MoveToOrigin( ENT( pev ), pev->origin + m_velocity, m_velocity.Length() * flInterval, MOVE_STRAFE ); } //========================================================= // Controller bouncy ball attack //========================================================= class CControllerHeadBall : public CBaseMonster { void Spawn( void ); void Precache( void ); void EXPORT HuntThink( void ); void EXPORT DieThink( void ); void EXPORT BounceTouch( CBaseEntity *pOther ); void MovetoTarget( Vector vecTarget ); void Crawl( void ); int m_flNextAttack; Vector m_vecIdeal; EHANDLE m_hOwner; }; LINK_ENTITY_TO_CLASS( controller_head_ball, CControllerHeadBall ) void CControllerHeadBall::Spawn( void ) { Precache(); // motor pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; SET_MODEL(ENT( pev ), "sprites/xspark4.spr" ); pev->rendermode = kRenderTransAdd; pev->rendercolor.x = 255; pev->rendercolor.y = 255; pev->rendercolor.z = 255; pev->renderamt = 255; pev->scale = 2.0; UTIL_SetSize(pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); UTIL_SetOrigin( pev, pev->origin ); SetThink( &CControllerHeadBall::HuntThink ); SetTouch( &CControllerHeadBall::BounceTouch ); m_vecIdeal = Vector( 0, 0, 0 ); pev->nextthink = gpGlobals->time + 0.1; m_hOwner = Instance( pev->owner ); pev->dmgtime = gpGlobals->time; } void CControllerHeadBall::Precache( void ) { PRECACHE_MODEL( "sprites/xspark1.spr" ); PRECACHE_SOUND( "debris/zap4.wav" ); PRECACHE_SOUND( "weapons/electro4.wav" ); } void CControllerHeadBall::HuntThink( void ) { pev->nextthink = gpGlobals->time + 0.1; pev->renderamt -= 5; 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 / 16 ); // radius WRITE_BYTE( 255 ); // R WRITE_BYTE( 255 ); // G WRITE_BYTE( 255 ); // B WRITE_BYTE( 2 ); // life * 10 WRITE_COORD( 0 ); // decay MESSAGE_END(); // check world boundaries if( gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == 0 || m_hOwner == 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; } MovetoTarget( m_hEnemy->Center() ); if( ( m_hEnemy->Center() - pev->origin ).Length() < 64 ) { TraceResult tr; UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, ENT( pev ), &tr ); CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); if( pEntity != NULL && pEntity->pev->takedamage ) { ClearMultiDamage(); pEntity->TraceAttack( m_hOwner->pev, gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); ApplyMultiDamage( pev, m_hOwner->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( 0 ); // noise WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 10 ); // speed MESSAGE_END(); UTIL_EmitAmbientSound( ENT( pev ), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); m_flNextAttack = gpGlobals->time + 3.0; SetThink( &CControllerHeadBall::DieThink ); pev->nextthink = gpGlobals->time + 0.3; } //Crawl(); } void CControllerHeadBall::DieThink( void ) { UTIL_Remove( this ); } void CControllerHeadBall::MovetoTarget( Vector vecTarget ) { // accelerate float flSpeed = m_vecIdeal.Length(); if( flSpeed == 0 ) { m_vecIdeal = pev->velocity; flSpeed = m_vecIdeal.Length(); } if( flSpeed > 400 ) { m_vecIdeal = m_vecIdeal.Normalize() * 400; } m_vecIdeal = m_vecIdeal + ( vecTarget - pev->origin ).Normalize() * 100; pev->velocity = m_vecIdeal; } void CControllerHeadBall::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.3 + vecAim * 64; 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( 0 ); // noise WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 10 ); // speed MESSAGE_END(); } void CControllerHeadBall::BounceTouch( CBaseEntity *pOther ) { Vector vecDir = m_vecIdeal.Normalize(); TraceResult tr = UTIL_GetGlobalTrace(); float n = -DotProduct( tr.vecPlaneNormal, vecDir ); vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; m_vecIdeal = vecDir * m_vecIdeal.Length(); } class CControllerZapBall : public CBaseMonster { void Spawn( void ); void Precache( void ); void EXPORT AnimateThink( void ); void EXPORT ExplodeTouch( CBaseEntity *pOther ); EHANDLE m_hOwner; }; LINK_ENTITY_TO_CLASS( controller_energy_ball, CControllerZapBall ) void CControllerZapBall::Spawn( void ) { Precache(); // motor pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; SET_MODEL( ENT( pev ), "sprites/xspark4.spr" ); pev->rendermode = kRenderTransAdd; pev->rendercolor.x = 255; pev->rendercolor.y = 255; pev->rendercolor.z = 255; pev->renderamt = 255; pev->scale = 0.5; UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); UTIL_SetOrigin( pev, pev->origin ); SetThink( &CControllerZapBall::AnimateThink ); SetTouch( &CControllerZapBall::ExplodeTouch ); m_hOwner = Instance( pev->owner ); pev->dmgtime = gpGlobals->time; // keep track of when ball spawned pev->nextthink = gpGlobals->time + 0.1; } void CControllerZapBall::Precache( void ) { PRECACHE_MODEL( "sprites/xspark4.spr" ); // PRECACHE_SOUND( "debris/zap4.wav" ); // PRECACHE_SOUND( "weapons/electro4.wav" ); } void CControllerZapBall::AnimateThink( void ) { pev->nextthink = gpGlobals->time + 0.1; pev->frame = ( (int)pev->frame + 1 ) % 11; if( gpGlobals->time - pev->dmgtime > 5 || pev->velocity.Length() < 10 ) { SetTouch( NULL ); UTIL_Remove( this ); } } void CControllerZapBall::ExplodeTouch( CBaseEntity *pOther ) { if( pOther->pev->takedamage ) { TraceResult tr = UTIL_GetGlobalTrace(); entvars_t *pevOwner; if( m_hOwner != 0 ) { pevOwner = m_hOwner->pev; } else { pevOwner = pev; } ClearMultiDamage(); pOther->TraceAttack(pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); ApplyMultiDamage( pevOwner, pevOwner ); UTIL_EmitAmbientSound( ENT( pev ), tr.vecEndPos, "weapons/electro4.wav", 0.3, ATTN_NORM, 0, RANDOM_LONG( 90, 99 ) ); } UTIL_Remove( this ); } #endif //!OEM && !HLDEMO