723 lines
17 KiB
C++
723 lines
17 KiB
C++
|
//NPC_reactions.cpp
|
||
|
#include "b_local.h"
|
||
|
#include "anims.h"
|
||
|
#include "g_functions.h"
|
||
|
#include "characters.h"
|
||
|
|
||
|
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
|
||
|
float g_crosshairEntDist = Q3_INFINITE;
|
||
|
int g_crosshairSameEntTime = 0;
|
||
|
int g_crosshairEntNum = ENTITYNUM_NONE;
|
||
|
int g_crosshairEntTime = 0;
|
||
|
extern int teamLastEnemyTime[];
|
||
|
extern cvar_t *g_spskill;
|
||
|
extern int PM_AnimLength( int index, animNumber_t anim );
|
||
|
extern characterName_t CharacterNames[];
|
||
|
extern void cgi_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx );
|
||
|
extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
|
||
|
extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
|
||
|
extern qboolean NPC_CheckDisguise( gentity_t *ent );
|
||
|
extern qboolean NPC_CheckLookTarget( gentity_t *self );
|
||
|
extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_CheckAttacker
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
static void NPC_CheckAttacker( gentity_t *other )
|
||
|
{
|
||
|
//FIXME: I don't see anything in here that would stop teammates from taking a teammate
|
||
|
// as an enemy. Ideally, there would be code before this to prevent that from
|
||
|
// happening, but that is presumptuous.
|
||
|
|
||
|
//valid ent - FIXME: a VALIDENT macro would be nice here
|
||
|
if ( !other )
|
||
|
return;
|
||
|
|
||
|
if ( !other->inuse )
|
||
|
return;
|
||
|
|
||
|
//Don't take a target that doesn't want to be
|
||
|
if ( other->flags & FL_NOTARGET )
|
||
|
return;
|
||
|
|
||
|
if ( NPC->svFlags & SVF_LOCKEDENEMY )
|
||
|
{//IF LOCKED, CANNOT CHANGE ENEMY!!!!!
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//If we haven't taken a target, just get mad
|
||
|
if ( NPC->enemy == NULL )//was using "other", fixed to NPC
|
||
|
{
|
||
|
G_SetEnemy( NPC, other );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//Don't take the same enemy again
|
||
|
if ( other == NPC->enemy )
|
||
|
return;
|
||
|
|
||
|
//Special case player interactions
|
||
|
if ( other == &g_entities[0] )
|
||
|
{
|
||
|
//Account for the skill level to skew the results
|
||
|
float luckThreshold;
|
||
|
|
||
|
switch ( g_spskill->integer )
|
||
|
{
|
||
|
//Easiest difficulty, mild chance of picking up the player
|
||
|
case 0:
|
||
|
luckThreshold = 0.9f;
|
||
|
break;
|
||
|
|
||
|
//Medium difficulty, half-half chance of picking up the player
|
||
|
case 1:
|
||
|
luckThreshold = 0.5f;
|
||
|
break;
|
||
|
|
||
|
//Hardest difficulty, always turn on attacking player
|
||
|
case 2:
|
||
|
default:
|
||
|
luckThreshold = 0.0f;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//Randomly pick up the target
|
||
|
if ( random() > luckThreshold )
|
||
|
{
|
||
|
G_ClearEnemy( other );
|
||
|
other->enemy = NPC;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void NPC_SetPainEvent( gentity_t *self )
|
||
|
{
|
||
|
if( self->client->playerTeam != TEAM_BORG )
|
||
|
{
|
||
|
if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
|
||
|
{
|
||
|
G_AddEvent( self, EV_PAIN, self->health );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_GetPainChance
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
#define MIN_FLINCH_DAMAGE 50
|
||
|
|
||
|
float NPC_GetPainChance( gentity_t *self, int damage )
|
||
|
{
|
||
|
if ( damage > MIN_FLINCH_DAMAGE )
|
||
|
return 1.0f;
|
||
|
|
||
|
switch ( g_spskill->integer )
|
||
|
{
|
||
|
case 0: //easy
|
||
|
return 0.75f;
|
||
|
break;
|
||
|
|
||
|
case 1://med
|
||
|
return 0.35f;
|
||
|
break;
|
||
|
|
||
|
case 2://hard
|
||
|
default:
|
||
|
return 0.05f;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_ChoosePainAnimation
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
#define MIN_PAIN_TIME 200
|
||
|
|
||
|
void NPC_ChoosePainAnimation( gentity_t *self, int damage )
|
||
|
{
|
||
|
//If we've already taken pain, then don't take it again
|
||
|
if ( level.time < self->painDebounceTime )
|
||
|
return;
|
||
|
|
||
|
int pain_anim;
|
||
|
float pain_chance = NPC_GetPainChance( self, damage );
|
||
|
|
||
|
//See what we want to do
|
||
|
switch( (int) self->client->playerTeam )
|
||
|
{
|
||
|
|
||
|
//Crewmembers shouldn't base their pain on skill level
|
||
|
case TEAM_STARFLEET:
|
||
|
|
||
|
//Don't always take pain
|
||
|
pain_chance = 0.25f; //25%
|
||
|
|
||
|
break;
|
||
|
|
||
|
case TEAM_BOTS:
|
||
|
|
||
|
//Never take pain
|
||
|
if ( ( Q_stricmp( self->NPC_type, "warriorbot" ) == 0 ) || ( Q_stricmp( self->NPC_type, "warriorbot_boss" ) == 0 ) )
|
||
|
{
|
||
|
//Have to hit them hard to make them flinch
|
||
|
if ( damage < 50 )
|
||
|
return;
|
||
|
|
||
|
//Take it less often
|
||
|
pain_chance *= 0.5f;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case TEAM_FORGE:
|
||
|
|
||
|
//Never take pain
|
||
|
if ( Q_stricmp( self->NPC_type, "Vohrsoth" ) == 0 )
|
||
|
return;
|
||
|
|
||
|
break;
|
||
|
|
||
|
//Hirogen Alpha does special shield maintenance
|
||
|
case TEAM_HIROGEN:
|
||
|
|
||
|
if ( Q_stricmp( self->NPC_type, "hirogenalpha" ) == 0 )
|
||
|
{
|
||
|
if ( Q_irand( 0, 1 ) )
|
||
|
{
|
||
|
//Set our pain animation
|
||
|
self->painDebounceTime = level.time + 1000;
|
||
|
NPC_SetAnim(self,SETANIM_BOTH,BOTH_PAIN1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//Enraged
|
||
|
self->painDebounceTime = level.time + 1000;
|
||
|
NPC_SetAnim(self,SETANIM_BOTH,BOTH_POWERUP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
|
||
|
}
|
||
|
|
||
|
//Turn the shield back on immediately, only allow one hit per shield drop
|
||
|
//FIXME: Is this reliable?
|
||
|
if ( self->client->ps.powerups[ PW_HIROGEN_SHIELD ] != -1 && !self->NPC->ignorePain )
|
||
|
{
|
||
|
self->s.powerups |= ( 1 << PW_HIROGEN_SHIELD );
|
||
|
self->client->ps.powerups[ PW_HIROGEN_SHIELD ] = level.time + 10000;
|
||
|
NPC_SetPainEvent( self );
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
//All other NPC pain reactions
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//See if we're going to flinch
|
||
|
if ( random() < pain_chance )
|
||
|
{
|
||
|
//Pick and play our animation
|
||
|
pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN3 ); //initialize to good data
|
||
|
NPC_SetAnim( self, SETANIM_BOTH, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
||
|
|
||
|
//Setup the timing for it
|
||
|
self->painDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t) pain_anim );
|
||
|
self->client->fireDelay = 0;
|
||
|
NPC_SetPainEvent( self );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
NPC_Pain
|
||
|
===============
|
||
|
*/
|
||
|
void NPC_Pain( gentity_t *self, gentity_t *other, int damage )
|
||
|
{
|
||
|
team_t otherTeam = TEAM_FREE;
|
||
|
|
||
|
if ( self->NPC == NULL )
|
||
|
return;
|
||
|
|
||
|
if ( other == NULL )
|
||
|
return;
|
||
|
|
||
|
//or just remove ->pain in player_die?
|
||
|
if ( self->client->ps.pm_type == PM_DEAD )
|
||
|
return;
|
||
|
|
||
|
if ( other == self )
|
||
|
return;
|
||
|
|
||
|
//MCG: Ignore damage from your own team for now
|
||
|
if ( other->client )
|
||
|
{
|
||
|
otherTeam = other->client->playerTeam;
|
||
|
if ( otherTeam == TEAM_DISGUISE )
|
||
|
{
|
||
|
otherTeam = TEAM_STARFLEET;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( other != self->enemy && self->client->playerTeam && other->client && otherTeam == self->client->playerTeam )
|
||
|
{//Still run pain and flee scripts
|
||
|
if ( self->client && self->NPC )
|
||
|
{//Run any pain instructions
|
||
|
if ( self->health <= (self->max_health/3) && ( VALIDSTRING( self->behaviorSet[BSET_FLEE] ) ) )
|
||
|
{
|
||
|
G_ActivateBehavior(self, BSET_FLEE);
|
||
|
}
|
||
|
else if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) )
|
||
|
{
|
||
|
G_ActivateBehavior(self, BSET_PAIN);
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//Hirogen boss with shield
|
||
|
if ( ( self->client->playerTeam == TEAM_HIROGEN ) )
|
||
|
{
|
||
|
if ( ( Q_stricmp( self->NPC_type, "hirogenalpha" ) == 0 ) && ( self->s.powerups & ( 1 << PW_HIROGEN_SHIELD ) ) )
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SaveNPCGlobals();
|
||
|
SetNPCGlobals( self );
|
||
|
|
||
|
//Do extra bits
|
||
|
if ( NPCInfo->ignorePain == qfalse )
|
||
|
{
|
||
|
//Check to take a new enemy
|
||
|
NPC_CheckAttacker( other );
|
||
|
|
||
|
if ( damage != -1 )
|
||
|
{//don't play pain anim
|
||
|
//Set our proper pain animation
|
||
|
NPC_ChoosePainAnimation( self, damage );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Attempt to run any pain instructions
|
||
|
if(self->client && self->NPC)
|
||
|
{
|
||
|
//FIXME: This needs better heuristics perhaps
|
||
|
if(self->health <= (self->max_health/3) && ( VALIDSTRING( self->behaviorSet[BSET_FLEE] ) ) )
|
||
|
{
|
||
|
G_ActivateBehavior(self, BSET_FLEE);
|
||
|
}
|
||
|
else if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) )
|
||
|
{
|
||
|
G_ActivateBehavior(self, BSET_PAIN);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Attempt to fire any paintargets we might have
|
||
|
if( self->paintarget && self->paintarget[0] )
|
||
|
{
|
||
|
G_UseTargets2(self, other, self->paintarget);
|
||
|
}
|
||
|
|
||
|
RestoreNPCGlobals();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_Touch
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
void NPC_Touch(gentity_t *self, gentity_t *other, trace_t *trace)
|
||
|
{
|
||
|
if(!self->NPC)
|
||
|
return;
|
||
|
|
||
|
SaveNPCGlobals();
|
||
|
SetNPCGlobals( self );
|
||
|
|
||
|
if ( other->client )
|
||
|
{//FIXME: if pushing against another bot, both ucmd.rightmove = 127???
|
||
|
//Except if not facing one another...
|
||
|
if ( other->health > 0 )
|
||
|
{
|
||
|
NPCInfo->touchedByPlayer = other;
|
||
|
}
|
||
|
|
||
|
if ( other == NPCInfo->goalEntity )
|
||
|
{
|
||
|
NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL;
|
||
|
}
|
||
|
|
||
|
if( !(self->svFlags&SVF_LOCKEDENEMY) && !(self->svFlags&SVF_IGNORE_ENEMIES) && !(other->flags & FL_NOTARGET) )
|
||
|
{
|
||
|
if ( self->client->enemyTeam )
|
||
|
{//See if we bumped into an enemy
|
||
|
if ( other->client->playerTeam == self->client->enemyTeam )
|
||
|
{//bumped into an enemy
|
||
|
//FIXME: should we care about disguise here?
|
||
|
if( NPCInfo->behaviorState != BS_HUNT_AND_KILL && !NPCInfo->tempBehavior )
|
||
|
{//MCG - Begin: checking specific BS mode here, this is bad, a HACK
|
||
|
//FIXME: not medics?
|
||
|
G_SetEnemy( NPC, other );
|
||
|
// NPCInfo->tempBehavior = BS_HUNT_AND_KILL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//FIXME: do this if player is moving toward me and with a certain dist?
|
||
|
/*
|
||
|
if ( other->s.number == 0 && self->client->playerTeam == other->client->playerTeam )
|
||
|
{
|
||
|
VectorAdd( self->s.pushVec, other->client->ps.velocity, self->s.pushVec );
|
||
|
}
|
||
|
*/
|
||
|
}
|
||
|
else
|
||
|
{//FIXME: check for SVF_NONNPC_ENEMY flag here?
|
||
|
if ( other == NPCInfo->goalEntity )
|
||
|
{
|
||
|
NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RestoreNPCGlobals();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_TempLookTarget
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime )
|
||
|
{
|
||
|
if ( !self->client )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( !minLookTime )
|
||
|
{
|
||
|
minLookTime = 1000;
|
||
|
}
|
||
|
|
||
|
if ( !maxLookTime )
|
||
|
{
|
||
|
maxLookTime = 1000;
|
||
|
}
|
||
|
|
||
|
if ( !NPC_CheckLookTarget( self ) )
|
||
|
{//Not already looking at something else
|
||
|
//Look at him for 1 to 3 seconds
|
||
|
NPC_SetLookTarget( self, lookEntNum, level.time + Q_irand( minLookTime, maxLookTime ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void NPC_Respond( gentity_t *self, int userNum )
|
||
|
{
|
||
|
int event;
|
||
|
|
||
|
if ( self->NPC->behaviorState == BS_FORMATION )
|
||
|
{
|
||
|
event = Q_irand(EV_MISSION1, EV_MISSION3);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( Q_irand( 0, 1 ) )
|
||
|
{
|
||
|
event = Q_irand(EV_RESPOND1, EV_RESPOND3);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
event = Q_irand(EV_BUSY1, EV_BUSY3);
|
||
|
}
|
||
|
|
||
|
if( !Q_irand( 0, 1 ) )
|
||
|
{//set looktarget to them for a second or two
|
||
|
NPC_TempLookTarget( self, userNum, 1000, 3000 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
G_AddVoiceEvent( self, event, 3000 );
|
||
|
}
|
||
|
|
||
|
void WaitNPCRespond ( gentity_t *self )
|
||
|
{
|
||
|
//make sure the responding ent is still valid
|
||
|
if ( !self->enemy || !self->enemy->client || !self->enemy->NPC )
|
||
|
{
|
||
|
G_FreeEntity( self );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( gi.S_Override[0] )
|
||
|
{//player is still talking
|
||
|
self->nextthink = level.time + 500;
|
||
|
//set enemy to not respond for a bit longer
|
||
|
self->enemy->NPC->blockedSpeechDebounceTime = level.time + 1000;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( self->enemy->health <= 0 || (!self->alt_fire && (self->enemy->NPC->scriptFlags&SCF_NO_RESPONSE)) )
|
||
|
{
|
||
|
G_FreeEntity( self );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//set enemy to be ready to respond
|
||
|
self->enemy->NPC->blockedSpeechDebounceTime = 0;
|
||
|
|
||
|
if ( self->alt_fire )
|
||
|
{//Run the NPC's usescript
|
||
|
G_ActivateBehavior( self->enemy, BSET_USE );
|
||
|
}
|
||
|
else
|
||
|
{//make them respond generically
|
||
|
NPC_Respond( self->enemy, 0 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
void G_PlayerGreet( gentity_t *self, thinkFunc_t thinkFunc )
|
||
|
|
||
|
Makes the player say a greeting and waits until he's done to make the NPC respond
|
||
|
*/
|
||
|
qboolean G_PlayerGreet( gentity_t *self, qboolean useWhenDone )
|
||
|
{
|
||
|
if ( !gi.S_Override[0] )
|
||
|
{//used by the player and the player isn't talking
|
||
|
sfxHandle_t greeting = NULL;
|
||
|
//If it's the player doing this, see who they're talking to and
|
||
|
//have Munro play the right greeting sound (character name if a known
|
||
|
//character else just lt., ensign or crewman).
|
||
|
if ( Q_irand( 0, 3 ) )
|
||
|
{
|
||
|
for ( int character = 0; character < CHARACTER_CREWMAN; character++ )
|
||
|
{
|
||
|
if ( Q_stricmp( self->NPC_type, CharacterNames[character].name ) == 0 )
|
||
|
{
|
||
|
greeting = CharacterNames[character].soundIndex;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//if didn't find one, use rank.
|
||
|
if ( greeting == NULL && Q_irand( 0, 1 ) )
|
||
|
{
|
||
|
switch( self->NPC->rank )
|
||
|
{
|
||
|
case RANK_CREWMAN:
|
||
|
case RANK_ENSIGN:
|
||
|
greeting = CharacterNames[CHARACTER_CREWMAN].soundIndex;
|
||
|
break;
|
||
|
/*
|
||
|
case RANK_ENSIGN:
|
||
|
greeting = CharacterNames[CHARACTER_ENSIGN].soundIndex;
|
||
|
break;
|
||
|
*/
|
||
|
case RANK_LT_JG:
|
||
|
case RANK_LT:
|
||
|
greeting = CharacterNames[CHARACTER_LT].soundIndex;
|
||
|
break;
|
||
|
case RANK_LT_COMM:
|
||
|
case RANK_COMMANDER:
|
||
|
greeting = CharacterNames[CHARACTER_COMM].soundIndex;
|
||
|
break;
|
||
|
case RANK_CAPTAIN:
|
||
|
greeting = CharacterNames[CHARACTER_CAPT].soundIndex;
|
||
|
break;
|
||
|
case RANK_CIVILIAN:
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}//else qwe'll use a generic greeting
|
||
|
|
||
|
if ( greeting == NULL )
|
||
|
{
|
||
|
greeting = CharacterNames[Q_irand(CHARACTER_GENERIC1, CHARACTER_GENERIC4)].soundIndex;
|
||
|
}
|
||
|
|
||
|
if ( greeting != NULL )
|
||
|
{
|
||
|
//G_SoundOnEnt( user, CHAN_VOICE, greeting );
|
||
|
cgi_S_StartSound (NULL, 0, CHAN_VOICE, greeting );
|
||
|
|
||
|
//Responder waits until Munro is done talking then makes the NPC respond.
|
||
|
gentity_t *responder = G_Spawn();
|
||
|
responder->enemy = self;
|
||
|
responder->alt_fire = useWhenDone;
|
||
|
responder->e_ThinkFunc = thinkF_WaitNPCRespond;
|
||
|
responder->nextthink = level.time + 500;
|
||
|
//set self to not respond for a bit longer
|
||
|
self->NPC->blockedSpeechDebounceTime = level.time + 1000;
|
||
|
return qtrue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return qfalse;
|
||
|
}
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_UseResponse
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone )
|
||
|
{
|
||
|
qboolean noGreet = qfalse;
|
||
|
|
||
|
if ( !self->NPC || !self->client )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( user->s.number != 0 )
|
||
|
{//not used by the player
|
||
|
if ( useWhenDone )
|
||
|
{
|
||
|
G_ActivateBehavior( self, BSET_USE );
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( user->client && self->client->playerTeam != user->client->playerTeam )
|
||
|
{//only those on the same team react
|
||
|
if ( useWhenDone )
|
||
|
{
|
||
|
G_ActivateBehavior( self, BSET_USE );
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( self->NPC->blockedSpeechDebounceTime > level.time )
|
||
|
{//I'm not responding right now
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( gi.S_Override[self->s.number] )
|
||
|
{//I'm talking already
|
||
|
if ( !useWhenDone )
|
||
|
{//you're not trying to use me
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{//I'm talking, so don't greet me
|
||
|
noGreet = qtrue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !noGreet && user->s.number == 0 && G_PlayerGreet( self, useWhenDone ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( useWhenDone )
|
||
|
{
|
||
|
G_ActivateBehavior( self, BSET_USE );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NPC_Respond( self, user->s.number );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_Use
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator )
|
||
|
{
|
||
|
if (self->client->ps.pm_type == PM_DEAD)
|
||
|
{//or just remove ->pain in player_die?
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SaveNPCGlobals();
|
||
|
SetNPCGlobals( self );
|
||
|
|
||
|
if(self->client && self->NPC)
|
||
|
{//Run any use instructions
|
||
|
if ( self->NPC->behaviorState == BS_MEDIC_HIDE && activator->client )
|
||
|
{//Heal me NOW, dammit!
|
||
|
if ( activator->health < activator->max_health )
|
||
|
{//person needs help
|
||
|
if ( self->NPC->eventualGoal != activator )
|
||
|
{//not my current patient already
|
||
|
NPC_TakePatient( activator );
|
||
|
if ( self->behaviorSet[BSET_USE] )
|
||
|
{
|
||
|
G_ActivateBehavior( self, BSET_USE );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( !self->enemy && activator->s.number == 0 && !gi.S_Override[self->s.number] && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) )
|
||
|
{//I don't have an enemy and I'm not talking and I was used by the player
|
||
|
NPC_UseResponse( self, other, qfalse );
|
||
|
}
|
||
|
}
|
||
|
else if ( self->behaviorSet[BSET_USE] )
|
||
|
{
|
||
|
NPC_UseResponse( self, other, qtrue );
|
||
|
}
|
||
|
else if ( isMedic( self ) )
|
||
|
{//Heal me NOW, dammit!
|
||
|
NPC_TakePatient( activator );
|
||
|
}
|
||
|
else if ( !self->enemy && activator->s.number == 0 && !gi.S_Override[self->s.number] && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) )
|
||
|
{//I don't have an enemy and I'm not talking and I was used by the player
|
||
|
NPC_UseResponse( self, other, qfalse );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RestoreNPCGlobals();
|
||
|
}
|
||
|
|
||
|
void NPC_CheckPlayerAim( void )
|
||
|
{
|
||
|
//FIXME: need appropriate dialogue
|
||
|
/*
|
||
|
gentity_t *player = &g_entities[0];
|
||
|
|
||
|
if ( player && player->client && player->client->ps.weapon > (int)(WP_NONE) && player->client->ps.weapon < (int)(WP_TRICORDER) )
|
||
|
{//player has a weapon ready
|
||
|
if ( g_crosshairEntNum == NPC->s.number && level.time - g_crosshairEntTime < 200
|
||
|
&& g_crosshairSameEntTime >= 3000 && g_crosshairEntDist < 256 )
|
||
|
{//if the player holds the crosshair on you for a few seconds
|
||
|
//ask them what the fuck they're doing
|
||
|
G_AddVoiceEvent( NPC, Q_irand( EV_FF_1A, EV_FF_1C ), 0 );
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
void NPC_CheckAllClear( void )
|
||
|
{
|
||
|
//FIXME: need to make this happen only once after losing enemies, not over and over again
|
||
|
/*
|
||
|
if ( NPC->client && !NPC->enemy && level.time - teamLastEnemyTime[NPC->client->playerTeam] > 10000 )
|
||
|
{//Team hasn't seen an enemy in 10 seconds
|
||
|
if ( !Q_irand( 0, 2 ) )
|
||
|
{
|
||
|
G_AddVoiceEvent( NPC, Q_irand(EV_SETTLE1, EV_SETTLE3), 3000 );
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
}
|