Skip to content

Commit 4c2823e

Browse files
authored
Merge pull request #271 from Blixibon/mapbase/feature/new-companion-npc-concepts
New and ported response concepts for companion NPCs
2 parents 4cea5d1 + 4e7814e commit 4c2823e

14 files changed

+325
-0
lines changed

sp/src/game/server/ai_basenpc.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,13 +671,27 @@ void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bo
671671
{
672672
BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
673673

674+
#ifdef MAPBASE
675+
// Alyx's enemy ignited code from below can now be run on any NPC as long as
676+
// it's our current enemy.
677+
if ( GetEnemy() && GetEnemy()->IsNPC() )
678+
{
679+
GetEnemy()->MyNPCPointer()->EnemyIgnited( this );
680+
}
681+
#endif
682+
674683
#ifdef HL2_EPISODIC
675684
CBasePlayer *pPlayer = AI_GetSinglePlayer();
676685
if ( pPlayer && pPlayer->IRelationType( this ) != D_LI )
677686
{
678687
CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx();
679688

689+
#ifdef MAPBASE
690+
// Alyx's code continues to run if Alyx was not this NPC's enemy.
691+
if ( alyx && alyx != GetEnemy() )
692+
#else
680693
if ( alyx )
694+
#endif
681695
{
682696
alyx->EnemyIgnited( this );
683697
}
@@ -16476,6 +16490,21 @@ void CAI_BaseNPC::ModifyOrAppendEnemyCriteria( AI_CriteriaSet& set, CBaseEntity
1647616490
set.AppendCriteria( "enemyclass", g_pGameRules->AIClassText( pEnemy->Classify() ) ); // UTIL_VarArgs("%i", pEnemy->Classify())
1647716491
set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(pEnemy) ) );
1647816492
set.AppendCriteria( "timesincecombat", "-1" );
16493+
16494+
CAI_BaseNPC *pNPC = pEnemy->MyNPCPointer();
16495+
if (pNPC)
16496+
{
16497+
set.AppendCriteria("enemy_is_npc", "1");
16498+
16499+
set.AppendCriteria( "enemy_activity", CAI_BaseNPC::GetActivityName( pNPC->GetActivity() ) );
16500+
set.AppendCriteria( "enemy_weapon", pNPC->GetActiveWeapon() ? pNPC->GetActiveWeapon()->GetClassname() : "0" );
16501+
}
16502+
else
16503+
{
16504+
set.AppendCriteria("enemy_is_npc", "0");
16505+
}
16506+
16507+
pEnemy->AppendContextToCriteria( set, "enemy_" );
1647916508
}
1648016509
else
1648116510
{

sp/src/game/server/ai_basenpc.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,9 @@ class CAI_BaseNPC : public CBaseCombatCharacter,
19951995
//---------------------------------
19961996

19971997
virtual void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false );
1998+
#ifdef MAPBASE
1999+
virtual void EnemyIgnited( CAI_BaseNPC *pVictim ) {}
2000+
#endif
19982001
virtual bool PassesDamageFilter( const CTakeDamageInfo &info );
19992002

20002003
//---------------------------------

sp/src/game/server/ai_playerally.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ ConceptInfo_t g_ConceptInfos[] =
128128

129129
// Passenger behaviour
130130
{ TLK_PASSENGER_NEW_RADAR_CONTACT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
131+
132+
#ifdef MAPBASE
133+
{ TLK_TAKING_FIRE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
134+
{ TLK_NEW_ENEMY, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
135+
{ TLK_COMBAT_IDLE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, },
136+
#endif
131137
};
132138

133139
//-----------------------------------------------------------------------------
@@ -1259,6 +1265,38 @@ void CAI_PlayerAlly::OnKilledNPC( CBaseCombatCharacter *pKilled )
12591265
}
12601266
}
12611267

