Merge pull request #337 from Blixibon/mapbase/feature/npc-memory-and-vscript-expansions

NPC memory fixes and NPC VScript expansions
This commit is contained in:
Blixibon 2025-02-28 20:37:39 -06:00 committed by GitHub
commit 9efadb7541
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 703 additions and 19 deletions

View File

@ -318,6 +318,14 @@ ScriptHook_t CAI_BaseNPC::g_Hook_TranslateSchedule;
ScriptHook_t CAI_BaseNPC::g_Hook_GetActualShootPosition;
ScriptHook_t CAI_BaseNPC::g_Hook_OverrideMove;
ScriptHook_t CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture;
ScriptHook_t CAI_BaseNPC::g_Hook_IsValidEnemy;
ScriptHook_t CAI_BaseNPC::g_Hook_CanBeAnEnemyOf;
ScriptHook_t CAI_BaseNPC::g_Hook_UpdateEnemyMemory;
ScriptHook_t CAI_BaseNPC::g_Hook_OnSeeEntity;
ScriptHook_t CAI_BaseNPC::g_Hook_OnListened;
ScriptHook_t CAI_BaseNPC::g_Hook_BuildScheduleTestBits;
ScriptHook_t CAI_BaseNPC::g_Hook_StartTask;
ScriptHook_t CAI_BaseNPC::g_Hook_RunTask;
#endif
//
@ -746,10 +754,19 @@ Vector CAI_BaseNPC::VScriptGetEnemyLKP()
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT pEnemy )
int CAI_BaseNPC::VScriptNumEnemies()
{
return GetEnemies()->NumEnemies();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CAI_BaseNPC::VScriptGetFirstEnemyMemory()
{
HSCRIPT hScript = NULL;
AI_EnemyInfo_t *info = GetEnemies()->Find( ToEnt(pEnemy) );
AIEnemiesIter_t iter;
AI_EnemyInfo_t *info = GetEnemies()->GetFirst( &iter );
if (info)
{
hScript = g_pScriptVM->RegisterInstance( reinterpret_cast<Script_AI_EnemyInfo_t*>(info) );
@ -758,6 +775,70 @@ HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT pEnemy )
return hScript;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CAI_BaseNPC::VScriptGetNextEnemyMemory( HSCRIPT hMemory )
{
Script_AI_EnemyInfo_t *pCurEMemory = HScriptToClass<Script_AI_EnemyInfo_t>( hMemory );
if (!pCurEMemory)
return NULL;
HSCRIPT hScript = NULL;
AIEnemiesIter_t iter = (AIEnemiesIter_t)GetEnemies()->FindIndex( pCurEMemory->hEnemy );
AI_EnemyInfo_t *pEMemory = GetEnemies()->GetNext( &iter );
if (pEMemory)
{
hScript = g_pScriptVM->RegisterInstance( reinterpret_cast<Script_AI_EnemyInfo_t*>(pEMemory) );
}
return hScript;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT hEnemy )
{
HSCRIPT hScript = NULL;
AI_EnemyInfo_t *info = GetEnemies()->Find( ToEnt(hEnemy) );
if (info)
{
hScript = g_pScriptVM->RegisterInstance( reinterpret_cast<Script_AI_EnemyInfo_t*>(info) );
}
return hScript;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CAI_BaseNPC::VScriptUpdateEnemyMemory( HSCRIPT hEnemy, const Vector &position, HSCRIPT hInformer )
{
return UpdateEnemyMemory( ToEnt( hEnemy ), position, ToEnt( hInformer ) );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_BaseNPC::VScriptClearEnemyMemory( HSCRIPT hEnemy )
{
CBaseEntity *pEnemy = ToEnt( hEnemy );
if (!pEnemy)
return;
GetEnemies()->ClearMemory( pEnemy );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_BaseNPC::VScriptSetFreeKnowledgeDuration( float flDuration )
{
GetEnemies()->SetFreeKnowledgeDuration( flDuration );
}
void CAI_BaseNPC::VScriptSetEnemyDiscardTime( float flDuration )
{
GetEnemies()->SetEnemyDiscardTime( flDuration );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CAI_BaseNPC::VScriptGetState()
@ -765,6 +846,34 @@ int CAI_BaseNPC::VScriptGetState()
return (int)GetState();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CAI_BaseNPC::VScriptGetIdealState()
{
return (int)GetIdealState();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_BaseNPC::VScriptSetIdealState( int nNPCState )
{
SetIdealState( (NPC_STATE)nNPCState );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CAI_BaseNPC::VScriptGetTarget()
{
return ToHScript( GetTarget() );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_BaseNPC::VScriptSetTarget( HSCRIPT hTarget )
{
SetTarget( ToEnt( hTarget ) );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CAI_BaseNPC::VScriptGetHintNode()
@ -832,7 +941,7 @@ int CAI_BaseNPC::VScriptGetTaskID()
const Task_t *pTask = GetTask();
int iID = -1;
if (pTask)
iID = GetTaskID( TaskName( pTask->iTask ) );
iID = AI_RemapFromGlobal( GetTaskID( TaskName( pTask->iTask ) ) );
return iID;
}
@ -867,6 +976,70 @@ HSCRIPT CAI_BaseNPC::VScriptGetSquad()
return hScript;
}
HSCRIPT CAI_BaseNPC::VScriptGetBestSound( int validTypes )
{
HSCRIPT hScript = NULL;
CSound *pSound = GetBestSound( validTypes );
if (pSound)
{
hScript = g_pScriptVM->RegisterInstance( pSound );
}
return hScript;
}
HSCRIPT CAI_BaseNPC::VScriptGetFirstHeardSound()
{
HSCRIPT hScript = NULL;
AISoundIter_t iter;
CSound *pSound = GetSenses()->GetFirstHeardSound( &iter );
if (pSound)
{
hScript = g_pScriptVM->RegisterInstance( pSound );
}
return hScript;
}
HSCRIPT CAI_BaseNPC::VScriptGetNextHeardSound( HSCRIPT hSound )
{
CSound *pCurSound = HScriptToClass<CSound>( hSound );
if (!pCurSound)
return NULL;
int iCurrent = pCurSound->m_iNextAudible;
if ( iCurrent == SOUNDLIST_EMPTY )
return NULL;
HSCRIPT hScript = NULL;
CSound *pNextSound = CSoundEnt::SoundPointerForIndex( iCurrent );
if (pNextSound)
{
hScript = g_pScriptVM->RegisterInstance( pNextSound );
}
return hScript;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
HSCRIPT CAI_BaseNPC::VScriptGetFirstSeenEntity( int nSeenType )
{
AISightIter_t iter;
return ToHScript( GetSenses()->GetFirstSeenEntity( &iter, (seentype_t)nSeenType ) );
}
HSCRIPT CAI_BaseNPC::VScriptGetNextSeenEntity( HSCRIPT hEnt, int nSeenType )
{
CBaseEntity *pEnt = ToEnt( hEnt );
AISightIter_t iter;
GetSenses()->GetSeenEntityIndex( &iter, pEnt, (seentype_t)nSeenType );
return ToHScript( GetSenses()->GetNextSeenEntity( &iter ) );
}
#endif
bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info )
@ -2595,6 +2768,29 @@ void CAI_BaseNPC::OnListened()
{
m_OnHearCombat.FireOutput(this, this);
}
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_OnListened.CanRunInScope( m_ScriptScope ))
{
ScriptVariant_t functionReturn;
g_Hook_OnListened.Call( m_ScriptScope, &functionReturn, NULL );
}
#endif
}
//-----------------------------------------------------------------------------
void CAI_BaseNPC::OnSeeEntity( CBaseEntity *pEntity )
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_OnSeeEntity.CanRunInScope( m_ScriptScope ))
{
// entity
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ToHScript( pEntity ) };
g_Hook_OnSeeEntity.Call( m_ScriptScope, &functionReturn, args );
}
#endif
}
//=========================================================
@ -6107,6 +6303,22 @@ bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position
if ( GetEnemies() )
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_UpdateEnemyMemory.CanRunInScope( m_ScriptScope ))
{
// enemy, position, informer
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ToHScript( pEnemy ), position, ToHScript( pInformer ) };
if (g_Hook_UpdateEnemyMemory.Call( m_ScriptScope, &functionReturn, args ))
{
// Returning false normally indicates this is a known enemy
// Most uses of that functionality involve checking for new enemies, so this is acceptable
if (functionReturn.m_bool == false)
return false;
}
}
#endif
// If the was eluding me and allow the NPC to play a sound
if (GetEnemies()->HasEludedMe(pEnemy))
{
@ -8979,6 +9191,20 @@ bool CAI_BaseNPC::IsValidEnemy( CBaseEntity *pEnemy )
if ( m_hEnemyFilter.Get()!= NULL && m_hEnemyFilter->PassesFilter( this, pEnemy ) == false )
return false;
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_IsValidEnemy.CanRunInScope(m_ScriptScope))
{
// enemy
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ToHScript( pEnemy ) };
if (g_Hook_IsValidEnemy.Call( m_ScriptScope, &functionReturn, args ))
{
if (functionReturn.m_bool == false)
return false;
}
}
#endif
return true;
}
@ -8988,6 +9214,20 @@ bool CAI_BaseNPC::CanBeAnEnemyOf( CBaseEntity *pEnemy )
if ( GetSleepState() > AISS_WAITING_FOR_THREAT )
return false;
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_CanBeAnEnemyOf.CanRunInScope(m_ScriptScope))
{
// enemy
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ToHScript( pEnemy ) };
if (g_Hook_CanBeAnEnemyOf.Call( m_ScriptScope, &functionReturn, args ))
{
if (functionReturn.m_bool == false)
return false;
}
}
#endif
return true;
}
@ -11529,6 +11769,13 @@ float CAI_BaseNPC::GetEnemyLastTimeSeen() const
void CAI_BaseNPC::MarkEnemyAsEluded()
{
GetEnemies()->MarkAsEluded( GetEnemy() );
#ifdef MAPBASE
if (m_pSquad)
{
m_pSquad->MarkEnemyAsEluded( this, GetEnemy() );
}
#endif
}
void CAI_BaseNPC::ClearEnemyMemory()
@ -12307,7 +12554,17 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC
DEFINE_SCRIPTFUNC_NAMED( VScriptSetEnemy, "SetEnemy", "Set the NPC's current enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetEnemyLKP, "GetEnemyLKP", "Get the last known position of the NPC's current enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptNumEnemies, "NumEnemies", "Get the number of enemies this NPC knows about." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetFirstEnemyMemory, "GetFirstEnemyMemory", "Get information about the NPC's first enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetNextEnemyMemory, "GetNextEnemyMemory", "Get information about the NPC's next enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptFindEnemyMemory, "FindEnemyMemory", "Get information about the NPC's current enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptUpdateEnemyMemory, "UpdateEnemyMemory", "Update information on this enemy. First parameter is the enemy, second is the position we now know the enemy is at, third parameter is the informer (e.g. squadmate who sees enemy, null if I see it myself). Returns true if this is a new enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptClearEnemyMemory, "ClearEnemyMemory", "Makes the NPC forget about the specified enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptSetFreeKnowledgeDuration, "SetFreeKnowledgeDuration", "Sets the amount of time the NPC can always know an enemy's location after losing sight." )
DEFINE_SCRIPTFUNC_NAMED( VScriptSetEnemyDiscardTime, "SetEnemyDiscardTime", "Sets the amount of time needed before the NPC discards an unseen enemy's memory." )
DEFINE_SCRIPTFUNC( GetLastAttackTime, "Get the last time the NPC has used an attack (e.g. fired a bullet from a gun)." )
DEFINE_SCRIPTFUNC( GetLastDamageTime, "Get the last time the NPC has been damaged." )
@ -12315,6 +12572,11 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC
DEFINE_SCRIPTFUNC( GetLastEnemyTime, "Get the last time the NPC has seen an enemy." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetState, "GetNPCState", "Get the NPC's current state." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetIdealState, "GetIdealNPCState", "Get the NPC's ideal state." )
DEFINE_SCRIPTFUNC_NAMED( VScriptSetIdealState, "SetIdealNPCState", "Set the NPC's ideal state." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetTarget, "GetNPCTarget", "Get the NPC's AI target." )
DEFINE_SCRIPTFUNC_NAMED( VScriptSetTarget, "SetNPCTarget", "Set the NPC's AI target." )
DEFINE_SCRIPTFUNC_NAMED( VScriptWake, "Wake", "Awakens the NPC if it is currently asleep." )
DEFINE_SCRIPTFUNC_NAMED( VScriptSleep, "Sleep", "Puts the NPC into a sleeping state." )
@ -12362,6 +12624,16 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC
DEFINE_SCRIPTFUNC_NAMED( VScriptClearCondition, "ClearCondition", "Clear a condition on the NPC." )
DEFINE_SCRIPTFUNC_NAMED( ClearCondition, "ClearConditionID", "Clear a condition on the NPC by ID." )
DEFINE_SCRIPTFUNC_NAMED( VScriptSetCustomInterruptCondition, "SetCustomInterruptCondition", "Use with BuildScheduleTestBits to define conditions which should interrupt the schedule." )
DEFINE_SCRIPTFUNC_NAMED( VScriptIsCustomInterruptConditionSet, "IsCustomInterruptConditionSet", "Use with BuildScheduleTestBits to define conditions which should interrupt the schedule." )
DEFINE_SCRIPTFUNC_NAMED( VScriptClearCustomInterruptCondition, "ClearCustomInterruptCondition", "Use with BuildScheduleTestBits to define conditions which should interrupt the schedule." )
DEFINE_SCRIPTFUNC_NAMED( VScriptChainStartTask, "ChainStartTask", "Use with StartTask to redirect to the specified task." )
DEFINE_SCRIPTFUNC_NAMED( VScriptChainRunTask, "ChainRunTask", "Use with RunTask to redirect to the specified task." )
DEFINE_SCRIPTFUNC_NAMED( VScriptFailTask, "FailTask", "Fails the currently running task with the specified error message." )
DEFINE_SCRIPTFUNC_NAMED( VScriptCompleteTask, "CompleteTask", "Completes the currently running task." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetTaskStatus, "GetTaskStatus", "Gets the current task's status." )
DEFINE_SCRIPTFUNC( IsMoving, "Check if the NPC is moving." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetExpresser, "GetExpresser", "Get a handle for this NPC's expresser." )
@ -12380,6 +12652,13 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC
DEFINE_SCRIPTFUNC( IsCrouching, "Returns true if the NPC is crouching." )
DEFINE_SCRIPTFUNC( Crouch, "Tells the NPC to crouch." )
DEFINE_SCRIPTFUNC( Stand, "Tells the NPC to stand if it is crouching." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetBestSound, "GetBestSound", "Get the NPC's best sound of the specified type(s). Use 'ALL_SOUNDS' to get any sound." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetFirstHeardSound, "GetFirstHeardSound", "Get the NPC's first heard sound." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetNextHeardSound, "GetNextHeardSound", "Get the NPC's next heard sound." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetFirstSeenEntity, "GetFirstSeenEntity", "Get the NPC's first seen entity in the specified 'SEEN_' list." )
DEFINE_SCRIPTFUNC_NAMED( VScriptGetNextSeenEntity, "GetNextSeenEntity", "Get the NPC's next seen entity in the specified 'SEEN_' list." )
//
// Hooks
@ -12409,6 +12688,32 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC
DEFINE_SCRIPTHOOK_PARAM( "activity", FIELD_CSTRING )
DEFINE_SCRIPTHOOK_PARAM( "translatedActivity", FIELD_CSTRING )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_IsValidEnemy, "IsValidEnemy", FIELD_BOOLEAN, "Whether or not the specified enemy should be considered valid." )
DEFINE_SCRIPTHOOK_PARAM( "enemy", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_CanBeAnEnemyOf, "CanBeAnEnemyOf", FIELD_BOOLEAN, "Whether or not this NPC can be an enemy of another NPC." )
DEFINE_SCRIPTHOOK_PARAM( "enemy", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_UpdateEnemyMemory, "UpdateEnemyMemory", FIELD_BOOLEAN, "Whether or not this NPC can be an enemy of another NPC." )
DEFINE_SCRIPTHOOK_PARAM( "enemy", FIELD_HSCRIPT )
DEFINE_SCRIPTHOOK_PARAM( "position", FIELD_VECTOR )
DEFINE_SCRIPTHOOK_PARAM( "informer", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_OnSeeEntity, "OnSeeEntity", FIELD_VOID, "Called when the NPC sees an entity." )
DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT )
END_SCRIPTHOOK()
DEFINE_SIMPLE_SCRIPTHOOK( CAI_BaseNPC::g_Hook_OnListened, "OnListened", FIELD_VOID, "Called when the NPC assigns sound conditions after checking for sounds it hears." )
DEFINE_SIMPLE_SCRIPTHOOK( CAI_BaseNPC::g_Hook_BuildScheduleTestBits, "BuildScheduleTestBits", FIELD_VOID, "Called when the NPC is determining which conditions can interrupt the current schedule." )
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_StartTask, "StartTask", FIELD_VOID, "Called when a task is starting. The task is provided in both string and ID form. Return false to override actual task functionality." )
DEFINE_SCRIPTHOOK_PARAM( "task", FIELD_CSTRING )
DEFINE_SCRIPTHOOK_PARAM( "task_id", FIELD_INTEGER )
DEFINE_SCRIPTHOOK_PARAM( "task_data", FIELD_FLOAT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_RunTask, "RunTask", FIELD_VOID, "Called every think while the task is running. The task is provided in both string and ID form. Return false to override actual task functionality." )
DEFINE_SCRIPTHOOK_PARAM( "task", FIELD_CSTRING )
DEFINE_SCRIPTHOOK_PARAM( "task_id", FIELD_INTEGER )
DEFINE_SCRIPTHOOK_PARAM( "task_data", FIELD_FLOAT )
END_SCRIPTHOOK()
END_SCRIPTDESC();
#endif

