forked from valve/halflife-sdk
264 lines
8.7 KiB
C++
264 lines
8.7 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2001, 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.
|
|
*
|
|
* Use, distribution, and modification of this source code and/or resulting
|
|
* object code is restricted to non-commercial enhancements to products from
|
|
* Valve LLC. All other use, distribution, or modification is prohibited
|
|
* without written permission from Valve LLC.
|
|
*
|
|
****/
|
|
|
|
|
|
#include "extdll.h"
|
|
#include "util.h"
|
|
#include "cbase.h"
|
|
#include "monsters.h"
|
|
#include "soundent.h"
|
|
#include "nodes.h"
|
|
#include "talkmonster.h"
|
|
|
|
|
|
float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once
|
|
|
|
/*********************************************************/
|
|
|
|
|
|
CGraph WorldGraph;
|
|
void CGraph :: InitGraph( void ) { }
|
|
int CGraph :: FLoadGraph ( char *szMapName ) { return FALSE; }
|
|
int CGraph :: AllocNodes ( void ) { return FALSE; }
|
|
int CGraph :: CheckNODFile ( char *szMapName ) { return FALSE; }
|
|
int CGraph :: FSetGraphPointers ( void ) { return 0; }
|
|
void CGraph :: ShowNodeConnections ( int iNode ) { }
|
|
int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) { return 0; }
|
|
|
|
|
|
/*********************************************************/
|
|
|
|
|
|
void CBaseMonster :: ReportAIState( void ) { }
|
|
float CBaseMonster :: ChangeYaw ( int speed ) { return 0; }
|
|
void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { }
|
|
|
|
|
|
void CBaseMonster::CorpseFallThink( void )
|
|
{
|
|
if ( pev->flags & FL_ONGROUND )
|
|
{
|
|
SetThink ( NULL );
|
|
|
|
SetSequenceBox( );
|
|
UTIL_SetOrigin( pev, pev->origin );// link into world.
|
|
}
|
|
else
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
}
|
|
// Call after animation/pose is set up
|
|
void CBaseMonster :: MonsterInitDead( void )
|
|
{
|
|
InitBoneControllers();
|
|
|
|
pev->solid = SOLID_BBOX;
|
|
pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground
|
|
|
|
pev->frame = 0;
|
|
ResetSequenceInfo( );
|
|
pev->framerate = 0;
|
|
|
|
// Copy health
|
|
pev->max_health = pev->health;
|
|
pev->deadflag = DEAD_DEAD;
|
|
|
|
UTIL_SetSize(pev, g_vecZero, g_vecZero );
|
|
UTIL_SetOrigin( pev, pev->origin );
|
|
|
|
// Setup health counters, etc.
|
|
BecomeDead();
|
|
SetThink( CorpseFallThink );
|
|
pev->nextthink = gpGlobals->time + 0.5;
|
|
}
|
|
|
|
|
|
BOOL CBaseMonster :: ShouldFadeOnDeath( void )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CBaseMonster :: FCheckAITrigger ( void )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void CBaseMonster :: KeyValue( KeyValueData *pkvd )
|
|
{
|
|
CBaseToggle::KeyValue( pkvd );
|
|
}
|
|
|
|
int CBaseMonster::IRelationship ( CBaseEntity *pTarget )
|
|
{
|
|
static int iEnemy[14][14] =
|
|
{ // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN
|
|
/*NONE*/ { R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO },
|
|
/*MACHINE*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_DL, R_DL },
|
|
/*PLAYER*/ { R_NO ,R_DL ,R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_DL, R_DL },
|
|
/*HUMANPASSIVE*/{ R_NO ,R_NO ,R_AL ,R_AL ,R_HT ,R_FR ,R_NO ,R_HT ,R_DL ,R_FR ,R_NO ,R_AL, R_NO, R_NO },
|
|
/*HUMANMILITAR*/{ R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_HT ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_HT, R_NO, R_NO },
|
|
/*ALIENMILITAR*/{ R_NO ,R_DL ,R_HT ,R_DL ,R_HT ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO },
|
|
/*ALIENPASSIVE*/{ R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO },
|
|
/*ALIENMONSTER*/{ R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO },
|
|
/*ALIENPREY */{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_FR ,R_NO ,R_DL, R_NO, R_NO },
|
|
/*ALIENPREDATO*/{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_DL, R_NO, R_NO },
|
|
/*INSECT*/ { R_FR ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR, R_NO, R_NO },
|
|
/*PLAYERALLY*/ { R_NO ,R_DL ,R_AL ,R_AL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_NO, R_NO },
|
|
/*PBIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_NO, R_DL },
|
|
/*ABIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_AL ,R_NO ,R_DL ,R_DL ,R_NO ,R_NO ,R_DL, R_DL, R_NO }
|
|
};
|
|
|
|
return iEnemy[ Classify() ][ pTarget->Classify() ];
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// 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 );
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// BestVisibleEnemy - this functions searches the link
|
|
// list whose head is the caller's m_pLink field, and returns
|
|
// a pointer to the enemy entity in that list that is nearest the
|
|
// caller.
|
|
//
|
|
// !!!UNDONE - currently, this only returns the closest enemy.
|
|
// we'll want to consider distance, relationship, attack types, back turned, etc.
|
|
//=========================================================
|
|
CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void )
|
|
{
|
|
CBaseEntity *pReturn;
|
|
CBaseEntity *pNextEnt;
|
|
int iNearest;
|
|
int iDist;
|
|
int iBestRelationship;
|
|
|
|
iNearest = 8192;// so first visible entity will become the closest.
|
|
pNextEnt = m_pLink;
|
|
pReturn = NULL;
|
|
iBestRelationship = R_NO;
|
|
|
|
while ( pNextEnt != NULL )
|
|
{
|
|
if ( pNextEnt->IsAlive() )
|
|
{
|
|
if ( IRelationship( pNextEnt) > iBestRelationship )
|
|
{
|
|
// this entity is disliked MORE than the entity that we
|
|
// currently think is the best visible enemy. No need to do
|
|
// a distance check, just get mad at this one for now.
|
|
iBestRelationship = IRelationship ( pNextEnt );
|
|
iNearest = ( pNextEnt->pev->origin - pev->origin ).Length();
|
|
pReturn = pNextEnt;
|
|
}
|
|
else if ( IRelationship( pNextEnt) == iBestRelationship )
|
|
{
|
|
// this entity is disliked just as much as the entity that
|
|
// we currently think is the best visible enemy, so we only
|
|
// get mad at it if it is closer.
|
|
iDist = ( pNextEnt->pev->origin - pev->origin ).Length();
|
|
|
|
if ( iDist <= iNearest )
|
|
{
|
|
iNearest = iDist;
|
|
iBestRelationship = IRelationship ( pNextEnt );
|
|
pReturn = pNextEnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
pNextEnt = pNextEnt->m_pLink;
|
|
}
|
|
|
|
return pReturn;
|
|
}
|