1268+
#ifdef MAPBASE
1269+
//-----------------------------------------------------------------------------
1270+
//-----------------------------------------------------------------------------
1271+
void CAI_PlayerAlly::OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd )
1272+
{
1273+
BaseClass::OnEnemyRangeAttackedMe( pEnemy, vecDir, vecEnd );
1274+
1275+
if ( IRelationType( pEnemy ) <= D_FR )
1276+
{
1277+
AI_CriteriaSet modifiers;
1278+
ModifyOrAppendEnemyCriteria( modifiers, pEnemy );
1279+
1280+
Vector vecEntDir = (pEnemy->EyePosition() - EyePosition());
1281+
float flDot = DotProduct( vecEntDir.Normalized(), vecDir );
1282+
modifiers.AppendCriteria( "shot_dot", CNumStr( flDot ) );
1283+
1284+
if (GetLastDamageTime() == gpGlobals->curtime)
1285+
modifiers.AppendCriteria( "missed", "0" );
1286+
else
1287+
modifiers.AppendCriteria( "missed", "1" );
1288+
1289+
// Check if they're out of ammo
1290+
if ( pEnemy->IsCombatCharacter() && pEnemy->MyCombatCharacterPointer()->GetActiveWeapon() && pEnemy->MyCombatCharacterPointer()->GetActiveWeapon()->Clip1() <= 0 )
1291+
modifiers.AppendCriteria( "last_attack", "1" );
1292+
else
1293+
modifiers.AppendCriteria( "last_attack", "0" );
1294+
1295+
SpeakIfAllowed( TLK_TAKING_FIRE, modifiers );
1296+
}
1297+
}
1298+
#endif
1299+
12621300
//-----------------------------------------------------------------------------
12631301
void CAI_PlayerAlly::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
12641302
{

sp/src/game/server/ai_playerally.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@
132132
#define TLK_TGCATCHUP "TLK_TGCATCHUP"
133133
#define TLK_TGENDTOUR "TLK_TGENDTOUR"
134134

135+
#ifdef MAPBASE
136+
// Additional concepts for companions in mods
137+
#define TLK_TAKING_FIRE "TLK_TAKING_FIRE" // Someone fired at me (regardless of whether I was hit)
138+
#define TLK_NEW_ENEMY "TLK_NEW_ENEMY" // A new enemy appeared while combat was already in progress
139+
#define TLK_COMBAT_IDLE "TLK_COMBAT_IDLE" // Similar to TLK_ATTACKING, but specifically for when *not* currently attacking (e.g. when in cover or reloading)
140+
#endif
141+
135142
//-----------------------------------------------------------------------------
136143

137144
#define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this
@@ -315,6 +322,10 @@ class CAI_PlayerAlly : public CAI_BaseActor
315322
//---------------------------------
316323
void OnKilledNPC( CBaseCombatCharacter *pKilled );
317324

325+
#ifdef MAPBASE
326+
void OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd );
327+
#endif
328+
318329
//---------------------------------
319330
// Damage handling
320331
//---------------------------------

sp/src/game/server/basecombatcharacter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ class CBaseCombatCharacter : public CBaseFlex
263263

264264
virtual bool CanBecomeServerRagdoll( void ) { return true; }
265265

266+
#ifdef MAPBASE
267+
virtual void OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd ) {}
268+
#endif
269+
266270
// -----------------------
267271
// Damage
268272
// -----------------------

sp/src/game/server/hl2/ai_behavior_functank.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,24 @@ int CAI_FuncTankBehavior::SelectSchedule()
190190
return SCHED_IDLE_STAND;
191191
}
192192

193+
//-----------------------------------------------------------------------------
194+
// Purpose:
195+
//-----------------------------------------------------------------------------
196+
void CAI_FuncTankBehavior::ModifyOrAppendCriteria( AI_CriteriaSet &set )
197+
{
198+
BaseClass::ModifyOrAppendCriteria( set );
199+
200+
#ifdef MAPBASE
201+
set.AppendCriteria( "ft_mounted", m_bMounted ? "1" : "0" );
202+
203+
if (m_hFuncTank)
204+
{
205+
set.AppendCriteria( "ft_classname", m_hFuncTank->GetClassname() );
206+
m_hFuncTank->AppendContextToCriteria( set, "ft_" );
207+
}
208+
#endif
209+
}
210+
193211
//-----------------------------------------------------------------------------
194212
// Purpose:
195213
// Input : activity -

sp/src/game/server/hl2/ai_behavior_functank.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class CAI_FuncTankBehavior : public CAI_SimpleBehavior
5555
bool CanManTank( CFuncTank *pTank, bool bForced );
5656
#endif
5757

58+
void ModifyOrAppendCriteria( AI_CriteriaSet &set );
59+
5860
Activity NPC_TranslateActivity( Activity activity );
5961

6062
// Conditions:

sp/src/game/server/hl2/npc_BaseZombie.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,9 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve
26082608
// Inherit some misc. properties
26092609
pCrab->m_bForceServerRagdoll = m_bForceServerRagdoll;
26102610
pCrab->m_iViewHideFlags = m_iViewHideFlags;
2611+
2612+
// Add response context for companion response (more reliable than checking for post-death zombie entity)
2613+
pCrab->AddContext( "from_zombie", "1", 2.0f );
26112614
#endif
26122615

26132616
// make me the crab's owner to avoid collision issues