View File

@ -1104,7 +1104,7 @@ public:
virtual void OnLooked( int iDistance );
virtual void OnListened();
virtual void OnSeeEntity( CBaseEntity *pEntity ) {}
virtual void OnSeeEntity( CBaseEntity *pEntity );
// If true, AI will try to see this entity regardless of distance.
virtual bool ShouldNotDistanceCull() { return false; }
@ -1271,9 +1271,24 @@ private:
void VScriptSetEnemy( HSCRIPT pEnemy );
Vector VScriptGetEnemyLKP();
HSCRIPT VScriptFindEnemyMemory( HSCRIPT pEnemy );
int VScriptNumEnemies();
HSCRIPT VScriptGetFirstEnemyMemory();
HSCRIPT VScriptGetNextEnemyMemory( HSCRIPT hMemory );
HSCRIPT VScriptFindEnemyMemory( HSCRIPT hEnemy );
bool VScriptUpdateEnemyMemory( HSCRIPT hEnemy, const Vector &position, HSCRIPT hInformer );
void VScriptClearEnemyMemory( HSCRIPT hEnemy );
void VScriptSetFreeKnowledgeDuration( float flDuration );
void VScriptSetEnemyDiscardTime( float flDuration );
int VScriptGetState();
int VScriptGetIdealState();
void VScriptSetIdealState( int nNPCState );
HSCRIPT VScriptGetTarget();
void VScriptSetTarget( HSCRIPT hTarget );
void VScriptWake( HSCRIPT hActivator ) { Wake( ToEnt(hActivator) ); }
void VScriptSleep() { Sleep(); }
@ -1308,12 +1323,29 @@ private:
void VScriptSetCondition( const char *szCondition ) { SetCondition( GetConditionID( szCondition ) ); }
void VScriptClearCondition( const char *szCondition ) { ClearCondition( GetConditionID( szCondition ) ); }
void VScriptSetCustomInterruptCondition( const char *szCondition ) { SetCustomInterruptCondition( GetConditionID( szCondition ) ); }
bool VScriptIsCustomInterruptConditionSet( const char *szCondition ) { return IsCustomInterruptConditionSet( GetConditionID( szCondition ) ); }
void VScriptClearCustomInterruptCondition( const char *szCondition ) { ClearCustomInterruptCondition( GetConditionID( szCondition ) ); }
void VScriptChainStartTask( const char *szTask, float flTaskData ) { ChainStartTask( AI_RemapFromGlobal( GetTaskID( szTask ) ), flTaskData ); }
void VScriptChainRunTask( const char *szTask, float flTaskData ) { ChainRunTask( AI_RemapFromGlobal( GetTaskID( szTask ) ), flTaskData ); }
void VScriptFailTask( const char *szFailReason ) { TaskFail( szFailReason ); }
void VScriptCompleteTask() { TaskComplete(); }
int VScriptGetTaskStatus() { return (int)GetTaskStatus(); }
HSCRIPT VScriptGetExpresser();
HSCRIPT VScriptGetCine();
int GetScriptState() { return m_scriptState; }
HSCRIPT VScriptGetSquad();
HSCRIPT VScriptGetBestSound( int validTypes );
HSCRIPT VScriptGetFirstHeardSound();
HSCRIPT VScriptGetNextHeardSound( HSCRIPT hSound );
HSCRIPT VScriptGetFirstSeenEntity( int nSeenType );
HSCRIPT VScriptGetNextSeenEntity( HSCRIPT hEnt, int nSeenType );
#endif
//-----------------------------------------------------
@ -2399,6 +2431,14 @@ public:
static ScriptHook_t g_Hook_GetActualShootPosition;
static ScriptHook_t g_Hook_OverrideMove;
static ScriptHook_t g_Hook_ShouldPlayFakeSequenceGesture;
static ScriptHook_t g_Hook_IsValidEnemy;
static ScriptHook_t g_Hook_CanBeAnEnemyOf;
static ScriptHook_t g_Hook_UpdateEnemyMemory;
static ScriptHook_t g_Hook_OnSeeEntity;
static ScriptHook_t g_Hook_OnListened;
static ScriptHook_t g_Hook_BuildScheduleTestBits;
static ScriptHook_t g_Hook_StartTask;
static ScriptHook_t g_Hook_RunTask;
#endif
private:

