//========================================================= // // SquadMakeEnemy - makes everyone in the squad angry at // the same entity. // //========================================================= void CSquadMonster :: SquadMakeEnemy ( CBaseEntity *pEnemy ) { if (!InSquad()) return; if ( !pEnemy ) { ALERT ( at_console, "ERROR: SquadMakeEnemy() - pEnemy is NULL!\n" ); return; } CSquadMonster *pSquadLeader = MySquadLeader( ); for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { CSquadMonster *pMember = pSquadLeader->MySquadMember(i); if (pMember) { // reset members who aren't activly engaged in fighting if (pMember->m_hEnemy != pEnemy && !pMember->HasConditions( bits_COND_SEE_ENEMY)) { if ( pMember->m_hEnemy != NULL) { // remember their current enemy pMember->PushEnemy( pMember->m_hEnemy, pMember->m_vecEnemyLKP ); } // give them a new enemy pMember->m_hEnemy = pEnemy; pMember->m_vecEnemyLKP = pEnemy->pev->origin; pMember->SetConditions ( bits_COND_NEW_ENEMY ); } } } }
//========================================================= // StartMonster //========================================================= void CSquadMonster :: StartMonster( void ) { CBaseMonster :: StartMonster(); if ( ( m_afCapability & bits_CAP_SQUAD ) && !InSquad() ) { if ( HasNetName() ) { // if I have a groupname, I can only recruit if I'm flagged as leader if ( !GetSpawnFlags().Any( SF_SQUADMONSTER_LEADER ) ) { return; } } // try to form squads now. int iSquadSize = SquadRecruit( 1024, 4 ); if ( iSquadSize ) { ALERT ( at_aiconsole, "Squad of %d %s formed\n", iSquadSize, GetClassname() ); } if ( IsLeader() && ClassnameIs( "monster_human_grunt" ) ) { SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack SetSkin( 0 ); } } }
//========================================================= // StartMonster //========================================================= void CSquadMonster :: StartMonster( void ) { CBaseMonster :: StartMonster(); if ( ( m_afCapability & bits_CAP_SQUAD ) && !InSquad() ) { if ( !FStringNull( pev->netname ) ) { // if I have a groupname, I can only recruit if I'm flagged as leader if ( !( pev->spawnflags & SF_SQUADMONSTER_LEADER ) ) { return; } } // try to form squads now. int iSquadSize = SquadRecruit( 1024, 4 ); if ( iSquadSize ) { ALERT ( at_aiconsole, "Squad of %d %s formed\n", iSquadSize, STRING( pev->classname ) ); } if ( IsLeader() && FClassnameIs ( pev, "monster_human_grunt" ) ) { SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack pev->skin = 0; } } }
//========================================================= // Leader boid calls this to form a flock from surrounding boids //========================================================= void CFlockingFlyer :: FormFlock( void ) { if ( !InSquad() ) { // I am my own leader m_pSquadLeader = this; m_pSquadNext = NULL; int squadCount = 1; CBaseEntity *pEntity = NULL; while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, AFLOCK_MAX_RECRUIT_RADIUS )) != NULL) { CBaseMonster *pRecruit = pEntity->MyMonsterPointer( ); if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) { // Can we recruit this guy? if ( FClassnameIs ( pRecruit->pev, "monster_flyer" ) ) { squadCount++; SquadAdd( (CFlockingFlyer *)pRecruit ); } } } } SetThink( &CFlockingFlyer::IdleThink );// now that flock is formed, go to idle and wait for a player to come along. pev->nextthink = gpGlobals->time; }
//========================================================= // VacateSlot //========================================================= void CSquadMonster :: VacateSlot() { if ( m_iMySlot != bits_NO_SLOT && InSquad() ) { // ALERT ( at_aiconsole, "Vacated Slot %d - %d\n", m_iMySlot, m_hSquadLeader->m_afSquadSlots ); MySquadLeader()->m_afSquadSlots &= ~m_iMySlot; m_iMySlot = bits_NO_SLOT; } }
//========================================================= // Killed //========================================================= void CSquadMonster :: Killed( entvars_t *pevAttacker, int iGib ) { VacateSlot(); if ( InSquad() ) { MySquadLeader()->SquadRemove( this ); } CBaseMonster :: Killed ( pevAttacker, iGib ); }
//========================================================= // Killed //========================================================= void CSquadMonster::Killed( const CTakeDamageInfo& info, GibAction gibAction ) { VacateSlot(); if ( InSquad() ) { MySquadLeader()->SquadRemove( this ); } CBaseMonster::Killed( info, gibAction ); }
//========================================================= // // SquadCount(), return the number of members of this squad // callable from leaders & followers // //========================================================= int CSquadMonster :: SquadCount( void ) { if (!InSquad()) return 0; CSquadMonster *pSquadLeader = MySquadLeader(); int squadCount = 0; for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { if (pSquadLeader->MySquadMember(i) != NULL) squadCount++; } return squadCount; }
//========================================================= // FValidateCover - determines whether or not the chosen // cover location is a good one to move to. (currently based // on proximity to others in the squad) //========================================================= BOOL CSquadMonster :: SquadMemberInRange ( const Vector &vecLocation, float flDist ) { if (!InSquad()) return FALSE; CSquadMonster *pSquadLeader = MySquadLeader(); for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { CSquadMonster *pSquadMember = pSquadLeader->MySquadMember(i); if (pSquadMember && (vecLocation - pSquadMember->pev->origin ).Length2D() <= flDist) return TRUE; } return FALSE; }
//========================================================= // FValidateCover - determines whether or not the chosen // cover location is a good one to move to. (currently based // on proximity to others in the squad) //========================================================= BOOL CSquadMonster :: FValidateCover ( const Vector &vecCoverLocation ) { if ( !InSquad() ) { return TRUE; } if (SquadMemberInRange( vecCoverLocation, 128 )) { // another squad member is too close to this piece of cover. return FALSE; } return TRUE; }
//========================================================= // FValidateCover - determines whether or not the chosen // cover location is a good one to move to. (currently based // on proximity to others in the squad) //========================================================= bool CSquadMonster::SquadMemberInRange( const Vector &vecLocation, float flDist ) { if (!InSquad()) return false; CSquadMonster *pSquadLeader = MySquadLeader(); for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { CSquadMonster *pSquadMember = pSquadLeader->MySquadMember(i); if (pSquadMember && (vecLocation - pSquadMember->GetAbsOrigin() ).Length2D() <= flDist) return true; } return false; }
//========================================================= // OccupySlot - if any slots of the passed slots are // available, the monster will be assigned to one. //========================================================= BOOL CSquadMonster :: OccupySlot( int iDesiredSlots ) { int i; int iMask; int iSquadSlots; if ( !InSquad() ) { return TRUE; } if ( SquadEnemySplit() ) { // if the squad members aren't all fighting the same enemy, slots are disabled // so that a squad member doesn't get stranded unable to engage his enemy because // all of the attack slots are taken by squad members fighting other enemies. m_iMySlot = bits_SLOT_SQUAD_SPLIT; return TRUE; } CSquadMonster *pSquadLeader = MySquadLeader(); if ( !( iDesiredSlots ^ pSquadLeader->m_afSquadSlots ) ) { // none of the desired slots are available. return FALSE; } iSquadSlots = pSquadLeader->m_afSquadSlots; for ( i = 0; i < NUM_SLOTS; i++ ) { iMask = 1<<i; if ( iDesiredSlots & iMask ) // am I looking for this bit? { if ( !(iSquadSlots & iMask) ) // Is it already taken? { // No, use this bit pSquadLeader->m_afSquadSlots |= iMask; m_iMySlot = iMask; // ALERT ( at_aiconsole, "Took slot %d - %d\n", i, m_hSquadLeader->m_afSquadSlots ); return TRUE; } } } return FALSE; }
//========================================================= // SquadEnemySplit- returns TRUE if not all squad members // are fighting the same enemy. //========================================================= BOOL CSquadMonster :: SquadEnemySplit ( void ) { if (!InSquad()) return FALSE; CSquadMonster *pSquadLeader = MySquadLeader(); CBaseEntity *pEnemy = pSquadLeader->m_hEnemy; for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { CSquadMonster *pMember = pSquadLeader->MySquadMember(i); if (pMember != NULL && pMember->m_hEnemy != NULL && pMember->m_hEnemy != pEnemy) { return TRUE; } } return FALSE; }
//========================================================= // SquadEnemySplit- returns true if not all squad members // are fighting the same enemy. //========================================================= bool CSquadMonster::SquadEnemySplit() { if (!InSquad()) return false; CSquadMonster *pSquadLeader = MySquadLeader(); CBaseEntity *pEnemy = pSquadLeader->m_hEnemy; for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { CSquadMonster *pMember = pSquadLeader->MySquadMember(i); if (pMember != NULL && pMember->m_hEnemy != NULL && pMember->m_hEnemy != pEnemy) { return true; } } return false; }
//========================================================= // WriteBeamColor - writes a color vector to the network // based on the size of the group. //========================================================= void CHoundeye :: WriteBeamColor ( void ) { BYTE bRed, bGreen, bBlue; if ( InSquad() ) { switch ( SquadCount() ) { case 2: // no case for 0 or 1, cause those are impossible for monsters in Squads. bRed = 101; bGreen = 133; bBlue = 221; break; case 3: bRed = 67; bGreen = 85; bBlue = 255; break; case 4: bRed = 62; bGreen = 33; bBlue = 211; break; default: ALERT ( at_aiconsole, "Unsupported Houndeye SquadSize!\n" ); bRed = 188; bGreen = 220; bBlue = 255; break; } } else { // solo houndeye - weakest beam bRed = 188; bGreen = 220; bBlue = 255; } WRITE_BYTE( bRed ); WRITE_BYTE( bGreen ); WRITE_BYTE( bBlue ); }
//========================================================= // GetIdealState - surveys the Conditions information available // and finds the best new state for a monster. //========================================================= MONSTERSTATE CSquadMonster :: GetIdealState ( void ) { int iConditions; iConditions = IScheduleFlags(); // If no schedule conditions, the new ideal state is probably the reason we're in here. switch ( m_MonsterState ) { case MONSTERSTATE_IDLE: case MONSTERSTATE_ALERT: if ( HasConditions ( bits_COND_NEW_ENEMY ) && InSquad() ) { SquadMakeEnemy ( m_hEnemy ); } break; } return CBaseMonster :: GetIdealState(); }
//========================================================= // CheckEnemy //========================================================= bool CSquadMonster::CheckEnemy( CBaseEntity *pEnemy ) { bool bUpdatedLKP = CBaseMonster::CheckEnemy( m_hEnemy ); // communicate with squad members about the enemy IF this individual has the same enemy as the squad leader. if ( InSquad() && (CBaseEntity *)m_hEnemy == MySquadLeader()->m_hEnemy ) { if ( bUpdatedLKP ) { // have new enemy information, so paste to the squad. SquadPasteEnemyInfo(); } else { // enemy unseen, copy from the squad knowledge. SquadCopyEnemyInfo(); } } return bUpdatedLKP; }
//========================================================= // AlertSound //========================================================= void CHoundeye :: AlertSound ( void ) { if ( InSquad() && !IsLeader() ) { return; // only leader makes ALERT sound. } switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM ); break; } }
//========================================================= // FCanActiveIdle //========================================================= BOOL CHoundeye :: FCanActiveIdle ( void ) { if ( InSquad() ) { CSquadMonster *pSquadLeader = MySquadLeader(); for (int i = 0; i < MAX_SQUAD_MEMBERS;i++) { CSquadMonster *pMember = pSquadLeader->MySquadMember(i); if ( pMember != NULL && pMember != this && pMember->m_iHintNode != NO_NODE ) { // someone else in the group is active idling right now! return FALSE; } } return TRUE; } return TRUE; }
//========================================================= // NoFriendlyFire - checks for possibility of friendly fire // // Builds a large box in front of the grunt and checks to see // if any squad members are in that box. //========================================================= BOOL CSquadMonster :: NoFriendlyFire( void ) { if ( !InSquad() ) { return TRUE; } CPlane backPlane; CPlane leftPlane; CPlane rightPlane; Vector vecLeftSide; Vector vecRightSide; Vector v_left; //!!!BUGBUG - to fix this, the planes must be aligned to where the monster will be firing its gun, not the direction it is facing!!! if ( m_hEnemy != NULL ) { UTIL_MakeVectors ( UTIL_VecToAngles( m_hEnemy->Center() - pev->origin ) ); } else { // if there's no enemy, pretend there's a friendly in the way, so the grunt won't shoot. return FALSE; } //UTIL_MakeVectors ( pev->angles ); vecLeftSide = pev->origin - ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); vecRightSide = pev->origin + ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); v_left = gpGlobals->v_right * -1; leftPlane.InitializePlane ( gpGlobals->v_right, vecLeftSide ); rightPlane.InitializePlane ( v_left, vecRightSide ); backPlane.InitializePlane ( gpGlobals->v_forward, pev->origin ); /* ALERT ( at_console, "LeftPlane: %f %f %f : %f\n", leftPlane.m_vecNormal.x, leftPlane.m_vecNormal.y, leftPlane.m_vecNormal.z, leftPlane.m_flDist ); ALERT ( at_console, "RightPlane: %f %f %f : %f\n", rightPlane.m_vecNormal.x, rightPlane.m_vecNormal.y, rightPlane.m_vecNormal.z, rightPlane.m_flDist ); ALERT ( at_console, "BackPlane: %f %f %f : %f\n", backPlane.m_vecNormal.x, backPlane.m_vecNormal.y, backPlane.m_vecNormal.z, backPlane.m_flDist ); */ CSquadMonster *pSquadLeader = MySquadLeader(); for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { CSquadMonster *pMember = pSquadLeader->MySquadMember(i); if (pMember && pMember != this) { if ( backPlane.PointInFront ( pMember->pev->origin ) && leftPlane.PointInFront ( pMember->pev->origin ) && rightPlane.PointInFront ( pMember->pev->origin) ) { // this guy is in the check volume! Don't shoot! return FALSE; } } } return TRUE; }
//========================================================= // GetScheduleOfType //========================================================= Schedule_t* CHoundeye :: GetScheduleOfType ( int Type ) { if ( m_fAsleep ) { // if the hound is sleeping, must wake and stand! if ( HasConditions( bits_COND_HEAR_SOUND ) ) { CSound *pWakeSound; pWakeSound = PBestSound(); ASSERT( pWakeSound != NULL ); if ( pWakeSound ) { MakeIdealYaw ( pWakeSound->m_vecOrigin ); if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME ) { // awakened by a loud sound return &slHoundWakeUrgent[ 0 ]; } } // sound was not loud enough to scare the bejesus out of houndeye return &slHoundWakeLazy[ 0 ]; } else if ( HasConditions( bits_COND_NEW_ENEMY ) ) { // get up fast, to fight. return &slHoundWakeUrgent[ 0 ]; } else { // hound is waking up on its own return &slHoundWakeLazy[ 0 ]; } } switch ( Type ) { case SCHED_IDLE_STAND: { // we may want to sleep instead of stand! if ( InSquad() && !IsLeader() && !m_fAsleep && RANDOM_LONG(0,29) < 1 ) { return &slHoundSleep[ 0 ]; } else { return CSquadMonster :: GetScheduleOfType( Type ); } } case SCHED_RANGE_ATTACK1: { return &slHoundRangeAttack[ 0 ]; /* if ( InSquad() ) { return &slHoundRangeAttack[ RANDOM_LONG( 0, 1 ) ]; } return &slHoundRangeAttack[ 1 ]; */ } case SCHED_SPECIAL_ATTACK1: { return &slHoundSpecialAttack1[ 0 ]; } case SCHED_GUARD: { return &slHoundGuardPack[ 0 ]; } case SCHED_HOUND_AGITATED: { return &slHoundAgitated[ 0 ]; } case SCHED_HOUND_HOP_RETREAT: { return &slHoundHopRetreat[ 0 ]; } case SCHED_FAIL: { if ( m_MonsterState == MONSTERSTATE_COMBAT ) { if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) { // client in PVS return &slHoundCombatFailPVS[ 0 ]; } else { // client has taken off! return &slHoundCombatFailNoPVS[ 0 ]; } } else { return CSquadMonster :: GetScheduleOfType ( Type ); } } default: { return CSquadMonster :: GetScheduleOfType ( Type ); } } }
//========================================================= // // SquadRecruit(), get some monsters of my classification and // link them as a group. returns the group size // //========================================================= int CSquadMonster :: SquadRecruit( int searchRadius, int maxMembers ) { int squadCount; int iMyClass = Classify();// cache this monster's class // Don't recruit if I'm already in a group if ( InSquad() ) return 0; if ( maxMembers < 2 ) return 0; // I am my own leader m_hSquadLeader = this; squadCount = 1; CBaseEntity *pEntity = NULL; if ( !FStringNull( pev->netname ) ) { // I have a netname, so unconditionally recruit everyone else with that name. pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); while ( pEntity ) { CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer(); if ( pRecruit ) { if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && pRecruit != this ) { // minimum protection here against user error.in worldcraft. if (!SquadAdd( pRecruit )) break; squadCount++; } } pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); } } else { while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, searchRadius )) != NULL) { CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer( ); if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) { // Can we recruit this guy? if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && ( (iMyClass != CLASS_ALIEN_MONSTER) || FStrEq(STRING(pev->classname), STRING(pRecruit->pev->classname))) && FStringNull( pRecruit->pev->netname ) ) { TraceResult tr; UTIL_TraceLine( pev->origin + pev->view_ofs, pRecruit->pev->origin + pev->view_ofs, ignore_monsters, pRecruit->edict(), &tr );// try to hit recruit with a traceline. if ( tr.flFraction == 1.0 ) { if (!SquadAdd( pRecruit )) break; squadCount++; } } } } } // no single member squads if (squadCount == 1) { m_hSquadLeader = NULL; } return squadCount; }
//========================================================= // follower boids execute this code when flocking //========================================================= void CFlockingFlyer :: FlockFollowerThink( void ) { TraceResult tr; Vector vecDist; Vector vecDir; Vector vecDirToLeader; float flDistToLeader; pev->nextthink = gpGlobals->time + 0.1; if ( IsLeader() || !InSquad() ) { // the leader has been killed and this flyer suddenly finds himself the leader. SetThink ( &CFlockingFlyer::FlockLeaderThink ); return; } vecDirToLeader = ( m_pSquadLeader->pev->origin - pev->origin ); flDistToLeader = vecDirToLeader.Length(); // match heading with leader pev->angles = m_pSquadLeader->pev->angles; // // We can see the leader, so try to catch up to it // if ( FInViewCone ( m_pSquadLeader ) ) { // if we're too far away, speed up if ( flDistToLeader > AFLOCK_TOO_FAR ) { m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 1.5; } // if we're too close, slow down else if ( flDistToLeader < AFLOCK_TOO_CLOSE ) { m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; } } else { // wait up! the leader isn't out in front, so we slow down to let him pass m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; } SpreadFlock2(); pev->speed = pev->velocity.Length(); pev->velocity = pev->velocity.Normalize(); // if we are too far from leader, average a vector towards it into our current velocity if ( flDistToLeader > AFLOCK_TOO_FAR ) { vecDirToLeader = vecDirToLeader.Normalize(); pev->velocity = (pev->velocity + vecDirToLeader) * 0.5; } // clamp speeds and handle acceleration if ( m_flGoalSpeed > AFLOCK_FLY_SPEED * 2 ) { m_flGoalSpeed = AFLOCK_FLY_SPEED * 2; } if ( pev->speed < m_flGoalSpeed ) { pev->speed += AFLOCK_ACCELERATE; } else if ( pev->speed > m_flGoalSpeed ) { pev->speed -= AFLOCK_ACCELERATE; } pev->velocity = pev->velocity * pev->speed; BoidAdvanceFrame( ); }