From 970887d706ac20a56a09f102a55008a21201f52a Mon Sep 17 00:00:00 2001 From: "ALLEN-PC\\acj30" Date: Thu, 2 Jan 2025 09:32:33 -0600 Subject: [PATCH] NPC memory fixes and VScript expansions --- sp/src/game/server/ai_basenpc.cpp | 311 +++++++++++++++++- sp/src/game/server/ai_basenpc.h | 44 ++- sp/src/game/server/ai_basenpc_schedule.cpp | 56 ++++ sp/src/game/server/ai_default.cpp | 7 + sp/src/game/server/ai_memory.cpp | 12 + sp/src/game/server/ai_memory.h | 3 + sp/src/game/server/ai_senses.cpp | 60 ++++ sp/src/game/server/ai_senses.h | 11 + sp/src/game/server/ai_squad.cpp | 29 +- sp/src/game/server/ai_squad.h | 10 +- sp/src/game/server/baseentity.cpp | 121 ++++++- sp/src/game/server/baseentity.h | 5 +- sp/src/game/server/soundent.cpp | 1 + sp/src/game/server/soundent.h | 2 + .../shared/mapbase/vscript_consts_shared.cpp | 10 +- 15 files changed, 663 insertions(+), 19 deletions(-) diff --git a/sp/src/game/server/ai_basenpc.cpp b/sp/src/game/server/ai_basenpc.cpp index 2d4c064c..afe22540 100644 --- a/sp/src/game/server/ai_basenpc.cpp +++ b/sp/src/game/server/ai_basenpc.cpp @@ -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(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( 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(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(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( 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)) { @@ -8972,6 +9184,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; } @@ -8981,6 +9207,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; } @@ -11522,6 +11762,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() @@ -12296,7 +12543,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." ) @@ -12304,6 +12561,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." ) @@ -12351,6 +12613,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." ) @@ -12369,6 +12641,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 @@ -12398,6 +12677,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 diff --git a/sp/src/game/server/ai_basenpc.h b/sp/src/game/server/ai_basenpc.h index dbc229e2..55b2732e 100644 --- a/sp/src/game/server/ai_basenpc.h +++ b/sp/src/game/server/ai_basenpc.h @@ -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 //----------------------------------------------------- @@ -2394,6 +2426,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: diff --git a/sp/src/game/server/ai_basenpc_schedule.cpp b/sp/src/game/server/ai_basenpc_schedule.cpp index 5f57b51d..7774b96a 100644 --- a/sp/src/game/server/ai_basenpc_schedule.cpp +++ b/sp/src/game/server/ai_basenpc_schedule.cpp @@ -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", "1", 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; diff --git a/sp/src/game/server/ai_default.cpp b/sp/src/game/server/ai_default.cpp index 8f660ec3..ec35dc58 100644 --- a/sp/src/game/server/ai_default.cpp +++ b/sp/src/game/server/ai_default.cpp @@ -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(); diff --git a/sp/src/game/server/ai_memory.cpp b/sp/src/game/server/ai_memory.cpp index 7ac69311..1f337910 100644 --- a/sp/src/game/server/ai_memory.cpp +++ b/sp/src/game/server/ai_memory.cpp @@ -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() diff --git a/sp/src/game/server/ai_memory.h b/sp/src/game/server/ai_memory.h index d348c53e..c300ec04 100644 --- a/sp/src/game/server/ai_memory.h +++ b/sp/src/game/server/ai_memory.h @@ -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(); } diff --git a/sp/src/game/server/ai_senses.cpp b/sp/src/game/server/ai_senses.cpp index 45e23e09..abf4e1d9 100644 --- a/sp/src/game/server/ai_senses.cpp +++ b/sp/src/game/server/ai_senses.cpp @@ -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 + //============================================================================= diff --git a/sp/src/game/server/ai_senses.h b/sp/src/game/server/ai_senses.h index fed85eb9..e0284909 100644 --- a/sp/src/game/server/ai_senses.h +++ b/sp/src/game/server/ai_senses.h @@ -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 ); diff --git a/sp/src/game/server/ai_squad.cpp b/sp/src/game/server/ai_squad.cpp index 15e7fb68..f364896d 100644 --- a/sp/src/game/server/ai_squad.cpp +++ b/sp/src/game/server/ai_squad.cpp @@ -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( 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; } diff --git a/sp/src/game/server/ai_squad.h b/sp/src/game/server/ai_squad.h index 9604d1f0..0d886c7f 100644 --- a/sp/src/game/server/ai_squad.h +++ b/sp/src/game/server/ai_squad.h @@ -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: diff --git a/sp/src/game/server/baseentity.cpp b/sp/src/game/server/baseentity.cpp index 9df5cc09..2543aa6e 100644 --- a/sp/src/game/server/baseentity.cpp +++ b/sp/src/game/server/baseentity.cpp @@ -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( CBaseEntity, "Root class of all server-side entities" ) @@ -2375,6 +2421,7 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" 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" ) @@ -2513,14 +2560,15 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" 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() @@ -2542,6 +2590,12 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" 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(); @@ -7542,6 +7596,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 } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/baseentity.h b/sp/src/game/server/baseentity.h index acbffdb2..b52f51f3 100644 --- a/sp/src/game/server/baseentity.h +++ b/sp/src/game/server/baseentity.h @@ -1430,7 +1430,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. @@ -2165,10 +2165,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; diff --git a/sp/src/game/server/soundent.cpp b/sp/src/game/server/soundent.cpp index 59273206..a84f7676 100644 --- a/sp/src/game/server/soundent.cpp +++ b/sp/src/game/server/soundent.cpp @@ -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 diff --git a/sp/src/game/server/soundent.h b/sp/src/game/server/soundent.h index 7ce3d401..47a1f742 100644 --- a/sp/src/game/server/soundent.h +++ b/sp/src/game/server/soundent.h @@ -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 diff --git a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp index 2ced7086..b1477f74 100644 --- a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp +++ b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp @@ -14,6 +14,7 @@ #include "c_ai_basenpc.h" #else #include "ai_basenpc.h" +#include "ai_senses.h" #include "globalstate.h" #endif @@ -416,8 +417,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." ); @@ -470,6 +471,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 //