View File

@ -45,6 +45,10 @@ extern ConVar ai_use_think_optimizations;
ConVar ai_simulate_task_overtime( "ai_simulate_task_overtime", "0" );
#ifdef MAPBASE
ConVar ai_enemy_memory_fixes( "ai_enemy_memory_fixes", "0", FCVAR_NONE, "Toggles Mapbase fixes for certain NPC AI not using enemy memory when it should." );
#endif
#define MAX_TASKS_RUN 10
struct TaskTimings
@ -276,6 +280,14 @@ void CAI_BaseNPC::NextScheduledTask ( void )
void CAI_BaseNPC::BuildScheduleTestBits( void )
{
//NOTENOTE: Always defined in the leaf classes
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_BuildScheduleTestBits.CanRunInScope( m_ScriptScope ))
{
ScriptVariant_t functionReturn;
g_Hook_BuildScheduleTestBits.Call( m_ScriptScope, &functionReturn, NULL );
}
#endif
}
@ -730,6 +742,23 @@ void CAI_BaseNPC::MaintainSchedule ( void )
AI_PROFILE_SCOPE_BEGIN_( pszTaskName );
AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_StartTask);
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_StartTask.CanRunInScope( m_ScriptScope ))
{
// task, task_id, task_data
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { (bDebugTaskNames) ? pszTaskName : TaskName( pTask->iTask ), pTask->iTask, pTask->flTaskData };
if (g_Hook_StartTask.Call( m_ScriptScope, &functionReturn, args ))
{
// Returning false overrides original functionality
if (functionReturn.m_bool != false)
StartTask( pTask );
}
else
StartTask( pTask );
}
else
#endif
StartTask( pTask );
AI_PROFILE_SCOPE_END();
@ -766,6 +795,23 @@ void CAI_BaseNPC::MaintainSchedule ( void )
int j;
for (j = 0; j < 8; j++)
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_RunTask.CanRunInScope( m_ScriptScope ))
{
// task, task_id, task_data
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { (bDebugTaskNames) ? pszTaskName : TaskName( pTask->iTask ), pTask->iTask, pTask->flTaskData };
if (g_Hook_RunTask.Call( m_ScriptScope, &functionReturn, args ))
{
// Returning false overrides original functionality
if (functionReturn.m_bool != false)
RunTask( pTask );
}
else
RunTask( pTask );
}
else
#endif
RunTask( pTask );
if ( GetTaskInterrupt() == 0 || TaskIsComplete() || HasCondition(COND_TASK_FAILED) )
@ -1971,7 +2017,17 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask )
flMaxRange = m_flDistTooFar;
}
#ifdef MAPBASE
// By default, TASK_GET_PATH_TO_ENEMY_LKP_LOS acts identical to TASK_GET_PATH_TO_ENEMY_LOS.
// Considering the fact TASK_GET_PATH_TO_ENEMY_LKP doesn't use this code, this appears to be a mistake.
// In HL2, this task is used by Combine soldiers, metrocops, striders, and hunters.
// With this change, these NPCs will establish LOS according to enemy memory instead of where the enemy
// actually is. This may make the NPCs more consistent and fair, but their AI and levels built around it
// may have been designed around this bug, so this is currently being tied to a cvar.
Vector vecEnemy = ( task != TASK_GET_PATH_TO_ENEMY_LKP_LOS || !ai_enemy_memory_fixes.GetBool() ) ? GetEnemy()->GetAbsOrigin() : GetEnemyLKP();
#else
Vector vecEnemy = ( task != TASK_GET_PATH_TO_ENEMY_LKP ) ? GetEnemy()->GetAbsOrigin() : GetEnemyLKP();
#endif
Vector vecEnemyEye = vecEnemy + GetEnemy()->GetViewOffset();
Vector posLos;