sp/src/game/server/hl2/npc_alyx_episodic.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,10 +1092,14 @@ void CNPC_Alyx::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &
10921092
//-----------------------------------------------------------------------------
10931093
void CNPC_Alyx::EnemyIgnited( CAI_BaseNPC *pVictim )
10941094
{
1095+
#ifdef MAPBASE
1096+
BaseClass::EnemyIgnited( pVictim );
1097+
#else
10951098
if ( FVisible( pVictim ) )
10961099
{
10971100
SpeakIfAllowed( TLK_ENEMY_BURNING );
10981101
}
1102+
#endif
10991103
}
11001104

11011105
//-----------------------------------------------------------------------------
@@ -1252,6 +1256,7 @@ void CNPC_Alyx::DoCustomSpeechAI( void )
12521256

12531257
CBasePlayer *pPlayer = AI_GetSinglePlayer();
12541258

1259+
#ifndef MAPBASE // Ported to CNPC_PlayerCompanion
12551260
if ( HasCondition(COND_NEW_ENEMY) && GetEnemy() )
12561261
{
12571262
if ( GetEnemy()->Classify() == CLASS_HEADCRAB )
@@ -1278,6 +1283,7 @@ void CNPC_Alyx::DoCustomSpeechAI( void )
12781283
}
12791284
}
12801285
}
1286+
#endif
12811287

12821288
// Darkness mode speech
12831289
ClearCondition( COND_ALYX_IN_DARK );
@@ -1917,6 +1923,7 @@ int CNPC_Alyx::SelectSchedule( void )
19171923
//-----------------------------------------------------------------------------
19181924
int CNPC_Alyx::SelectScheduleDanger( void )
19191925
{
1926+
#ifndef MAPBASE
19201927
if( HasCondition( COND_HEAR_DANGER ) )
19211928
{
19221929
CSound *pSound;
@@ -1929,6 +1936,7 @@ int CNPC_Alyx::SelectScheduleDanger( void )
19291936
SpeakIfAllowed( TLK_DANGER_ZOMBINE_GRENADE );
19301937
}
19311938
}
1939+
#endif
19321940

19331941
return BaseClass::SelectScheduleDanger();
19341942
}

sp/src/game/server/hl2/npc_playercompanion.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "mapbase/GlobalStrings.h"
3636
#include "world.h"
3737
#include "vehicle_base.h"
38+
#include "npc_headcrab.h"
39+
#include "npc_BaseZombie.h"
3840
#endif
3941

4042
ConVar ai_debug_readiness("ai_debug_readiness", "0" );
@@ -640,6 +642,55 @@ void CNPC_PlayerCompanion::DoCustomSpeechAI( void )
640642
{
641643
SpeakIfAllowed( TLK_PLDEAD );
642644
}
645+
646+
#ifdef MAPBASE
647+
// Unique new enemy concepts ported from Alyx
648+
// The casts have been changed to dynamic_cast due to the risk of non-CBaseHeadcrab/CNPC_BaseZombie enemies using those classes
649+
if ( HasCondition(COND_NEW_ENEMY) && GetEnemy() )
650+
{
651+
int nClass = GetEnemy()->Classify();
652+
if ( nClass == CLASS_HEADCRAB )
653+
{
654+
CBaseHeadcrab *pHC = dynamic_cast<CBaseHeadcrab*>(GetEnemy());
655+
if ( pHC )
656+
{
657+
// If we see a headcrab for the first time as he's jumping at me, freak out!
658+
if ( ( GetEnemy()->GetEnemy() == this ) && pHC->IsJumping() && gpGlobals->curtime - GetEnemies()->FirstTimeSeen(GetEnemy()) < 0.5 )
659+
{
660+
SpeakIfAllowed( "TLK_SPOTTED_INCOMING_HEADCRAB" );
661+
}
662+
else
663+
{
664+
// If we see a headcrab leaving a zombie that just died, mention it
665+
// (Note that this is now a response context since some death types remove the zombie instantly)
666+
int nContext = pHC->FindContextByName( "from_zombie" );
667+
if ( nContext > -1 && !ContextExpired( nContext ) ) // pHC->GetOwnerEntity() && ( pHC->GetOwnerEntity()->Classify() == CLASS_ZOMBIE ) && !pHC->GetOwnerEntity()->IsAlive()
668+
{
669+
SpeakIfAllowed( "TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE" );
670+
}
671+
}
672+
}
673+
}
674+
else if ( nClass == CLASS_ZOMBIE )
675+
{
676+
CNPC_BaseZombie *pZombie = dynamic_cast<CNPC_BaseZombie*>(GetEnemy());
677+
// If we see a zombie getting up, mention it
678+
if ( pZombie && pZombie->IsGettingUp() )
679+
{
680+
SpeakIfAllowed( "TLK_SPOTTED_ZOMBIE_WAKEUP" );
681+
}
682+
}
683+
684+
if ( gpGlobals->curtime - GetEnemies()->TimeAtFirstHand( GetEnemy() ) <= 1.0f && nClass != CLASS_BULLSEYE )
685+
{
686+
// New concept which did not originate from Alyx, but is in the same category as the above concepts.
687+
// This is meant to be used when a new enemy enters the arena while combat is already in progress.
688+
// (Note that this can still trigger when combat begins, but unlike TLK_STARTCOMBAT, it has no delay
689+
// between combat engagements.)
690+
SpeakIfAllowed( TLK_NEW_ENEMY );
691+
}
692+
}
693+
#endif
643694
}
644695

