void NPC_PlayConfusionSound( gentity_t *self ) { if ( self->health > 0 ) { if ( self->enemy ||//was mad !TIMER_Done( self, "enemyLastVisible" ) ||//saw something suspicious self->client->renderInfo.lookTarget == 0//was looking at player ) { self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this G_AddVoiceEvent( self, Q_irand( EV_CONFUSE2, EV_CONFUSE3 ), 2000 ); } else if ( self->NPC && self->NPC->investigateDebounceTime+self->NPC->pauseTime > level.time )//was checking something out { self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this G_AddVoiceEvent( self, EV_CONFUSE1, 2000 ); } //G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); } //reset him to be totally unaware again TIMER_Set( self, "enemyLastVisible", 0 ); self->NPC->tempBehavior = BS_DEFAULT; //self->NPC->behaviorState = BS_PATROL; G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? self->NPC->investigateCount = 0; }
//[CoOp] //[SPPortComplete] void G_CheckCharmed( gentity_t *self ) { if ( self && self->client && self->client->playerTeam == NPCTEAM_PLAYER && self->NPC && self->NPC->charmedTime && (self->NPC->charmedTime < level.time ||self->health <= 0) ) {//we were charmed, set us back! //NOTE: presumptions here... team_t savTeam = self->client->enemyTeam; self->client->enemyTeam = self->client->playerTeam; self->client->playerTeam = savTeam; self->client->leader = NULL; self->NPC->charmedTime = 0; if ( self->health > 0 ) { if ( self->NPC->tempBehavior == BS_FOLLOW_LEADER ) { self->NPC->tempBehavior = BS_DEFAULT; } G_ClearEnemy( self ); //say something to let player know you've snapped out of it G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); } } }
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 NPC_Grenadier_Pain( gentity_t *self, gentity_t *attacker, int damage ) { self->NPC->localState = LSTATE_UNDERFIRE; TIMER_Set( self, "duck", -1 ); TIMER_Set( self, "stand", 2000 ); NPC_Pain( self, attacker, damage ); if ( !damage && self->health > 0 ) {//FIXME: better way to know I was pushed G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 ); } }
void NPC_Sniper_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod ) { self->NPC->localState = LSTATE_UNDERFIRE; TIMER_Set( self, "duck", -1 ); TIMER_Set( self, "stand", 2000 ); NPC_Pain( self, inflictor, other, point, damage, mod ); if ( !damage && self->health > 0 ) {//FIXME: better way to know I was pushed G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); } }
void NPC_Grenadier_PlayConfusionSound( gentity_t *self ) {//FIXME: make this a custom sound in sound set if ( self->health > 0 ) { G_AddVoiceEvent( self, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), 2000 ); } //reset him to be totally unaware again TIMER_Set( self, "enemyLastVisible", 0 ); TIMER_Set( self, "flee", 0 ); self->NPC->squadState = SQUAD_IDLE; self->NPC->tempBehavior = BS_DEFAULT; //self->NPC->behaviorState = BS_PATROL; G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? self->NPC->investigateCount = 0; }
void NPC_CheckCharmed( void ) { if ( NPCInfo->charmedTime && NPCInfo->charmedTime < level.time && NPC->client ) {//we were charmed, set us back! NPC->client->playerTeam = NPC->genericValue1; NPC->client->enemyTeam = NPC->genericValue2; NPC->s.teamowner = NPC->genericValue3; NPC->client->leader = NULL; if ( NPCInfo->tempBehavior == BS_FOLLOW_LEADER ) { NPCInfo->tempBehavior = BS_DEFAULT; } G_ClearEnemy( NPC ); NPCInfo->charmedTime = 0; //say something to let player know you've snapped out of it G_AddVoiceEvent( NPC, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); } }
void NPC_CheckCharmed( void ) { if ( NPC->client->playerTeam == TEAM_PLAYER && NPCInfo->charmedTime && NPCInfo->charmedTime < level.time && NPC->client ) {//we were charmed, set us back! //NOTE: presumptions here... team_t savTeam = NPC->client->enemyTeam; NPC->client->enemyTeam = NPC->client->playerTeam; NPC->client->playerTeam = savTeam; NPC->client->leader = NULL; if ( NPCInfo->tempBehavior == BS_FOLLOW_LEADER ) { NPCInfo->tempBehavior = BS_DEFAULT; } G_ClearEnemy( NPC ); NPCInfo->charmedTime = 0; //say something to let player know you've snapped out of it G_AddVoiceEvent( NPC, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); } }
void NPC_HandleAIFlags (void) { //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers if ( NPCInfo->aiFlags & NPCAI_LOST ) {//Print that you need help! //FIXME: shouldn't remove this just yet if cg_draw needs it NPCInfo->aiFlags &= ~NPCAI_LOST; if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) {//We can't nav to our enemy //Drop enemy and see if we should search for him NPC_LostEnemyDecideChase(); } } //been told to play a victory sound after a delay if ( NPCInfo->greetingDebounceTime && NPCInfo->greetingDebounceTime < level.time ) { G_AddVoiceEvent( NPC, Q_irand(EV_VICTORY1, EV_VICTORY3), Q_irand( 2000, 4000 ) ); NPCInfo->greetingDebounceTime = 0; } if ( NPCInfo->ffireCount > 0 ) { if ( NPCInfo->ffireFadeDebounce < level.time ) { NPCInfo->ffireCount--; NPCInfo->ffireFadeDebounce = level.time + 3000; } } if ( d_patched.integer ) {//use patch-style navigation if ( NPCInfo->consecutiveBlockedMoves > 20 ) {//been stuck for a while, try again? NPCInfo->consecutiveBlockedMoves = 0; } } }
void NPC_Surrender( void ) {//FIXME: say "don't shoot!" if we weren't already surrendering if ( NPC->client->ps.weaponTime || PM_InKnockDown( &NPC->client->ps ) ) { return; } if ( NPC->s.weapon != WP_NONE && NPC->s.weapon != WP_MELEE && NPC->s.weapon != WP_SABER ) { WP_DropWeapon( NPC, NULL ); } if ( NPCInfo->surrenderTime < level.time - 5000 ) {//haven't surrendered for at least 6 seconds, tell them what you're doing //FIXME: need real dialogue EV_SURRENDER NPCInfo->blockedSpeechDebounceTime = 0;//make sure we say this G_AddVoiceEvent( NPC, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 3000 ); } NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); NPC->client->ps.torsoAnimTimer = 1000; NPCInfo->surrenderTime = level.time + 1000;//stay surrendered for at least 1 second //FIXME: while surrendering, make a big sight/sound alert? Or G_AlertTeam? }
void NPC_Respond( gentity_t *self, int userNum ) { int event = -1; /* 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 ); } //some last-minute hacked in responses switch ( self->client->NPC_class ) { case CLASS_JAN: if ( self->enemy ) { if ( !Q_irand( 0, 2 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 1 ) ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 2 ) ) { event = EV_SUSPICIOUS4; } else if ( !Q_irand( 0, 1 ) ) { event = EV_SOUND1; } else { event = EV_CONFUSE1; } break; case CLASS_LANDO: if ( self->enemy ) { if ( !Q_irand( 0, 2 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 1 ) ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 6 ) ) { event = EV_SIGHT2; } else if ( !Q_irand( 0, 5 ) ) { event = EV_GIVEUP4; } else if ( Q_irand( 0, 4 ) > 1 ) { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } else { event = Q_irand( EV_JDETECTED1, EV_JDETECTED2 ); } break; case CLASS_LUKE: if ( self->enemy ) { event = EV_COVER1; } else { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } break; case CLASS_JEDI: case CLASS_KYLE: if ( !self->enemy ) { /* if ( !(self->svFlags&SVF_IGNORE_ENEMIES) && (self->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) && self->client->enemyTeam == TEAM_ENEMY ) { event = Q_irand( EV_ANGER1, EV_ANGER3 ); } else */ { event = Q_irand( EV_CONFUSE1, EV_CONFUSE3 ); } } break; case CLASS_PRISONER: if ( self->enemy ) { if ( Q_irand( 0, 1 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } } else { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } break; case CLASS_REBEL: if ( self->enemy ) { if ( !Q_irand( 0, 2 ) ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else { event = Q_irand( EV_DETECTED1, EV_DETECTED5 ); } } else { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } break; case CLASS_BESPIN_COP: if ( !Q_stricmp( "bespincop", self->NPC_type ) ) {//variant 1 if ( self->enemy ) { if ( Q_irand( 0, 9 ) > 6 ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 6 ) > 4 ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 3 ) ) { event = Q_irand( EV_SIGHT2, EV_SIGHT3 ); } else if ( !Q_irand( 0, 1 ) ) { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } else if ( !Q_irand( 0, 2 ) ) { event = EV_LOST1; } else if ( !Q_irand( 0, 1 ) ) { event = EV_ESCAPING2; } else { event = EV_GIVEUP4; } } else {//variant2 if ( self->enemy ) { if ( Q_irand( 0, 9 ) > 6 ) { event = Q_irand( EV_CHASE1, EV_CHASE3 ); } else if ( Q_irand( 0, 6 ) > 4 ) { event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); } else { event = Q_irand( EV_COVER1, EV_COVER5 ); } } else if ( !Q_irand( 0, 3 ) ) { event = Q_irand( EV_SIGHT1, EV_SIGHT2 ); } else if ( !Q_irand( 0, 1 ) ) { event = Q_irand( EV_SOUND1, EV_SOUND3 ); } else if ( !Q_irand( 0, 2 ) ) { event = EV_LOST1; } else if ( !Q_irand( 0, 1 ) ) { event = EV_GIVEUP3; } else { event = EV_CONFUSE1; } } break; case CLASS_R2D2: // droid G_Sound(self, G_SoundIndex(va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)))); break; case CLASS_R5D2: // droid G_Sound(self, G_SoundIndex(va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)))); break; case CLASS_MOUSE: // droid G_Sound(self, G_SoundIndex(va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)))); break; case CLASS_GONK: // droid G_Sound(self, G_SoundIndex(va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)))); break; case CLASS_JAWA: G_SoundOnEnt(self, CHAN_VOICE, va("sound/chars/jawa/misc/chatter%d.wav",Q_irand(1, 6)) ); if ( self->NPC ) { self->NPC->blockedSpeechDebounceTime = level.time + 2000; } break; default: break; } if ( event != -1 ) { //hack here because we reuse some "combat" and "extra" sounds qboolean addFlag = (qboolean)((self->NPC->scriptFlags&SCF_NO_COMBAT_TALK) != 0); self->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK; G_AddVoiceEvent( self, event, 3000 ); if ( addFlag ) { self->NPC->scriptFlags |= SCF_NO_COMBAT_TALK; } } }
void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc, int voiceEvent = -1 ) { //If we've already taken pain, then don't take it again if ( level.time < self->painDebounceTime && mod != MOD_ELECTROCUTE && mod != MOD_MELEE ) {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? return; } int pain_anim = -1; float pain_chance; if ( self->s.weapon == WP_THERMAL && self->client->fireDelay > 0 ) {//don't interrupt thermal throwing anim return; } else if (self->client->ps.powerups[PW_GALAK_SHIELD]) { return; } else if ( self->client->NPC_class == CLASS_GALAKMECH ) { if ( hitLoc == HL_GENERIC1 ) {//hit the antenna! pain_chance = 1.0f; self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); } else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) {//shield up return; } else if ( self->health > 200 && damage < 100 ) {//have a *lot* of health pain_chance = 0.05f; } else {//the lower my health and greater the damage, the more likely I am to play a pain anim pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; } } else if ( self->client && self->client->playerTeam == TEAM_PLAYER && other && !other->s.number ) {//ally shot by player always complains pain_chance = 1.1f; } else { if ( other && (other->s.weapon == WP_SABER || mod == MOD_ELECTROCUTE || mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/) ) { if ( self->client->ps.weapon == WP_SABER && other->s.number < MAX_CLIENTS ) {//hmm, shouldn't *always* react to damage from player if I have a saber pain_chance = 1.05f - ((self->NPC->rank)/(float)RANK_CAPTAIN); } else { pain_chance = 1.0f;//always take pain from saber } } else if ( mod == MOD_GAS ) { pain_chance = 1.0f; } else if ( mod == MOD_MELEE ) {//higher in rank (skill) we are, less likely we are to be fazed by a punch pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); } else if ( self->client->NPC_class == CLASS_PROTOCOL ) { pain_chance = 1.0f; } else { pain_chance = NPC_GetPainChance( self, damage ); } if ( self->client->NPC_class == CLASS_DESANN ) { pain_chance *= 0.5f; } } //See if we're going to flinch if ( Q_flrand(0.0f, 1.0f) < pain_chance ) { //Pick and play our animation if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) ) { G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } else if ( mod == MOD_GAS ) { //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code! if ( TIMER_Done( self, "gasChokeSound" ) ) { TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) ); G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } } else if ( (self->client->ps.eFlags&EF_FORCE_DRAINED) ) { NPC_SetPainEvent( self ); } else {//not being force-gripped or force-drained if ( G_CheckForStrongAttackMomentum( self ) || PM_SpinningAnim( self->client->ps.legsAnim ) || PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) || PM_InKnockDown( &self->client->ps ) || PM_RollingAnim( self->client->ps.legsAnim ) || (PM_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain return; } else {//play an anim if ( self->client->NPC_class == CLASS_GALAKMECH ) {//only has 1 for now //FIXME: never plays this, it seems... pain_anim = BOTH_PAIN1; } else if ( mod == MOD_MELEE ) { pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( self->s.weapon == WP_SABER ) {//temp HACK: these are the only 2 pain anims that look good when holding a saber pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( mod != MOD_ELECTROCUTE ) { pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); } if ( pain_anim == -1 ) { pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 ); } self->client->ps.saberAnimLevel = SS_FAST;//next attack must be a quick attack self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in int parts = SETANIM_BOTH; if ( PM_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) { parts = SETANIM_LEGS; } self->NPC->aiFlags &= ~NPCAI_KNEEL; NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } if ( voiceEvent != -1 ) { G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); } else { NPC_SetPainEvent( self ); } } //Setup the timing for it if ( mod == MOD_ELECTROCUTE ) { self->painDebounceTime = level.time + 4000; } self->painDebounceTime = level.time + PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t) pain_anim ); self->client->fireDelay = 0; } }
void NPC_HandleAIFlags (void) { //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers if ( NPCInfo->aiFlags & NPCAI_LOST ) {//Print that you need help! //FIXME: shouldn't remove this just yet if cg_draw needs it NPCInfo->aiFlags &= ~NPCAI_LOST; /* if ( showWaypoints ) { Q3_DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint ); } */ if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) {//We can't nav to our enemy //Drop enemy and see if we should search for him NPC_LostEnemyDecideChase(); } } if ( NPCInfo->aiFlags & NPCAI_AWAITING_COMM ) { if(NPCInfo->commWaitTime < level.time) { //FIXME: we shouldn't assume team_leader, we should remember who sent this hail! NPC_SetSayState(NPC, NPC->client->team_leader, Q_irand(SAY_BADHAIL1, SAY_BADHAIL4)); NPCInfo->aiFlags &= ~NPCAI_AWAITING_COMM; } } /* NPCInfo->canShove = qfalse; //flag never gets set in current nav implementation if (NPCInfo->aiFlags & NPCAI_BLOCKED) { NPCInfo->consecutiveBlockedMoves++; NPCInfo->blockedDebounceTime = level.time + 1000;//Remember you were blocked for a whole second //If totally blocked, should we see if we can jump the obstacle? if(NPCInfo->blockingEntNum == ENTITYNUM_WORLD)//WORLD {//Can't go anywhere G_ActivateBehavior( NPC, BSET_STUCK); //If you're in formation, what do we do here? } else { gentity_t *blocker = &g_entities[NPCInfo->blockingEntNum]; if( NPCInfo->consecutiveBlockedMoves > 10 ) {//Okay, shove them out of the way! if(NPCInfo->shoveCount > 3) {//Already tried shoving 4 times, just stand here NPCInfo->canShove = qfalse; } else { NPCInfo->canShove = qtrue; } } if(blocker->client && blocker->client->playerTeam == NPC->client->playerTeam) {//Should we ask it to get out of the way? //FIXME: NPC_SetSayBState(NPC, blocker, Q_irand(SAY_MOVEIT1, SAY_MOVEIT4);// ? if(NPCInfo->blockedSpeechDebounceTime < level.time) { if ( NPC->behaviorSet[BSET_BLOCKED] ) { G_ActivateBehavior( NPC, BSET_BLOCKED); } else { G_AddVoiceEvent( NPC, Q_irand(EV_BLOCKED1, EV_BLOCKED3), 0 ); } #ifdef _DEBUG //gi.Printf( "%s: 'Hey, %s, move it!'\n", NPC->targetname, blocker->targetname ); #endif //NPCInfo->blockedSpeechDebounceTime = level.time + 10000;//FIXME: make a define //Ok, need to make it get out of the way... } } else if((blocker->client || blocker->takedamage) && blocker->health > 0 && blocker->health < 200 ) {//Attack it!? Set enemy and temp behavior? Hmm... //Careful, what if it's explosive? G_SetEnemy( NPC, blocker ); if( NPCInfo->consecutiveBlockedMoves == 30 ) {//Blocked for three seconds straight G_ActivateBehavior( NPC, BSET_BLOCKED); } } } } else if(NPCInfo->blockedDebounceTime < level.time) {//Only clear if haven't been blocked for a whole second NPCInfo->consecutiveBlockedMoves = 0; NPCInfo->shoveCount = 0; } if(NPCInfo->shoveDebounce < level.time) {//We have shoved for 1 second at least NPCInfo->lastShoveDir = 0.0f; } //NAV_ClearBlockedInfo(NPC); */ //MRJ Request: if ( NPCInfo->aiFlags & NPCAI_GREET_ALLIES && !NPC->enemy )//what if "enemy" is the greetEnt? {//If no enemy, look for teammates to greet //FIXME: don't say hi to the same guy over and over again. if ( NPCInfo->greetingDebounceTime < level.time ) {//Has been at least 2 seconds since we greeted last if ( !NPCInfo->greetEnt ) {//Find a teammate whom I'm facing and who is facing me and within 128 NPCInfo->greetEnt = NPC_PickAlly( qtrue, 128, qtrue, qtrue ); } if ( NPCInfo->greetEnt && !Q_irand(0, 5) ) {//Start greeting someone qboolean greeted = qfalse; //TODO: If have a greetscript, run that instead? //FIXME: make them greet back? if( !Q_irand( 0, 2 ) ) {//Play gesture anim (press gesture button?) greeted = qtrue; NPC_SetAnim( NPC, SETANIM_TORSO, Q_irand( BOTH_GESTURE1, BOTH_GESTURE3 ), SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); //NOTE: play full-body gesture if not moving? } if( !Q_irand( 0, 2 ) ) {//Play random voice greeting sound greeted = qtrue; //FIXME: need NPC sound sets G_AddVoiceEvent( NPC, Q_irand(EV_GREET1, EV_GREET3), 2000 ); } if( !Q_irand( 0, 1 ) ) {//set looktarget to them for a second or two greeted = qtrue; NPC_TempLookTarget( NPC, NPCInfo->greetEnt->s.number, 1000, 3000 ); } if ( greeted ) {//Did at least one of the things above //Don't greet again for 2 - 4 seconds NPCInfo->greetingDebounceTime = level.time + Q_irand( 2000, 4000 ); NPCInfo->greetEnt = NULL; } } } } }
void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, vec3_t point, int damage, int mod, int hitLoc, int voiceEvent ) { int pain_anim = -1; float pain_chance; //If we've already taken pain, then don't take it again if ( level.time < self->painDebounceTime && /*mod != MOD_ELECTROCUTE &&*/ mod != MOD_MELEE ) //rwwFIXMEFIXME: MOD_ELECTROCUTE { //FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? return; } if ( self->s.weapon == WP_THERMAL && self->client->ps.weaponTime > 0 ) { //don't interrupt thermal throwing anim return; } else if ( self->client->NPC_class == CLASS_GALAKMECH ) { if ( hitLoc == HL_GENERIC1 ) { //hit the antenna! pain_chance = 1.0f; // self->s.powerups |= ( 1 << PW_SHOCKED ); // self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); //rwwFIXMEFIXME: support for this } // else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) // {//shield up // return; // } //rwwFIXMEFIXME: and this else if ( self->health > 200 && damage < 100 ) { //have a *lot* of health pain_chance = 0.05f; } else { //the lower my health and greater the damage, the more likely I am to play a pain anim pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; } } else if ( self->client && self->client->playerTeam == NPCTEAM_PLAYER && other && !other->s.number ) { //ally shot by player always complains pain_chance = 1.1f; } else { if ( other && other->s.weapon == WP_SABER || /*mod == MOD_ELECTROCUTE ||*/ mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/ ) { pain_chance = 1.0f;//always take pain from saber } else if ( mod == MOD_MELEE ) { //higher in rank (skill) we are, less likely we are to be fazed by a punch pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); } else if ( self->client->NPC_class == CLASS_PROTOCOL ) { pain_chance = 1.0f; } else { pain_chance = NPC_GetPainChance( self, damage ); } if ( self->client->NPC_class == CLASS_DESANN ) { pain_chance *= 0.5f; } } //See if we're going to flinch if ( random() < pain_chance ) { int animLength; //Pick and play our animation if ( self->client->ps.fd.forceGripBeingGripped < level.time ) { //not being force-gripped or force-drained if ( /*G_CheckForStrongAttackMomentum( self ) //rwwFIXMEFIXME: Is this needed? ||*/ PM_SpinningAnim( self->client->ps.legsAnim ) || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) || PM_InKnockDown( &self->client->ps ) || PM_RollingAnim( self->client->ps.legsAnim ) || (BG_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) { //strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain } else { //play an anim int parts; if ( self->client->NPC_class == CLASS_GALAKMECH ) { //only has 1 for now //FIXME: never plays this, it seems... pain_anim = BOTH_PAIN1; } else if ( mod == MOD_MELEE ) { pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( self->s.weapon == WP_SABER ) { //temp HACK: these are the only 2 pain anims that look good when holding a saber pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); } /* else if ( mod != MOD_ELECTROCUTE ) { pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); } */ if ( pain_anim == -1 ) { pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN1, BOTH_PAIN18 ); } self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;//next attack must be a quick attack self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in parts = SETANIM_BOTH; if ( BG_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) { parts = SETANIM_LEGS; } if (pain_anim != -1) { NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } if ( voiceEvent != -1 ) { G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); } else { NPC_SetPainEvent( self ); } } else { G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } //Setup the timing for it /* if ( mod == MOD_ELECTROCUTE ) { self->painDebounceTime = level.time + 4000; } */ animLength = bgAllAnims[self->localAnimIndex].anims[pain_anim].numFrames * fabs((float)(bgHumanoidAnimations[pain_anim].frameLerp)); self->painDebounceTime = level.time + animLength; self->client->ps.weaponTime = 0; } }
void NPC_HandleAIFlags (void) { //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers if ( NPCInfo->aiFlags & NPCAI_LOST ) {//Print that you need help! //FIXME: shouldn't remove this just yet if cg_draw needs it NPCInfo->aiFlags &= ~NPCAI_LOST; /* if ( showWaypoints ) { Q3_DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint ); } */ if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) {//We can't nav to our enemy //Drop enemy and see if we should search for him NPC_LostEnemyDecideChase(); } } //MRJ Request: /* if ( NPCInfo->aiFlags & NPCAI_GREET_ALLIES && !NPC->enemy )//what if "enemy" is the greetEnt? {//If no enemy, look for teammates to greet //FIXME: don't say hi to the same guy over and over again. if ( NPCInfo->greetingDebounceTime < level.time ) {//Has been at least 2 seconds since we greeted last if ( !NPCInfo->greetEnt ) {//Find a teammate whom I'm facing and who is facing me and within 128 NPCInfo->greetEnt = NPC_PickAlly( qtrue, 128, qtrue, qtrue ); } if ( NPCInfo->greetEnt && !Q_irand(0, 5) ) {//Start greeting someone qboolean greeted = qfalse; //TODO: If have a greetscript, run that instead? //FIXME: make them greet back? if( !Q_irand( 0, 2 ) ) {//Play gesture anim (press gesture button?) greeted = qtrue; NPC_SetAnim( NPC, SETANIM_TORSO, Q_irand( BOTH_GESTURE1, BOTH_GESTURE3 ), SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); //NOTE: play full-body gesture if not moving? } if( !Q_irand( 0, 2 ) ) {//Play random voice greeting sound greeted = qtrue; //FIXME: need NPC sound sets //G_AddVoiceEvent( NPC, Q_irand(EV_GREET1, EV_GREET3), 2000 ); } if( !Q_irand( 0, 1 ) ) {//set looktarget to them for a second or two greeted = qtrue; NPC_TempLookTarget( NPC, NPCInfo->greetEnt->s.number, 1000, 3000 ); } if ( greeted ) {//Did at least one of the things above //Don't greet again for 2 - 4 seconds NPCInfo->greetingDebounceTime = level.time + Q_irand( 2000, 4000 ); NPCInfo->greetEnt = NULL; } } } } */ //been told to play a victory sound after a delay if ( NPCInfo->greetingDebounceTime && NPCInfo->greetingDebounceTime < level.time ) { G_AddVoiceEvent( NPC, Q_irand(EV_VICTORY1, EV_VICTORY3), Q_irand( 2000, 4000 ) ); NPCInfo->greetingDebounceTime = 0; } if ( NPCInfo->ffireCount > 0 ) { if ( NPCInfo->ffireFadeDebounce < level.time ) { NPCInfo->ffireCount--; //Com_Printf( "drop: %d < %d\n", NPCInfo->ffireCount, 3+((2-g_spSkill.integer)*2) ); NPCInfo->ffireFadeDebounce = level.time + 3000; } } if ( d_patched.integer ) {//use patch-style navigation if ( NPCInfo->consecutiveBlockedMoves > 20 ) {//been stuck for a while, try again? NPCInfo->consecutiveBlockedMoves = 0; } } }
void NPC_BSGM_Attack( void ) { //Don't do anything if we're hurt if ( NPC->painDebounceTime > level.time ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //FIXME: if killed enemy, use victory anim if ( NPC->enemy && NPC->enemy->health <= 0 && !NPC->enemy->s.number ) {//my enemy is dead if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer += 500; NPC->client->ps.torsoAnimTimer += 500; } } else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) { if ( NPC->client->ps.torsoAnimTimer <= 500 ) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); NPC->client->ps.legsAnimTimer = -1; NPC->client->ps.torsoAnimTimer = -1; } } else if ( NPC->wait ) { if ( TIMER_Done( NPC, "gloatTime" ) ) { GM_StartGloat(); } else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared { NPCInfo->goalEntity = NPC->enemy; GM_Move(); } else {//got there GM_StartGloat(); } } NPC_FaceEnemy( qtrue ); NPC_UpdateAngles( qtrue, qtrue ); return; } //If we don't have an enemy, just idle if ( NPC_CheckEnemyExt() == qfalse || !NPC->enemy ) { NPC->enemy = NULL; NPC_BSGM_Patrol(); return; } enemyLOS = enemyCS = qfalse; bMove = qtrue; faceEnemy = qfalse; shoot = qfalse; hitAlly = qfalse; VectorClear( impactPos ); enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) { shoot = qfalse; if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) {//time to smack //recheck enemyDist and InFront if ( enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) { vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); //hurt them G_Sound( NPC->enemy, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) {//smackdown int knockAnim = BOTH_KNOCKDOWN1; if ( PM_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) {//knockdown from crouch knockAnim = BOTH_KNOCKDOWN4; } //throw them smackDir[2] = 1; VectorNormalize( smackDir ); G_Throw( NPC->enemy, smackDir, 50 ); NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } else {//uppercut //throw them G_Throw( NPC->enemy, smackDir, 100 ); //make them backflip NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } //done with the damage NPCInfo->blockedDebounceTime = 1; } } } else if ( NPC->lockCount ) //already shooting laser {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; if ( NPC->lockCount == 1 ) {//charging up if ( TIMER_Done( NPC, "beamDelay" ) ) {//time to start the beam int laserAnim; if ( Q_irand( 0, 1 ) ) { laserAnim = BOTH_ATTACK2; } else { laserAnim = BOTH_ATTACK7; } NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //turn on beam effect NPC->lockCount = 2; G_PlayEffect( "galak/trace_beam", NPC->s.number ); NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); if ( !NPCInfo->coverTarg ) {//for moving looping sound at end of trace NPCInfo->coverTarg = G_Spawn(); if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); NPCInfo->coverTarg->svFlags |= SVF_BROADCAST; NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); } } } } else {//in the actual attack now if ( !NPC->client->ps.torsoAnimTimer ) {//attack done! NPC->lockCount = 0; G_FreeEntity( NPCInfo->coverTarg ); NPC->s.loopSound = 0; NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); } else {//attack still going //do the trace and damage trace_t trace; vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); gi.trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT ); if ( trace.allsolid || trace.startsolid ) {//oops, in a wall if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); } } else {//clear if ( trace.fraction < 1.0f ) {//hit something gentity_t *traceEnt = &g_entities[trace.entityNum]; if ( traceEnt && traceEnt->takedamage ) {//damage it G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_ENERGY ); } } if ( NPCInfo->coverTarg ) { G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); } if ( !Q_irand( 0, 5 ) ) { G_SoundAtSpot( trace.endpos, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); } } } } } else {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack /* if ( NPC->s.weapon == WP_REPEATER && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire && NPC->enemy->s.weapon == WP_SABER //enemy using saber && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) && !Q_irand( 0, 50 ) ) {//he's deflecting my shots, switch to the laser or the lob fire for a while TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist < MAX_LOB_DIST_SQUARED) ) {//shield down, use laser NPC_GM_StartLaser(); } } else*/ if ( !NPC->client->ps.powerups[PW_GALAK_SHIELD] && enemyDist < MELEE_DIST_SQUARED && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && G_StandardHumanoid( NPC->enemy->NPC_type ) )//within 80 and in front {//our shield is down, and enemy within 80, if very close, use melee attack to slap away if ( TIMER_Done( NPC, "attackDelay" ) ) { //animate me int swingAnim; if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) {//generator down, use random melee swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut } else {//always knock-away swingAnim = BOTH_ATTACK5;//uppercut } //FIXME: swing sound NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer + Q_irand( 1000, 3000 ) ); //delay the hurt until the proper point in the anim TIMER_Set( NPC, "smackTime", 600 ); NPCInfo->blockedDebounceTime = 0; //FIXME: say something? } } else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && TIMER_Done( NPC, "attackDelay" ) && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.3f ) && ((!Q_irand( 0, 10*(2-g_spskill->integer))&& enemyDist > MIN_LOB_DIST_SQUARED&& enemyDist < MAX_LOB_DIST_SQUARED) ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) && NPC->enemy->s.weapon != WP_TURRET ) {//sometimes use the laser beam attack, but only after he's taken down our generator shoot = qfalse; NPC_GM_StartLaser(); } else if ( enemyDist < MIN_LOB_DIST_SQUARED && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) && TIMER_Done( NPC, "noRapid" ) )//256 {//enemy within 256 if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//shooting an explosive, but enemy too close, switch to primary fire NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } else if ( (enemyDist > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) && TIMER_Done( NPC, "noLob" ) )//448 {//enemy more than 448 away and we are ready to try lob fire again if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//enemy far enough away to use lobby explosives NPCInfo->scriptFlags |= SCF_ALT_FIRE; NPC->alt_fire = qtrue; //FIXME: use weap raise & lower anims NPC_ChangeWeapon( WP_REPEATER ); } } } //can we see our target? if ( NPC_ClearLOS( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS enemyLOS = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS = qfalse;//not true, but should stop us from firing NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon } else {//can we shoot our target? if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_LOB_DIST_SQUARED )//256 { enemyCS = qfalse;//not true, but should stop us from firing hitAlly = qtrue;//us! //FIXME: if too close, run away! } else { int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); } else {//Hmm, have to get around this bastard NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) {//would hit an ally, don't fire!!! hitAlly = qtrue; } else {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire } } } } } else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) { if ( NPCInfo->enemyCheckDebounceTime < 8 ) { int speech = -1; switch( NPCInfo->enemyCheckDebounceTime ) { case 0: case 1: case 2: speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; break; case 3: case 4: case 5: speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; break; case 6: case 7: speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; break; } NPCInfo->enemyCheckDebounceTime++; if ( speech != -1 ) { G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); } } } NPCInfo->enemyLastSeenTime = level.time; int hit = NPC_ShotEntity( NPC->enemy, impactPos ); gentity_t *hitEnt = &g_entities[hit]; if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) || ( hitEnt && hitEnt->takedamage ) ) {//can hit enemy or will hit glass or other breakable, so shoot anyway enemyCS = qtrue; } else { faceEnemy = qtrue; NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } } if ( enemyLOS ) { faceEnemy = qtrue; } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } if ( enemyCS ) { shoot = qtrue; //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS } else { if ( !NPCInfo->goalEntity ) { NPCInfo->goalEntity = NPC->enemy; } if ( NPCInfo->goalEntity == NPC->enemy ) {//for now, always chase the enemy bMove = qtrue; } } //Check for movement to take care of GM_CheckMoveState(); //See if we should override shooting decision with any special considerations GM_CheckFireState(); if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot && TIMER_Done( NPC, "attackDelay" ) ) { vec3_t muzzle; vec3_t angles; vec3_t target; vec3_t velocity = {0,0,0}; vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorCopy( NPC->enemy->currentOrigin, target ); target[0] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[1] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); target[2] += Q_flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); //Find the desired angles qboolean clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, velocity, qtrue, NPC->s.number, NPC->enemy->s.number, 300, 1100, 1500, qtrue ); if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS&&enemyCS) ) {//no clear lob shot and no lob shot that will hit something breakable if ( enemyLOS && enemyCS && TIMER_Done( NPC, "noRapid" ) ) {//have a clear straight shot, so switch to primary NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; NPC->alt_fire = qfalse; NPC_ChangeWeapon( WP_REPEATER ); //keep this weap for a bit TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); } else { shoot = qfalse; } } else { vectoangles( velocity, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); VectorCopy( velocity, NPC->client->hiddenDir ); NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); } } else if ( faceEnemy ) {//face the enemy NPC_FaceEnemy( qtrue ); } if ( !TIMER_Done( NPC, "standTime" ) ) { bMove = qfalse; } if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) {//not supposed to chase my enemies if ( NPCInfo->goalEntity == NPC->enemy ) {//goal is my entity, so don't bMove bMove = qfalse; } } if ( bMove && !NPC->lockCount ) {//bMove toward goal if ( NPCInfo->goalEntity && NPC->client->ps.legsAnim != BOTH_ALERT1 && NPC->client->ps.legsAnim != BOTH_ATTACK2 && NPC->client->ps.legsAnim != BOTH_ATTACK4 && NPC->client->ps.legsAnim != BOTH_ATTACK5 && NPC->client->ps.legsAnim != BOTH_ATTACK7 ) { bMove = GM_Move(); } else { bMove = qfalse; } } if ( !TIMER_Done( NPC, "flee" ) ) {//running away faceEnemy = qfalse; } //FIXME: check scf_face_move_dir here? if ( !faceEnemy ) {//we want to face in the dir we're running if ( !bMove ) {//if we haven't moved, we should look in the direction we last looked? VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); } if ( bMove ) {//don't run away and shoot NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; NPCInfo->desiredPitch = 0; shoot = qfalse; } } NPC_UpdateAngles( qtrue, qtrue ); if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) { shoot = qfalse; } if ( NPC->enemy && NPC->enemy->enemy ) { if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) shoot = qfalse; } } //FIXME: don't shoot right away! if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "attackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } //also: if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) {//crush turrets if ( G_BoundsOverlap( NPC->absmin, NPC->absmax, NPC->enemy->absmin, NPC->enemy->absmax ) ) {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) { NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); } else { G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); } } } else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) {//touched enemy if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) {//zap him! //animate me NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoAnimTimer ); TIMER_Set( NPC, "standTime", NPC->client->ps.legsAnimTimer ); //FIXME: debounce this? NPCInfo->touchedByPlayer = NULL; //FIXME: some shield effect? NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; vec3_t smackDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, smackDir ); smackDir[2] += 30; VectorNormalize( smackDir ); G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE ); //throw them G_Throw( NPC->enemy, smackDir, 100 ); NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); if ( NPC->enemy->client ) { NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; } //stop any attacks ucmd.buttons = 0; } } if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) { if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) { if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) { G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 3; } else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) { G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 2; } else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) { G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); NPCInfo->movementSpeech = 1; } } } }
void NPC_GM_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod,int hitLoc ) { if ( self->client->ps.powerups[PW_GALAK_SHIELD] == 0 ) {//shield is currently down //FIXME: allow for radius damage? if ( (hitLoc==HL_GENERIC1) && (self->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH) ) { int newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*antenna_base" ); if ( newBolt != -1 ) { GM_CreateExplosion( self, newBolt ); } gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_shield_off", TURN_OFF ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna", TURN_OFF ); gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "torso_antenna_base_cap_off", TURN_ON ); self->client->ps.powerups[PW_GALAK_SHIELD] = 0;//temp, for effect self->client->ps.stats[STAT_ARMOR] = 0;//no more armor self->NPC->investigateDebounceTime = 0;//stop recharging NPC_SetAnim( self, SETANIM_BOTH, BOTH_ALERT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); TIMER_Set( self, "attackDelay", self->client->ps.torsoAnimTimer ); G_AddEvent( self, Q_irand( EV_DEATH1, EV_DEATH3 ), self->health ); } } else {//store the point for shield impact if ( point ) { VectorCopy( point, self->pos4 ); self->client->poisonTime = level.time; } } if ( !self->lockCount && !self->client->ps.torsoAnimTimer ) {//don't interrupt laser sweep attack or other special attacks/moves if ( self->count < 4 && self->health > 100 && hitLoc != HL_GENERIC1 ) { if ( self->delay < level.time ) { int speech; switch( self->count ) { default: case 0: speech = EV_PUSHED1; break; case 1: speech = EV_PUSHED2; break; case 2: speech = EV_PUSHED3; break; case 3: speech = EV_DETECTED1; break; } self->count++; self->NPC->blockedSpeechDebounceTime = 0; G_AddVoiceEvent( self, speech, Q_irand( 3000, 5000 ) ); self->delay = level.time + Q_irand( 5000, 7000 ); } } else { NPC_Pain( self, inflictor, attacker, point, damage, mod, hitLoc ); } } else if ( hitLoc == HL_GENERIC1 ) { NPC_SetPainEvent( self ); self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); } if ( inflictor && inflictor->lastEnemy == self ) {//He force-pushed my own lobfires back at me if ( mod == MOD_REPEATER_ALT && !Q_irand( 0, 2 ) ) { if ( TIMER_Done( self, "noRapid" ) ) { self->NPC->scriptFlags &= ~SCF_ALT_FIRE; self->alt_fire = qfalse; TIMER_Set( self, "noLob", Q_irand( 2000, 6000 ) ); } else {//hopefully this will make us fire the laser TIMER_Set( self, "noLob", Q_irand( 1000, 2000 ) ); } } else if ( mod == MOD_REPEATER && !Q_irand( 0, 5 ) ) { if ( TIMER_Done( self, "noLob" ) ) { self->NPC->scriptFlags |= SCF_ALT_FIRE; self->alt_fire = qtrue; TIMER_Set( self, "noRapid", Q_irand( 2000, 6000 ) ); } else {//hopefully this will make us fire the laser TIMER_Set( self, "noRapid", Q_irand( 1000, 2000 ) ); } } } }