View File

@ -500,6 +500,13 @@ public:
g_AI_SensedObjectsManager.Init();
}
#ifdef MAPBASE_VSCRIPT
virtual void RegisterVScript()
{
g_pScriptVM->RegisterInstance( &g_AI_SensedObjectsManager, "SensedObjectsManager" );
}
#endif
void LevelShutdownPreEntity()
{
CBaseCombatCharacter::ResetVisibilityCache();

View File

@ -225,6 +225,18 @@ AI_EnemyInfo_t *CAI_Enemies::Find( CBaseEntity *pEntity, bool bTryDangerMemory )
}
//-----------------------------------------------------------------------------
#ifdef MAPBASE
unsigned char CAI_Enemies::FindIndex( CBaseEntity *pEntity )
{
if ( pEntity == AI_UNKNOWN_ENEMY )
pEntity = NULL;
return m_Map.Find( pEntity );
}
#endif
//-----------------------------------------------------------------------------
AI_EnemyInfo_t *CAI_Enemies::GetDangerMemory()

View File

@ -63,6 +63,9 @@ public:
AI_EnemyInfo_t *GetFirst( AIEnemiesIter_t *pIter );
AI_EnemyInfo_t *GetNext( AIEnemiesIter_t *pIter );
AI_EnemyInfo_t *Find( CBaseEntity *pEntity, bool bTryDangerMemory = false );
#ifdef MAPBASE
unsigned char FindIndex( CBaseEntity *pEntity );
#endif
AI_EnemyInfo_t *GetDangerMemory();
int NumEnemies() const { return m_Map.Count(); }