645696
//-----------------------------------------------------------------------------
@@ -910,8 +961,21 @@ int CNPC_PlayerCompanion::SelectScheduleDanger()
910961

911962
if ( pSound && (pSound->m_iType & SOUND_DANGER) )
912963
{
964+
#ifdef MAPBASE
965+
if ( pSound->SoundChannel() == SOUNDENT_CHANNEL_ZOMBINE_GRENADE )
966+
{
967+
SetSpeechTarget( pSound->m_hOwner );
968+
SpeakIfAllowed( TLK_DANGER_ZOMBINE_GRENADE );
969+
}
970+
else if (!(pSound->SoundContext() & (SOUND_CONTEXT_MORTAR | SOUND_CONTEXT_FROM_SNIPER)) || IsOkToCombatSpeak())
971+
{
972+
SetSpeechTarget( pSound->m_hOwner );
973+
SpeakIfAllowed( TLK_DANGER );
974+
}
975+
#else
913976
if ( !(pSound->SoundContext() & (SOUND_CONTEXT_MORTAR|SOUND_CONTEXT_FROM_SNIPER)) || IsOkToCombatSpeak() )
914977
SpeakIfAllowed( TLK_DANGER );
978+
#endif
915979

916980
if ( HasCondition( COND_PC_SAFE_FROM_MORTAR ) )
917981
{
@@ -4309,6 +4373,20 @@ void CNPC_PlayerCompanion::Event_KilledOther( CBaseEntity *pVictim, const CTakeD
43094373
}
43104374
}
43114375

4376+
//-----------------------------------------------------------------------------
4377+
// Purpose: Called by enemy NPC's when they are ignited
4378+
// Input : pVictim - entity that was ignited
4379+
//-----------------------------------------------------------------------------
4380+
void CNPC_PlayerCompanion::EnemyIgnited( CAI_BaseNPC *pVictim )
4381+
{
4382+
BaseClass::EnemyIgnited( pVictim );
4383+
4384+
if ( FVisible( pVictim ) )
4385+
{
4386+
SpeakIfAllowed( TLK_ENEMY_BURNING );
4387+
}
4388+
}
4389+
43124390
//-----------------------------------------------------------------------------
43134391
// Purpose: Handles custom combat speech stuff ported from Alyx.
43144392
//-----------------------------------------------------------------------------
@@ -4376,6 +4454,21 @@ void CNPC_PlayerCompanion::DoCustomCombatAI( void )
43764454
{
43774455
SpeakIfAllowed( TLK_MANY_ENEMIES );
43784456
}
4457+
4458+
// If we're not currently attacking or vulnerable, try speaking
4459+
else if ( gpGlobals->curtime - GetLastAttackTime() > 1.0f && (!HasCondition( COND_SEE_ENEMY ) || IsCurSchedule( SCHED_RELOAD ) || IsCurSchedule( SCHED_HIDE_AND_RELOAD )) )
4460+
{
4461+
int chance = ( IsMoving() ) ? 20 : 3;
4462+
if ( ShouldSpeakRandom( TLK_COMBAT_IDLE, chance ) )
4463+
{
4464+
AI_CriteriaSet modifiers;
4465+
4466+
modifiers.AppendCriteria( "in_cover", HasMemory( bits_MEMORY_INCOVER ) ? "1" : "0" );
4467+
modifiers.AppendCriteria( "lastseenenemy", UTIL_VarArgs( "%f", gpGlobals->curtime - GetEnemyLastTimeSeen() ) );
4468+
4469+
SpeakIfAllowed( TLK_COMBAT_IDLE, modifiers );
4470+
}
4471+
}
43794472
}
43804473
#endif
43814474

0 commit comments

Comments
 (0)