//========================================================= // Override all damage //========================================================= int CGMan :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) { pev->health = pev->max_health / 2; // always trigger the 50% damage aitrigger if ( flDamage > 0 ) { SetConditions(bits_COND_LIGHT_DAMAGE); } if ( flDamage >= 20 ) { SetConditions(bits_COND_HEAVY_DAMAGE); } return TRUE; }
//========================================================= // CheckAmmo - overridden for the grunt because he actually // uses ammo! (base class doesn't) //========================================================= void CFriend :: CheckAmmo ( void ) { if ( m_cAmmoLoaded <= 0 ) { SetConditions(bits_COND_NO_AMMO_LOADED); } }
//========================================================= // If there's a player around, watch him. //========================================================= void CTalkMonster :: PrescheduleThink ( void ) { if ( !HasConditions ( bits_COND_SEE_CLIENT ) ) { SetConditions ( bits_COND_CLIENT_UNSEEN ); } }
/* ============ Killed ============ */ void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) { unsigned int cCount = 0; BOOL fDone = FALSE; CBaseEntity* theBaseEntity = CBaseEntity::Instance(pevAttacker->owner); if(!theBaseEntity) { theBaseEntity = CBaseEntity::Instance(pevAttacker); } ASSERT(theBaseEntity); theBaseEntity->AwardKill(this->pev); if ( HasMemory( bits_MEMORY_KILLED ) ) { if ( ShouldGibMonster( iGib ) ) CallGibMonster(); return; } Remember( bits_MEMORY_KILLED ); // clear the deceased's sound channels.(may have been firing or reloading when killed) EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM); m_IdealMonsterState = MONSTERSTATE_DEAD; // Make sure this condition is fired too (TakeDamage breaks out before this happens on death) SetConditions( bits_COND_LIGHT_DAMAGE ); // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); if ( pOwner ) { pOwner->DeathNotice( pev ); } if ( ShouldGibMonster( iGib ) ) { CallGibMonster(); return; } else if ( pev->flags & FL_MONSTER ) { SetTouch( NULL ); BecomeDead(); } // don't let the status bar glitch for players.with <0 health. if (pev->health < -99) { pev->health = 0; } //pev->enemy = ENT( pevAttacker );//why? (sjb) m_IdealMonsterState = MONSTERSTATE_DEAD; }
void ParserMgr::InitializeFull() { #ifdef DEBUG_PM_FUNC ScopeTracker st("ParserMgr::InitializeFull", std::this_thread::get_id()); #endif InitData(); InitParsers(); SetExpressions(); SetConditions(); }
//========================================================= // Look - overriden for the roach, which can virtually see // 360 degrees. //========================================================= void CRoach :: Look ( int iDistance ) { CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with CBaseEntity *pPreviousEnt;// the last entity added to the link list int iSighted = 0; // DON'T let visibility information from last frame sit around! ClearConditions( bits_COND_SEE_HATE |bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR ); // don't let monsters outside of the player's PVS act up, or most of the interesting // things will happen before the player gets there! if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) { return; } m_pLink = NULL; pPreviousEnt = this; // Does sphere also limit itself to PVS? // Examine all entities within a reasonable radius // !!!PERFORMANCE - let's trivially reject the ent list before radius searching! while ((pSightEnt = UTIL_FindEntityInSphere( pSightEnt, pev->origin, iDistance )) != NULL) { // only consider ents that can be damaged. !!!temporarily only considering other monsters and clients if ( pSightEnt->IsPlayer() || FBitSet ( pSightEnt->pev->flags, FL_MONSTER ) ) { if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && pSightEnt->pev->health > 0 ) { // NULL the Link pointer for each ent added to the link list. If other ents follow, the will overwrite // this value. If this ent happens to be the last, the list will be properly terminated. pPreviousEnt->m_pLink = pSightEnt; pSightEnt->m_pLink = NULL; pPreviousEnt = pSightEnt; // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when // we see monsters other than the Enemy. switch ( IRelationship ( pSightEnt ) ) { case R_FR: iSighted |= bits_COND_SEE_FEAR; break; case R_NO: break; default: ALERT ( at_console, "%s can't asses %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); break; } } } } SetConditions( iSighted ); }
void CBaseMonster::Look(int iDistance) { int iSighted = 0; ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); m_pLink = NULL; CBaseEntity *pSightEnt = NULL; CBaseEntity *pList[100]; Vector delta = Vector(iDistance, iDistance, iDistance); int count = UTIL_EntitiesInBox(pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT | FL_MONSTER); for (int i = 0; i < count; i++) { pSightEnt = pList[i]; if (pSightEnt != this && pSightEnt->pev->health > 0) { if (IRelationship(pSightEnt) != R_NO && FInViewCone(pSightEnt) && !FBitSet(pSightEnt->pev->flags, FL_NOTARGET) && FVisible(pSightEnt)) { if (pSightEnt->IsPlayer()) iSighted |= bits_COND_SEE_CLIENT; pSightEnt->m_pLink = m_pLink; m_pLink = pSightEnt; if (pSightEnt == m_hEnemy) iSighted |= bits_COND_SEE_ENEMY; switch (IRelationship(pSightEnt)) { case R_NM: iSighted |= bits_COND_SEE_NEMESIS; break; case R_HT: iSighted |= bits_COND_SEE_HATE; break; case R_DL: iSighted |= bits_COND_SEE_DISLIKE; break; case R_FR: iSighted |= bits_COND_SEE_FEAR; break; case R_AL: break; } } } } SetConditions(iSighted); }
int CMGargantua::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { ALERT( at_aiconsole, "CMGargantua::TakeDamage\n"); if ( IsAlive() ) { if ( !(bitsDamageType & GARG_DAMAGE) ) flDamage *= 0.01; if ( bitsDamageType & DMG_BLAST ) SetConditions( bits_COND_LIGHT_DAMAGE ); } return CMBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); }
//========================================================= // NextScheduledTask - increments the ScheduleIndex //========================================================= void CBaseMonster :: NextScheduledTask ( void ) { ASSERT( m_pSchedule != NULL ); m_iTaskStatus = TASKSTATUS_NEW; m_iScheduleIndex++; if ( FScheduleDone() ) { // just completed last task in schedule, so make it invalid by clearing it. SetConditions( bits_COND_SCHEDULE_DONE ); //ClearSchedule(); } }
int CGargantua::TakeDamage( CBaseEntity* pInflictor, CBaseEntity* pAttacker, float flDamage, int bitsDamageType ) { ALERT( at_aiconsole, "CGargantua::TakeDamage\n"); if ( IsAlive() ) { if ( !(bitsDamageType & GARG_DAMAGE) ) flDamage *= 0.01; if ( bitsDamageType & DMG_BLAST ) SetConditions( bits_COND_LIGHT_DAMAGE ); } return CBaseMonster::TakeDamage( pInflictor, pAttacker, flDamage, bitsDamageType ); }
void CGargantua::OnTakeDamage( const CTakeDamageInfo& info ) { ALERT( at_aiconsole, "CGargantua::OnTakeDamage\n"); CTakeDamageInfo newInfo = info; if ( IsAlive() ) { if ( !( newInfo.GetDamageTypes() & GARG_DAMAGE) ) newInfo.GetMutableDamage() *= 0.01; if ( newInfo.GetDamageTypes() & DMG_BLAST ) SetConditions( bits_COND_LIGHT_DAMAGE ); } CBaseMonster::OnTakeDamage( newInfo ); }
void CTalkMonster :: Touch( CBaseEntity *pOther ) { // Did the player touch me? if ( pOther->IsPlayer() ) { // Ignore if pissed at player if ( m_afMemory & bits_MEMORY_PROVOKED ) return; // Stay put during speech if ( IsTalking() ) return; // Heuristic for determining if the player is pushing me away float speed = fabs(pOther->GetAbsVelocity().x) + fabs(pOther->GetAbsVelocity().y); if ( speed > 50 ) { SetConditions( bits_COND_CLIENT_PUSH ); MakeIdealYaw( pOther->GetAbsOrigin() ); } } }
void CMTalkMonster :: TalkTouch( edict_t *pOther ) { // Did the player touch me? if ( UTIL_IsPlayer(pOther) ) { // Ignore if pissed at player if ( m_afMemory & bits_MEMORY_PROVOKED ) return; // Stay put during speech if ( IsTalking() ) return; // Heuristic for determining if the player is pushing me away float speed = fabs(pOther->v.velocity.x) + fabs(pOther->v.velocity.y); if ( speed > 50 ) { SetConditions( bits_COND_CLIENT_PUSH ); MakeIdealYaw( pOther->v.origin ); } } }
/* ============ TakeDamage The damage is coming from inflictor, but get mad at attacker This should be the only function that ever reduces health. bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK Time-based damage: only occurs while the monster is within the trigger_hurt. When a monster is poisoned via an arrow etc it takes all the poison damage at once. GLOBALS ASSUMED SET: g_iSkillLevel ============ */ int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { float flTake; Vector vecDir; if (!pev->takedamage) return 0; if ( !IsAlive() ) { return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } if ( pev->deadflag == DEAD_NO ) { // no pain sound during death animation. PainSound();// "Ouch!" } //!!!LATER - make armor consideration here! flTake = flDamage; // set damage type sustained m_bitsDamageType |= bitsDamageType; // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). vecDir = Vector( 0, 0, 0 ); if (!FNullEnt( pevInflictor )) { CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); if (pInflictor) { vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); vecDir = g_vecAttackDir = vecDir.Normalize(); } } // add to the damage total for clients, which will be sent as a single // message at the end of the frame // todo: remove after combining shotgun blasts? if ( IsPlayer() ) { if ( pevInflictor ) pev->dmg_inflictor = ENT(pevInflictor); pev->dmg_take += flTake; // check for godmode or invincibility if ( pev->flags & FL_GODMODE ) { return 0; } } // if this is a player, move him around! if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) { pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); } // do the damage pev->health -= flTake; // HACKHACK Don't kill monsters in a script. Let them break their scripts first if ( m_MonsterState == MONSTERSTATE_SCRIPT ) { SetConditions( bits_COND_LIGHT_DAMAGE ); return 0; } if ( pev->health <= 0 ) { g_pevLastInflictor = pevInflictor; if ( bitsDamageType & DMG_ALWAYSGIB ) { Killed( pevAttacker, GIB_ALWAYS ); } else if ( bitsDamageType & DMG_NEVERGIB ) { Killed( pevAttacker, GIB_NEVER ); } else { Killed( pevAttacker, GIB_NORMAL ); } g_pevLastInflictor = NULL; return 0; } // react to the damage (get mad) if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) { if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) {// only if the attack was a monster or client! // enemy's last known position is somewhere down the vector that the attack came from. if (pevInflictor) { if (m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) { m_vecEnemyLKP = pevInflictor->origin; } } else { m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); } MakeIdealYaw( m_vecEnemyLKP ); // add pain to the conditions // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and // heavy damage per monster class? if ( flDamage > 0 ) { SetConditions(bits_COND_LIGHT_DAMAGE); } if ( flDamage >= 20 ) { SetConditions(bits_COND_HEAVY_DAMAGE); } } } return 1; }
BOOL CBaseMonster::CineCleanup() { CCineMonster *pOldCine = m_pCine; // am I linked to a cinematic? if(m_pCine) { // okay, reset me to what it thought I was before m_pCine->m_hTargetEnt = NULL; pev->movetype = m_pCine->m_saved_movetype; pev->solid = m_pCine->m_saved_solid; pev->effects = m_pCine->m_saved_effects; } else { // arg, punt pev->movetype = MOVETYPE_STEP; // this is evil pev->solid = SOLID_SLIDEBOX; } m_pCine = NULL; m_hTargetEnt = NULL; m_pGoalEnt = NULL; if(pev->deadflag == DEAD_DYING) { // last frame of death animation? pev->health = 0; pev->framerate = 0.0; pev->solid = SOLID_NOT; SetState(MONSTERSTATE_DEAD); pev->deadflag = DEAD_DEAD; UTIL_SetSize(pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2)); if(pOldCine && FBitSet(pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE)) { SetUse(NULL); // BUGBUG -- This doesn't call Killed() SetThink(NULL); // This will probably break some stuff SetTouch(NULL); } else SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); // This turns off animation & physics in case their origin ends up stuck in the world or something StopAnimation(); pev->movetype = MOVETYPE_NONE; pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place return FALSE; } // If we actually played a sequence if(pOldCine && pOldCine->m_iszPlay) { if(!(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT)) { // reset position Vector new_origin, new_angle; GetBonePosition(0, new_origin, new_angle); // Figure out how far they have moved // We can't really solve this problem because we can't query the movement of the origin relative // to the sequence. We can get the root bone's position as we do here, but there are // cases where the root bone is in a different relative position to the entity's origin // before/after the sequence plays. So we are stuck doing this: // !!!HACKHACK: Float the origin up and drop to floor because some sequences have // irregular motion that can't be properly accounted for. // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. Vector oldOrigin = pev->origin; // UNDONE: ugly hack. Don't move monster if they don't "seem" to move // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly // being set, so animations that really do move won't be caught. if((oldOrigin - new_origin).Length2D() < 8.0) new_origin = oldOrigin; pev->origin.x = new_origin.x; pev->origin.y = new_origin.y; pev->origin.z += 1; pev->flags |= FL_ONGROUND; int drop = DROP_TO_FLOOR(ENT(pev)); // Origin in solid? Set to org at the end of the sequence if(drop < 0) pev->origin = oldOrigin; else if(drop == 0) // Hanging in air? { pev->origin.z = new_origin.z; pev->flags &= ~FL_ONGROUND; } // else entity hit floor, leave there // pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this UTIL_SetOrigin(pev, pev->origin); pev->effects |= EF_NOINTERP; } // We should have some animation to put these guys in, but for now it's idle. // Due to NOINTERP above, there won't be any blending between this anim & the sequence m_Activity = ACT_RESET; } // set them back into a normal state pev->enemy = NULL; if(pev->health > 0) m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState; else { // Dropping out because he got killed // Can't call killed() no attacker and weirdness (late gibbing) may result m_IdealMonsterState = MONSTERSTATE_DEAD; SetConditions(bits_COND_LIGHT_DAMAGE); pev->deadflag = DEAD_DYING; FCheckAITrigger(); pev->deadflag = DEAD_NO; } // SetAnimation( m_MonsterState ); ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT); return TRUE; }
//========================================================= // Look - Base class monster function to find enemies or // food by sight. iDistance is distance ( in units ) that the // monster can see. // // Sets the sight bits of the m_afConditions mask to indicate // which types of entities were sighted. // Function also sets the Looker's m_pLink // to the head of a link list that contains all visible ents. // (linked via each ent's m_pLink field) // //========================================================= void CBaseMonster :: Look ( int iDistance ) { int iSighted = 0; // DON'T let visibility information from last frame sit around! ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); m_pLink = NULL; CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with CBaseEntity *pList[100]; Vector delta = Vector( iDistance, iDistance, iDistance ); // Find only monsters/clients in box, NOT limited to PVS int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); for ( int i = 0; i < count; i++ ) { pSightEnt = pList[i]; if ( pSightEnt != this && pSightEnt->pev->health > 0 ) { // the looker will want to consider this entity // don't check anything else about an entity that can't be seen, or an entity that you don't care about. if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) { if ( pSightEnt->IsPlayer() ) { // if we see a client, remember that (mostly for scripted AI) iSighted |= bits_COND_SEE_CLIENT; } pSightEnt->m_pLink = m_pLink; m_pLink = pSightEnt; if ( pSightEnt == m_hEnemy ) { // we know this ent is visible, so if it also happens to be our enemy, store that now. iSighted |= bits_COND_SEE_ENEMY; } // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when // we see monsters other than the Enemy. switch ( IRelationship ( pSightEnt ) ) { case R_NM: iSighted |= bits_COND_SEE_NEMESIS; break; case R_HT: iSighted |= bits_COND_SEE_HATE; break; case R_DL: iSighted |= bits_COND_SEE_DISLIKE; break; case R_FR: iSighted |= bits_COND_SEE_FEAR; break; case R_AL: break; default: ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); break; } } } } SetConditions( iSighted ); }
// The damage is coming from inflictor, but get mad at attacker // This should be the only function that ever reduces health. // bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK // // Time-based damage: only occurs while the monster is within the trigger_hurt. // When a monster is poisoned via an arrow etc it takes all the poison damage at once. int CBaseMonster::__MAKE_VHOOK(TakeDamage)(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) { if (pev->takedamage == DAMAGE_NO) return 0; if (!IsAlive()) { return DeadTakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); } if (pev->deadflag == DEAD_NO) { // no pain sound during death animation. PainSound(); } // LATER: make armor consideration here! float flTake = flDamage; // set damage type sustained m_bitsDamageType |= bitsDamageType; // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). Vector vecDir(0, 0, 0); if (!FNullEnt(pevInflictor)) { CBaseEntity *pInflictor = CBaseEntity::Instance(pevInflictor); if (pInflictor) { #ifndef PLAY_GAMEDLL vecDir = (pInflictor->Center() - Vector(0, 0, 10) - Center()).Normalize(); #else // TODO: fix test demo vecDir = NormalizeSubtract< float_precision, float, float_precision, float_precision >(Center(), pInflictor->Center() - Vector(0, 0, 10)); #endif vecDir = g_vecAttackDir = vecDir.Normalize(); } } // add to the damage total for clients, which will be sent as a single // message at the end of the frame // TODO: remove after combining shotgun blasts? if (IsPlayer()) { if (pevInflictor) { pev->dmg_inflictor = ENT(pevInflictor); } pev->dmg_take += flTake; } pev->health -= flTake; if (m_MonsterState == MONSTERSTATE_SCRIPT) { SetConditions(bits_COND_LIGHT_DAMAGE); return 0; } if (pev->health <= 0.0f) { g_pevLastInflictor = pevInflictor; if (bitsDamageType & DMG_ALWAYSGIB) Killed(pevAttacker, GIB_ALWAYS); else if (bitsDamageType & DMG_NEVERGIB) Killed(pevAttacker, GIB_NEVER); else Killed(pevAttacker, GIB_NORMAL); g_pevLastInflictor = NULL; return 0; } if ((pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker)) { if (pevAttacker->flags & (FL_MONSTER | FL_CLIENT)) { if (pevInflictor) { if (m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) m_vecEnemyLKP = pevInflictor->origin; } else { m_vecEnemyLKP = pev->origin + (g_vecAttackDir * 64); } MakeIdealYaw(m_vecEnemyLKP); if (flDamage > 20.0f) { SetConditions(bits_COND_LIGHT_DAMAGE); } if (flDamage >= 20.0f) { SetConditions(bits_COND_HEAVY_DAMAGE); } } } return 1; }
void CLeech::SwimThink( void ) { TraceResult tr; float flLeftSide; float flRightSide; float targetSpeed; float targetYaw = 0; CBaseEntity *pTarget; if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) { pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); pev->velocity = g_vecZero; return; } else pev->nextthink = gpGlobals->time + 0.1; targetSpeed = LEECH_SWIM_SPEED; if ( m_waterTime < gpGlobals->time ) RecalculateWaterlevel(); if ( m_stateTime < gpGlobals->time ) SwitchLeechState(); ClearConditions( bits_COND_CAN_MELEE_ATTACK1 ); switch( m_MonsterState ) { case MONSTERSTATE_COMBAT: pTarget = m_hEnemy; if ( !pTarget ) SwitchLeechState(); else { // Chase the enemy's eyes m_height = pTarget->pev->origin.z + pTarget->pev->view_ofs.z - 5; // Clip to viable water area if ( m_height < m_bottom ) m_height = m_bottom; else if ( m_height > m_top ) m_height = m_top; Vector location = pTarget->pev->origin - pev->origin; location.z += (pTarget->pev->view_ofs.z); if ( location.Length() < 40 ) SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); // Turn towards target ent targetYaw = UTIL_VecToYaw( location ); targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( pev->angles.y ) ); if ( targetYaw < (-LEECH_TURN_RATE*0.75) ) targetYaw = (-LEECH_TURN_RATE*0.75); else if ( targetYaw > (LEECH_TURN_RATE*0.75) ) targetYaw = (LEECH_TURN_RATE*0.75); else targetSpeed *= 2; } break; default: if ( m_zTime < gpGlobals->time ) { float newHeight = RANDOM_FLOAT( m_bottom, m_top ); m_height = 0.5 * m_height + 0.5 * newHeight; m_zTime = gpGlobals->time + RANDOM_FLOAT( 1, 4 ); } if ( RANDOM_LONG( 0, 100 ) < 10 ) targetYaw = RANDOM_LONG( -30, 30 ); pTarget = NULL; // oldorigin test if ( (pev->origin - pev->oldorigin).Length() < 1 ) { // If leech didn't move, there must be something blocking it, so try to turn m_sideTime = 0; } break; } m_obstacle = ObstacleDistance( pTarget ); pev->oldorigin = pev->origin; if ( m_obstacle < 0.1 ) m_obstacle = 0.1; // is the way ahead clear? if ( m_obstacle == 1.0 ) { // if the leech is turning, stop the trend. if ( m_flTurning != 0 ) { m_flTurning = 0; } m_fPathBlocked = FALSE; pev->speed = UTIL_Approach( targetSpeed, pev->speed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME ); pev->velocity = gpGlobals->v_forward * pev->speed; } else { m_obstacle = 1.0 / m_obstacle; // IF we get this far in the function, the leader's path is blocked! m_fPathBlocked = TRUE; if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid { Vector vecTest; // measure clearance on left and right to pick the best dir to turn vecTest = pev->origin + (gpGlobals->v_right * LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); flRightSide = tr.flFraction; vecTest = pev->origin + (gpGlobals->v_right * -LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); flLeftSide = tr.flFraction; // turn left, right or random depending on clearance ratio float delta = (flRightSide - flLeftSide); if ( delta > 0.1 || (delta > -0.1 && RANDOM_LONG(0,100)<50) ) m_flTurning = -LEECH_TURN_RATE; else m_flTurning = LEECH_TURN_RATE; } pev->speed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), pev->speed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle ); pev->velocity = gpGlobals->v_forward * pev->speed; } pev->ideal_yaw = m_flTurning + targetYaw; UpdateMotion(); }