View File

@ -298,6 +298,43 @@ CBaseEntity *CAI_Senses::GetNextSeenEntity( AISightIter_t *pIter ) const
return NULL;
}
//-----------------------------------------------------------------------------
#ifdef MAPBASE
bool CAI_Senses::GetSeenEntityIndex( AISightIter_t *pIter, CBaseEntity *pSightEnt, seentype_t iSeenType ) const
{
COMPILE_TIME_ASSERT( sizeof( AISightIter_t ) == sizeof( AISightIterVal_t ) );
AISightIterVal_t *pIterVal = (AISightIterVal_t *)pIter;
// If we're searching for a specific type, start in that array
pIterVal->SeenArray = (char)iSeenType;
int iFirstArray = ( iSeenType == SEEN_ALL ) ? 0 : iSeenType;
for ( int i = iFirstArray; i < ARRAYSIZE( m_SeenArrays ); i++ )
{
for ( int j = pIterVal->iNext; j < m_SeenArrays[i]->Count(); j++ )
{
if ( (*m_SeenArrays[i])[j].Get() == pSightEnt )
{
pIterVal->array = i;
pIterVal->iNext = j+1;
return true;
}
}
pIterVal->iNext = 0;
// If we're searching for a specific type, don't move to the next array
if ( pIterVal->SeenArray != SEEN_ALL )
break;
}
(*pIter) = (AISightIter_t)(-1);
return false;
}
#endif
//-----------------------------------------------------------------------------
void CAI_Senses::BeginGather()
@ -749,4 +786,27 @@ void CAI_SensedObjectsManager::AddEntity( CBaseEntity *pEntity )
m_SensedObjects.AddToTail( pEntity );
}
#ifdef MAPBASE
void CAI_SensedObjectsManager::RemoveEntity( CBaseEntity *pEntity )
{
int i = m_SensedObjects.Find( pEntity );
if (i == m_SensedObjects.InvalidIndex())
return;
pEntity->RemoveFlag( FL_OBJECT );
m_SensedObjects.FastRemove( i );
}
#endif
//-----------------------------------------------------------------------------
#ifdef MAPBASE_VSCRIPT
BEGIN_SCRIPTDESC_ROOT( CAI_SensedObjectsManager, SCRIPT_SINGLETON "Manager which handles sensed objects." )
DEFINE_SCRIPTFUNC_NAMED( ScriptAddEntity, "AddEntity", "Adds an entity to the sensed object list." )
DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveEntity, "RemoveEntity", "Removes an entity from the sensed object list." )
END_SCRIPTDESC();
#endif
//=============================================================================

View File

