You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
636 lines
17 KiB
636 lines
17 KiB
/*** |
|
* |
|
* 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. |
|
* |
|
****/ |
|
//========================================================= |
|
// Squadmonster functions |
|
//========================================================= |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "nodes.h" |
|
#include "monsters.h" |
|
#include "animation.h" |
|
#include "saverestore.h" |
|
#include "squadmonster.h" |
|
#include "plane.h" |
|
|
|
//========================================================= |
|
// Save/Restore |
|
//========================================================= |
|
TYPEDESCRIPTION CSquadMonster::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CSquadMonster, m_hSquadLeader, FIELD_EHANDLE ), |
|
DEFINE_ARRAY( CSquadMonster, m_hSquadMember, FIELD_EHANDLE, MAX_SQUAD_MEMBERS - 1 ), |
|
|
|
// DEFINE_FIELD( CSquadMonster, m_afSquadSlots, FIELD_INTEGER ), // these need to be reset after transitions! |
|
DEFINE_FIELD( CSquadMonster, m_fEnemyEluded, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CSquadMonster, m_flLastEnemySightTime, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( CSquadMonster, m_iMySlot, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CSquadMonster, CBaseMonster ) |
|
|
|
//========================================================= |
|
// 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; |
|
} |
|
|
|
//========================================================= |
|
// 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; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// ScheduleChange |
|
//========================================================= |
|
void CSquadMonster::ScheduleChange ( void ) |
|
{ |
|
VacateSlot(); |
|
} |
|
|
|
//========================================================= |
|
// Killed |
|
//========================================================= |
|
void CSquadMonster::Killed( entvars_t *pevAttacker, int iGib ) |
|
{ |
|
VacateSlot(); |
|
|
|
if( InSquad() ) |
|
{ |
|
MySquadLeader()->SquadRemove( this ); |
|
} |
|
|
|
CBaseMonster::Killed( pevAttacker, iGib ); |
|
} |
|
|
|
// These functions are still awaiting conversion to CSquadMonster |
|
|
|
|
|
//========================================================= |
|
// |
|
// SquadRemove(), remove pRemove from my squad. |
|
// If I am pRemove, promote m_pSquadNext to leader |
|
// |
|
//========================================================= |
|
void CSquadMonster::SquadRemove( CSquadMonster *pRemove ) |
|
{ |
|
ASSERT( pRemove!=NULL ); |
|
ASSERT( this->IsLeader() ); |
|
ASSERT( pRemove->m_hSquadLeader == this ); |
|
|
|
// If I'm the leader, get rid of my squad |
|
if( pRemove == MySquadLeader() ) |
|
{ |
|
for( int i = 0; i < MAX_SQUAD_MEMBERS - 1; i++ ) |
|
{ |
|
CSquadMonster *pMember = MySquadMember( i ); |
|
if( pMember ) |
|
{ |
|
pMember->m_hSquadLeader = NULL; |
|
m_hSquadMember[i] = NULL; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
CSquadMonster *pSquadLeader = MySquadLeader(); |
|
if( pSquadLeader ) |
|
{ |
|
for( int i = 0; i < MAX_SQUAD_MEMBERS - 1; i++ ) |
|
{ |
|
if( pSquadLeader->m_hSquadMember[i] == this ) |
|
{ |
|
pSquadLeader->m_hSquadMember[i] = NULL; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
pRemove->m_hSquadLeader = NULL; |
|
} |
|
|
|
//========================================================= |
|
// |
|
// SquadAdd(), add pAdd to my squad |
|
// |
|
//========================================================= |
|
BOOL CSquadMonster::SquadAdd( CSquadMonster *pAdd ) |
|
{ |
|
ASSERT( pAdd != NULL ); |
|
ASSERT( !pAdd->InSquad() ); |
|
ASSERT( this->IsLeader() ); |
|
|
|
for( int i = 0; i < MAX_SQUAD_MEMBERS - 1; i++ ) |
|
{ |
|
if( m_hSquadMember[i] == 0 ) |
|
{ |
|
m_hSquadMember[i] = pAdd; |
|
pAdd->m_hSquadLeader = this; |
|
return TRUE; |
|
} |
|
} |
|
return FALSE; |
|
// should complain here |
|
} |
|
|
|
//========================================================= |
|
// |
|
// SquadPasteEnemyInfo - called by squad members that have |
|
// current info on the enemy so that it can be stored for |
|
// members who don't have current info. |
|
// |
|
//========================================================= |
|
void CSquadMonster::SquadPasteEnemyInfo( void ) |
|
{ |
|
CSquadMonster *pSquadLeader = MySquadLeader(); |
|
if( pSquadLeader ) |
|
pSquadLeader->m_vecEnemyLKP = m_vecEnemyLKP; |
|
} |
|
|
|
//========================================================= |
|
// |
|
// SquadCopyEnemyInfo - called by squad members who don't |
|
// have current info on the enemy. Reads from the same fields |
|
// in the leader's data that other squad members write to, |
|
// so the most recent data is always available here. |
|
// |
|
//========================================================= |
|
void CSquadMonster::SquadCopyEnemyInfo( void ) |
|
{ |
|
CSquadMonster *pSquadLeader = MySquadLeader(); |
|
if( pSquadLeader ) |
|
m_vecEnemyLKP = pSquadLeader->m_vecEnemyLKP; |
|
} |
|
|
|
//========================================================= |
|
// |
|
// 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 != 0 ) |
|
{ |
|
// 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 ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// |
|
// 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; |
|
} |
|
|
|
//========================================================= |
|
// |
|
// 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.0f ) |
|
{ |
|
if( !SquadAdd( pRecruit ) ) |
|
break; |
|
|
|
squadCount++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// no single member squads |
|
if( squadCount == 1 ) |
|
{ |
|
m_hSquadLeader = NULL; |
|
} |
|
|
|
return squadCount; |
|
} |
|
|
|
//========================================================= |
|
// CheckEnemy |
|
//========================================================= |
|
int CSquadMonster::CheckEnemy( CBaseEntity *pEnemy ) |
|
{ |
|
int iUpdatedLKP; |
|
|
|
iUpdatedLKP = 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( iUpdatedLKP ) |
|
{ |
|
// have new enemy information, so paste to the squad. |
|
SquadPasteEnemyInfo(); |
|
} |
|
else |
|
{ |
|
// enemy unseen, copy from the squad knowledge. |
|
SquadCopyEnemyInfo(); |
|
} |
|
} |
|
|
|
return iUpdatedLKP; |
|
} |
|
|
|
//========================================================= |
|
// 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; |
|
} |
|
|
|
} |
|
} |
|
|
|
BOOL CSquadMonster :: NoFriendlyFire( void ) |
|
{ |
|
return NoFriendlyFire( FALSE ); //default: don't like the player |
|
} |
|
|
|
//========================================================= |
|
// 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. |
|
// |
|
// Can now, also, check whether the player is in the box. LRC |
|
//========================================================= |
|
BOOL CSquadMonster :: NoFriendlyFire( BOOL playerAlly ) |
|
{ |
|
if ( !playerAlly && !InSquad() ) |
|
{ |
|
return TRUE; |
|
} |
|
|
|
CPlane backPlane; |
|
CPlane leftPlane; |
|
CPlane rightPlane; |
|
|
|
Vector vecLeftSide; |
|
Vector vecRightSide; |
|
Vector v_left; |
|
Vector v_dir; |
|
|
|
//!!!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 != 0 ) |
|
{ |
|
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.5f ) ); |
|
// vecRightSide = pev->origin + ( gpGlobals->v_right * ( pev->size.x * 1.5f ) ); |
|
v_dir = gpGlobals->v_right * ( pev->size.x * 1.5f ); |
|
vecLeftSide = pev->origin - v_dir; |
|
vecRightSide = pev->origin + v_dir; |
|
|
|
v_left = gpGlobals->v_right * -1.0f; |
|
|
|
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; |
|
} |
|
} |
|
} |
|
|
|
if (playerAlly) |
|
{ |
|
edict_t *pentPlayer = FIND_CLIENT_IN_PVS( edict() ); |
|
if (!FNullEnt(pentPlayer) && |
|
backPlane.PointInFront ( pentPlayer->v.origin ) && |
|
leftPlane.PointInFront ( pentPlayer->v.origin ) && |
|
rightPlane.PointInFront ( pentPlayer->v.origin ) ) |
|
{ |
|
// the player is in the check volume! Don't shoot! |
|
return FALSE; |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
//========================================================= |
|
// 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; |
|
default: |
|
break; |
|
} |
|
|
|
return CBaseMonster::GetIdealState(); |
|
} |
|
|
|
//========================================================= |
|
// 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; |
|
} |
|
|
|
//========================================================= |
|
// 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 != 0 && pMember->m_hEnemy != pEnemy ) |
|
{ |
|
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::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; |
|
} |
|
|
|
extern Schedule_t slChaseEnemyFailed[]; |
|
|
|
Schedule_t *CSquadMonster::GetScheduleOfType( int iType ) |
|
{ |
|
switch( iType ) |
|
{ |
|
case SCHED_CHASE_ENEMY_FAILED: |
|
{ |
|
return &slChaseEnemyFailed[0]; |
|
} |
|
default: |
|
return CBaseMonster::GetScheduleOfType( iType ); |
|
} |
|
}
|
|
|