@ -82,6 +82,9 @@ public:
CBaseEntity * GetFirstSeenEntity( AISightIter_t *pIter, seentype_t iSeenType = SEEN_ALL ) const;
CBaseEntity * GetNextSeenEntity( AISightIter_t *pIter ) const;
#ifdef MAPBASE
bool GetSeenEntityIndex( AISightIter_t *pIter, CBaseEntity *pSightEnt, seentype_t iSeenType ) const;
#endif
CSound * GetFirstHeardSound( AISoundIter_t *pIter );
CSound * GetNextHeardSound( AISoundIter_t *pIter );
@ -152,6 +155,14 @@ public:
CBaseEntity * GetNext( int *pIter );
virtual void AddEntity( CBaseEntity *pEntity );
#ifdef MAPBASE
virtual void RemoveEntity( CBaseEntity *pEntity );
#endif
#ifdef MAPBASE_VSCRIPT
void ScriptAddEntity( HSCRIPT hEnt ) { AddEntity( ToEnt( hEnt ) ); }
void ScriptRemoveEntity( HSCRIPT hEnt ) { RemoveEntity( ToEnt( hEnt ) ); }
#endif
private:
virtual void OnEntitySpawned( CBaseEntity *pEntity );

View File

@ -18,6 +18,10 @@
CAI_SquadManager g_AI_SquadManager;
#ifdef MAPBASE
ConVar ai_squad_broadcast_elusion("ai_squad_broadcast_elusion", "0", FCVAR_NONE, "Tells the entire squad when an enemy is eluded");
#endif
//-----------------------------------------------------------------------------
// CAI_SquadManager
//
@ -740,6 +744,25 @@ void CAI_Squad::UpdateEnemyMemory( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy, c
//------------------------------------------------------------------------------
#ifdef MAPBASE
void CAI_Squad::MarkEnemyAsEluded( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy )
{
if (!ai_squad_broadcast_elusion.GetBool())
return;
//Broadcast to all members of the squad
for ( int i = 0; i < m_SquadMembers.Count(); i++ )
{
if ( m_SquadMembers[i] != pUpdater )
{
m_SquadMembers[i]->GetEnemies()->MarkAsEluded( pEnemy );
}
}
}
#endif
//------------------------------------------------------------------------------
#ifdef PER_ENEMY_SQUADSLOTS
AISquadEnemyInfo_t *CAI_Squad::FindEnemyInfo( CBaseEntity *pEnemy )
@ -883,14 +906,14 @@ void CAI_Squad::ScriptRemoveFromSquad( HSCRIPT hNPC ) { RemoveFromSquad( HScrip
bool CAI_Squad::ScriptIsSilentMember( HSCRIPT hNPC ) { return IsSilentMember( HScriptToClass<CAI_BaseNPC>( hNPC ) ); }
void CAI_Squad::ScriptSetSquadData( int iSlot, const char *data )
void CAI_Squad::ScriptSetSquadData( int iSlot, int data )
{
SetSquadData( iSlot, data );
}
const char *CAI_Squad::ScriptGetSquadData( int iSlot )
int CAI_Squad::ScriptGetSquadData( int iSlot )
{
const char *data;
int data;
GetSquadData( iSlot, &data );
return data;
}

View File

@ -107,6 +107,12 @@ public:
void SquadNewEnemy ( CBaseEntity *pEnemy );
void UpdateEnemyMemory( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy, const Vector &position );
#ifdef MAPBASE
// The idea behind this is that, if one squad member fails to locate the enemy, nobody in the squad knows where the enemy is
// Makes combat utilizing elusion a bit smoother
// (gated by ai_squad_broadcast_elusion cvar)
void MarkEnemyAsEluded( CAI_BaseNPC *pUpdater, CBaseEntity *pEnemy );
#endif
bool OccupyStrategySlotRange( CBaseEntity *pEnemy, int slotIDStart, int slotIDEnd, int *pSlot );
void VacateStrategySlot( CBaseEntity *pEnemy, int slot);
@ -186,8 +192,8 @@ private:
bool ScriptIsSilentMember( HSCRIPT hNPC );
void ScriptSetSquadData( int iSlot, const char *data );
const char *ScriptGetSquadData( int iSlot );
void ScriptSetSquadData( int iSlot, int data );
int ScriptGetSquadData( int iSlot );
#endif
private:

View File

@ -1628,10 +1628,53 @@ void CBaseEntity::TakeDamage( const CTakeDamageInfo &inputInfo )
DamageFilterDamageMod(info);
#endif
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_OnTakeDamage.CanRunInScope( m_ScriptScope ))
{
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( &info );
// info
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) };
if ( g_Hook_OnTakeDamage.Call( m_ScriptScope, &functionReturn, args ) )
{
if (functionReturn.m_type == FIELD_BOOLEAN && functionReturn.m_bool == false)
{
g_pScriptVM->RemoveInstance( hInfo );
return;
}
}
g_pScriptVM->RemoveInstance( hInfo );
}
#endif
OnTakeDamage( info );
}
}
//-----------------------------------------------------------------------------
// Purpose: Allows entities to be 'invisible' to NPC senses.
//-----------------------------------------------------------------------------
bool CBaseEntity::CanBeSeenBy( CAI_BaseNPC *pNPC )
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_CanBeSeenBy.CanRunInScope(m_ScriptScope))
{
// npc
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ToHScript( pNPC ) };
if (g_Hook_CanBeSeenBy.Call( m_ScriptScope, &functionReturn, args ))
{
if (functionReturn.m_bool == false)
return false;
}
}
#endif
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Returns a value that scales all damage done by this entity.
//-----------------------------------------------------------------------------
@ -2251,10 +2294,13 @@ ScriptHook_t CBaseEntity::g_Hook_OnEntText;
ScriptHook_t CBaseEntity::g_Hook_VPhysicsCollision;
ScriptHook_t CBaseEntity::g_Hook_FireBullets;
ScriptHook_t CBaseEntity::g_Hook_OnDeath;
ScriptHook_t CBaseEntity::g_Hook_OnTakeDamage;
ScriptHook_t CBaseEntity::g_Hook_OnKilledOther;
ScriptHook_t CBaseEntity::g_Hook_HandleInteraction;
ScriptHook_t CBaseEntity::g_Hook_ModifyEmitSoundParams;
ScriptHook_t CBaseEntity::g_Hook_ModifySentenceParams;
ScriptHook_t CBaseEntity::g_Hook_ModifyOrAppendCriteria;
ScriptHook_t CBaseEntity::g_Hook_CanBeSeenBy;
#endif
BEGIN_ENT_SCRIPTDESC_ROOT_WITH_HELPER( CBaseEntity, "Root class of all server-side entities", &g_BaseEntityScriptInstanceHelper )
@ -2376,6 +2422,7 @@ BEGIN_ENT_SCRIPTDESC_ROOT_WITH_HELPER( CBaseEntity, "Root class of all server-si
DEFINE_SCRIPTFUNC_NAMED( ScriptGetContext, "GetContext", "Get a response context value" )
DEFINE_SCRIPTFUNC_NAMED( ScriptAddContext, "AddContext", "Add a response context value" )
DEFINE_SCRIPTFUNC( RemoveContext, "Remove a response context" )
DEFINE_SCRIPTFUNC( GetContextExpireTime, "Get a response context's expiration time" )
DEFINE_SCRIPTFUNC( GetContextCount, "Get the number of response contexts" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetContextIndex, "GetContextIndex", "Get a response context at a specific index in the form of a table" )
@ -2514,14 +2561,15 @@ BEGIN_ENT_SCRIPTDESC_ROOT_WITH_HELPER( CBaseEntity, "Root class of all server-si
DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_FireBullets, "FireBullets", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." )
DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT )
DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT )
DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR )
DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR )
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_FireBullets, "FireBullets", FIELD_VOID, "Called when the entity fires bullets from itself or from a weapon. The parameter is the associated FireBulletsInfo_t handle. Return false to cancel bullet firing." )
DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnDeath, "OnDeath", FIELD_BOOLEAN, "Called when the entity dies (Event_Killed). Returning false makes the entity cancel death, although this could have unforeseen consequences. For hooking any damage instead of just death, see filter_script and PassesFinalDamageFilter." )
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnDeath, "OnDeath", FIELD_BOOLEAN, "Called when the entity dies (Event_Killed). Returning false makes the entity cancel death, although this could have unforeseen consequences. For hooking any damage instead of just death, use OnTakeDamage." )
DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnTakeDamage, "OnTakeDamage", FIELD_BOOLEAN, "Called when the entity takes damage (OnTakeDamage). Returning false makes the entity cancel the damage, similar to a damage filter. This is called after any damage filter operations." )
DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT )
END_SCRIPTHOOK()
@ -2543,6 +2591,12 @@ BEGIN_ENT_SCRIPTDESC_ROOT_WITH_HELPER( CBaseEntity, "Root class of all server-si
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifySentenceParams, "ModifySentenceParams", FIELD_VOID, "Called every time a sentence is emitted on this entity, allowing for its parameters to be modified." )
DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT )
END_SCRIPTHOOK()
DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_ModifyOrAppendCriteria, "ModifyOrAppendCriteria", FIELD_HSCRIPT, "Called when the criteria set is collected for a response. Return a table of keyvalues to add to the criteria set." )
BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_CanBeSeenBy, "CanBeSeenBy", FIELD_BOOLEAN, "Whether or not this entity can be seen by the specified NPC." )
DEFINE_SCRIPTHOOK_PARAM( "npc", FIELD_HSCRIPT )
END_SCRIPTHOOK()
#endif
END_SCRIPTDESC();
@ -7543,6 +7597,61 @@ void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set )
set.AppendCriteria("spawnflags", UTIL_VarArgs("%i", GetSpawnFlags()));
set.AppendCriteria("flags", UTIL_VarArgs("%i", GetFlags()));
#endif
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_ModifyOrAppendCriteria.CanRunInScope(m_ScriptScope))
{
ScriptVariant_t functionReturn;
if (g_Hook_ModifyOrAppendCriteria.Call( m_ScriptScope, &functionReturn, NULL ))
{
if (functionReturn.m_hScript != NULL)
{
int nIterator = -1;
ScriptVariant_t varKey, varValue;
while ((nIterator = g_pScriptVM->GetKeyValue( functionReturn.m_hScript, nIterator, &varKey, &varValue )) != -1)
{
float flWeight = 1.0f;
char szValue[128];
switch (varValue.m_type)
{
case FIELD_CSTRING:
{
char *colon = V_strstr( varValue.m_pszString, ":" );
if (colon)
{
// Use as weight
flWeight = atof(colon+1);
*colon = NULL;
}
V_strncpy( szValue, varValue.m_pszString, sizeof( szValue ) );
}
break;
case FIELD_BOOLEAN:
V_snprintf( szValue, sizeof( szValue ), "%d", varValue.m_bool );
break;
case FIELD_INTEGER:
V_snprintf( szValue, sizeof( szValue ), "%i", varValue.m_int );
break;
case FIELD_FLOAT:
V_snprintf( szValue, sizeof( szValue ), "%f", varValue.m_float );
break;
case FIELD_VECTOR:
V_snprintf( szValue, sizeof( szValue ), "%f %f %f", varValue.m_pVector->x, varValue.m_pVector->y, varValue.m_pVector->z );
break;
default:
Warning( "ModifyOrAppendCriteria doesn't know how to handle field %i for %s\n", varValue.m_type, varKey.m_pszString );
break;
}
set.AppendCriteria( varKey.m_pszString, szValue, flWeight );
g_pScriptVM->ReleaseValue( varKey );
g_pScriptVM->ReleaseValue( varValue );
}
}
}
}
#endif
}
//-----------------------------------------------------------------------------

View File

@ -1440,7 +1440,7 @@ public:
virtual bool FVisible ( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
virtual bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
virtual bool CanBeSeenBy( CAI_BaseNPC *pNPC ) { return true; } // allows entities to be 'invisible' to NPC senses.
virtual bool CanBeSeenBy( CAI_BaseNPC *pNPC ); // allows entities to be 'invisible' to NPC senses.
// This function returns a value that scales all damage done by this entity.
// Use CDamageModifier to hook in damage modifiers on a guy.
@ -2177,10 +2177,13 @@ public:
static ScriptHook_t g_Hook_VPhysicsCollision;
static ScriptHook_t g_Hook_FireBullets;
static ScriptHook_t g_Hook_OnDeath;
static ScriptHook_t g_Hook_OnTakeDamage;
static ScriptHook_t g_Hook_OnKilledOther;
static ScriptHook_t g_Hook_HandleInteraction;
static ScriptHook_t g_Hook_ModifyEmitSoundParams;
static ScriptHook_t g_Hook_ModifySentenceParams;
static ScriptHook_t g_Hook_ModifyOrAppendCriteria;
static ScriptHook_t g_Hook_CanBeSeenBy;
#endif
string_t m_iszVScripts;

View File

@ -2551,6 +2551,24 @@ int CNPC_Combine::TranslateSchedule( int scheduleType )
}
}
#ifdef MAPBASE
extern ConVar ai_enemy_memory_fixes;
// SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE uses TASK_GET_PATH_TO_ENEMY_LKP_LOS, a task with a mistake
// detailed in CAI_BaseNPC::StartTask and fixed by ai_enemy_memory_fixes.
//
// However, SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE only stops being used once the NPC has LOS to its target.
// Since the fixed task now uses the enemy's last known position instead of the enemy's actual position,
// this schedule risks getting stuck in a loop.
//
// This code makes the soldier run up directly to the last known position if it's visible, allowing the AI
// to mark the enemy as eluded.
if ( ai_enemy_memory_fixes.GetBool() && FVisible( GetEnemyLKP() ) )
{
return SCHED_COMBINE_PRESS_ATTACK;
}
#endif
return SCHED_COMBINE_ESTABLISH_LINE_OF_FIRE;
}
break;

View File

@ -69,6 +69,7 @@ public:
int RangeAttack2Conditions( float flDot, float flDist ); // For innate grenade attack
int MeleeAttack1Conditions( float flDot, float flDist ); // For kick/punch
bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ) { return BaseClass::FVisible( vecTarget, traceMask, ppBlocker ); }
virtual bool IsCurTaskContinuousMove();
virtual float GetJumpGravity() const { return 1.8f; }

View File

@ -1520,6 +1520,26 @@ int CNPC_Strider::TranslateSchedule( int scheduleType )
return SCHED_COMBAT_PATROL;
}
}
else
{
#ifdef MAPBASE
extern ConVar ai_enemy_memory_fixes;
// Striders convert TASK_GET_PATH_TO_ENEMY_LOS to TASK_GET_PATH_TO_ENEMY_LKP_LOS, a task which incorrectly
// acts identically to the former. This is detailed in CAI_BaseNPC::StartTask and fixed by ai_enemy_memory_fixes.
//
// However, SCHED_ESTABLISH_LINE_OF_FIRE only stops being used once the NPC has LOS to its target.
// Since the fixed task now uses the enemy's last known position instead of the enemy's actual position,
// this schedule risks getting stuck in a loop.
//
// This code chains back up to SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK, which is what's supposed to happen when a
// strider is eluded in this way.
if ( ai_enemy_memory_fixes.GetBool() && FVisible( GetEnemyLKP() ) )
{
return TranslateSchedule( SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK );
}
#endif
}
break;

View File

@ -188,6 +188,7 @@ public:
bool HasPass() { return m_PlayerFreePass.HasPass(); }
bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ) { return BaseClass::FVisible( vecTarget, traceMask, ppBlocker ); }
Vector BodyTarget( const Vector &posSrc, bool bNoisy );
bool IsValidEnemy( CBaseEntity *pTarget );

View File

@ -66,6 +66,7 @@ BEGIN_SCRIPTDESC_ROOT( CSound, "A sound NPCs can hear." )
DEFINE_SCRIPTFUNC( ValidateOwner, "Returns true if the sound's owner is still valid or if the sound never had an owner in the first place." )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetOwner, "GetOwner", "Gets the sound's owner." )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetTarget, "GetTarget", "Gets the sound's target." )
DEFINE_SCRIPTFUNC_NAMED( ScriptFreeSound, "FreeSound", "Frees the sound from the sound list." )
END_SCRIPTDESC();
#endif

View File

@ -140,6 +140,8 @@ public:
// For VScript functions
HSCRIPT ScriptGetOwner() const { return ToHScript( m_hOwner ); }
HSCRIPT ScriptGetTarget() const { return ToHScript( m_hTarget ); }
void ScriptFreeSound() { m_flExpireTime = gpGlobals->curtime; m_bNoExpirationTime = false; }
#endif
EHANDLE m_hOwner; // sound's owner

View File

@ -14,6 +14,7 @@
#include "c_ai_basenpc.h"
#else
#include "ai_basenpc.h"
#include "ai_senses.h"
#include "globalstate.h"
#endif
@ -428,8 +429,8 @@ void RegisterSharedScriptConstants()
#ifdef GAME_DLL
//
// AI Sounds
// (QueryHearSound hook can use these)
// AI Senses
// (NPC hooks can use these)
//
ScriptRegisterConstant( g_pScriptVM, SOUND_NONE, "Sound type used in QueryHearSound hooks, etc." );
ScriptRegisterConstant( g_pScriptVM, SOUND_COMBAT, "Sound type used in QueryHearSound hooks, etc." );
@ -482,6 +483,11 @@ void RegisterSharedScriptConstants()
ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_PISTOL, "SOUNDENT_VOLUME_PISTOL", "Sound volume preset for use in InsertAISound, etc." );
ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_EMPTY, "SOUNDENT_VOLUME_PISTOL", "Sound volume preset for use in InsertAISound, etc." );
ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_ALL, "SEEN_ALL", "All NPC sight arrays. Used in GetFirstSeenEntity, etc." );
ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_HIGH_PRIORITY, "SEEN_HIGH_PRIORITY", "NPC sight array for players. Used in GetFirstSeenEntity, etc." );
ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_NPCS, "SEEN_NPCS", "NPC sight array for other NPCs. Used in GetFirstSeenEntity, etc." );
ScriptRegisterConstantNamed( g_pScriptVM, (int)SEEN_MISC, "SEEN_MISC", "NPC sight array for objects. Used in GetFirstSeenEntity, etc." );
//
// Capabilities
//