/* ------------------------- GM_Move ------------------------- */ static qboolean GM_Move( void ) { qboolean moved; navInfo_t info; NPCInfo->combatMove = qtrue;//always move straight toward our goal moved = NPC_MoveToGoal( qtrue ); //Get the move info NAV_GetLastMove( &info ); //FIXME: if we bump into another one of our guys and can't get around him, just stop! //If we hit our target, then stop and fire! if ( info.flags & NIF_COLLISION ) { if ( info.blocker == NPC->enemy ) { GM_HoldPosition(); } } //If our move failed, then reset if ( moved == qfalse ) {//FIXME: if we're going to a combat point, need to pick a different one if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) {//can't transfer movegoal or stop when a script we're running is waiting to complete GM_HoldPosition(); } } return moved; }
static void GM_HoldPosition( void ) { NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) {//don't have a script waiting for me to get to my point, okay to stop trying and stand NPCInfo->goalEntity = NULL; } }
static void GM_CheckMoveState( void ) { if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) {//moving toward a goal that a script is waiting on, so don't stop for anything! move4 = qtrue; } //See if we're moving towards a goal, not the enemy if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) { //Did we make it? if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, qfalse ) || ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) && enemyLOS4 && enemyDist4 <= 10000 ) ) {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy NPC_ReachedGoal(); //don't attack right away TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels return; } } }
void NPC_SetPainEvent( gentity_t *self ) { if ( !self->NPC || !(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) { // no more borg // if( self->client->playerTeam != TEAM_BORG ) // { //if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) if (!trap_ICARUS_TaskIDPending(self, TID_CHAN_VOICE) && self->client) { //G_AddEvent( self, EV_PAIN, floor((float)self->health/self->max_health*100.0f) ); G_AddEvent( self, EV_PAIN, floor((float)self->health/self->client->ps.stats[STAT_MAX_HEALTH]*100.0f) ); //rwwFIXMEFIXME: Do this properly? } // } } }
//RACC - NPC version of G_AddEvent for sound useage. //seems to do some checking to prevent event spamming void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ) { if ( !self->NPC ) { return; } if ( !self->client || self->client->ps.pm_type >= PM_DEAD ) { return; } if ( self->NPC->blockedSpeechDebounceTime > level.time ) { return; } if ( trap_ICARUS_TaskIDPending( self, TID_CHAN_VOICE ) ) { return; } if ( (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK) && ( (event >= EV_ANGER1 && event <= EV_VICTORY3) || (event >= EV_CHASE1 && event <= EV_SUSPICIOUS5) ) )//(event < EV_FF_1A || event > EV_FF_3C) && (event < EV_RESPOND1 || event > EV_MISSION3) ) { return; } if ( (self->NPC->scriptFlags&SCF_NO_ALERT_TALK) && (event >= EV_GIVEUP1 && event <= EV_SUSPICIOUS5) ) { return; } //FIXME: Also needs to check for teammates. Don't want // everyone babbling at once //NOTE: was losing too many speech events, so we do it directly now, screw networking! //G_AddEvent( self, event, 0 ); G_SpeechEvent( self, event ); //won't speak again for 5 seconds (unless otherwise specified) self->NPC->blockedSpeechDebounceTime = level.time + ((speakDebounceTime==0) ? 5000 : speakDebounceTime); }
void NPC_BSDefault( void ) { // vec3_t enemyDir; // float enemyDist; // float shootDist; // qboolean enemyFOV = qfalse; // qboolean enemyShotFOV = qfalse; // qboolean enemyPVS = qfalse; // vec3_t enemyHead; // vec3_t muzzle; // qboolean enemyLOS = qfalse; // qboolean enemyCS = qfalse; qboolean move = qtrue; // qboolean shoot = qfalse; if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) { WeaponThink( qtrue ); } if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) {//being forced to walk if( NPC->client->ps.torsoAnim != TORSO_SURRENDER_START ) { NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD ); } } //look for a new enemy if don't have one and are allowed to look, validate current enemy if have one NPC_CheckEnemy( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES), qfalse, qtrue ); if ( !NPC->enemy ) {//still don't have an enemy if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) {//check for alert events //FIXME: Check Alert events, see if we should investigate or just look at it int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); //There is an event to look at if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) {//heard/saw something if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) {//was a big event if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) {//an enemy G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); } } else {//FIXME: investigate lesser events } } //FIXME: also check our allies' condition? } } if ( NPC->enemy && !(NPCInfo->scriptFlags&SCF_FORCED_MARCH) ) { // just use the stormtrooper attack AI... NPC_CheckGetNewWeapon(); if ( NPC->client->leader && NPCInfo->goalEntity == NPC->client->leader && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) { NPC_ClearGoal(); } NPC_BSST_Attack(); return; /* //have an enemy //FIXME: if one of these fails, meaning we can't shoot, do we really need to do the rest? VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir ); enemyDist = VectorNormalize( enemyDir ); enemyDist *= enemyDist; shootDist = NPC_MaxDistSquaredForWeapon(); enemyFOV = InFOV( NPC->enemy, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ); enemyShotFOV = InFOV( NPC->enemy, NPC, 20, 20 ); enemyPVS = gi.inPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); if ( enemyPVS ) {//in the pvs trace_t tr; CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemyHead ); enemyHead[2] -= Q_flrand( 0.0f, NPC->enemy->maxs[2]*0.5f ); CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); enemyLOS = NPC_ClearLOS( muzzle, enemyHead ); gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, enemyHead, NPC->s.number, MASK_SHOT ); enemyCS = NPC_EvaluateShot( tr.entityNum, qtrue ); } else {//skip thr 2 traces since they would have to fail enemyLOS = qfalse; enemyCS = qfalse; } if ( enemyCS && enemyShotFOV ) {//can hit enemy if we want NPC->cantHitEnemyCounter = 0; } else {//can't hit NPC->cantHitEnemyCounter++; } if ( enemyCS && enemyShotFOV && enemyDist < shootDist ) {//can shoot shoot = qtrue; if ( NPCInfo->goalEntity == NPC->enemy ) {//my goal is my enemy and I have a clear shot, no need to chase right now move = qfalse; } } else {//don't shoot yet, keep chasing shoot = qfalse; move = qtrue; } //shoot decision if ( !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) {//try to shoot if ( NPC->enemy ) { if ( shoot ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } } } } //chase decision if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) {//go after him NPCInfo->goalEntity = NPC->enemy; //FIXME: don't need to chase when have a clear shot and in range? if ( !enemyCS && NPC->cantHitEnemyCounter > 60 ) {//haven't been able to shoot enemy for about 6 seconds, need to do something //FIXME: combat points? Just chase? if ( enemyPVS ) {//in my PVS, just pick a combat point //FIXME: implement } else {//just chase him } } //FIXME: in normal behavior, should we use combat Points? Do we care? Is anyone actually going to ever use this AI? } else if ( NPC->cantHitEnemyCounter > 60 ) {//pick a new one NPC_CheckEnemy( qtrue, qfalse, qtrue ); } if ( enemyPVS && enemyLOS )//&& !enemyShotFOV ) {//have a clear LOS to him//, but not looking at him //Find the desired angles vec3_t angles; GetAnglesForDirection( muzzle, enemyHead, angles ); NPCInfo->desiredYaw = AngleNormalize180( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize180( angles[PITCH] ); } */ } if ( UpdateGoal() ) {//have a goal if ( !NPC->enemy && NPC->client->leader && NPCInfo->goalEntity == NPC->client->leader && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) { NPC_BSFollowLeader(); } else { //set angles if ( (NPCInfo->scriptFlags & SCF_FACE_MOVE_DIR) || NPCInfo->goalEntity != NPC->enemy ) {//face direction of movement, NOTE: default behavior when not chasing enemy NPCInfo->combatMove = qfalse; } else {//face goal.. FIXME: what if have a navgoal but want to face enemy while moving? Will this do that? vec3_t dir, angles; NPCInfo->combatMove = qfalse; VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); vectoangles( dir, angles ); NPCInfo->desiredYaw = angles[YAW]; if ( NPCInfo->goalEntity == NPC->enemy ) { NPCInfo->desiredPitch = angles[PITCH]; } } //set movement //override default walk/run behavior //NOTE: redundant, done in NPC_ApplyScriptFlags if ( NPCInfo->scriptFlags & SCF_RUNNING ) { ucmd.buttons &= ~BUTTON_WALKING; } else if ( NPCInfo->scriptFlags & SCF_WALKING ) { ucmd.buttons |= BUTTON_WALKING; } else if ( NPCInfo->goalEntity == NPC->enemy ) { ucmd.buttons &= ~BUTTON_WALKING; } else { ucmd.buttons |= BUTTON_WALKING; } if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) {//being forced to walk //if ( g_crosshairEntNum != NPC->s.number ) if (!NPC_SomeoneLookingAtMe(NPC)) {//don't walk if player isn't aiming at me move = qfalse; } } if ( move ) { //move toward goal NPC_MoveToGoal( qtrue ); } } } else if ( !NPC->enemy && NPC->client->leader ) { NPC_BSFollowLeader(); } //update angles NPC_UpdateAngles( qtrue, qtrue ); }
/* qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) Added: option to do just pitch or just yaw Does not include "aim" in it's calculations FIXME: stop compressing angles into shorts!!!! */ qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) { #if 1 float error; float decay; float targetPitch = 0; float targetYaw = 0; float yawSpeed; qboolean exact = qtrue; // if angle changes are locked; just keep the current angles // aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv if ( !NPC->enemy && ( (level.time < NPCInfo->aimTime) /*|| NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE*/) ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { // we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag // NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; if(doPitch) { targetPitch = NPCInfo->desiredPitch; NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; } if(doYaw) { targetYaw = NPCInfo->desiredYaw; NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } } if ( NPC->s.weapon == WP_EMPLACED_GUN ) { // FIXME: this seems to do nothing, actually... yawSpeed = 20; } else { yawSpeed = NPCInfo->stats.yawSpeed; } if ( NPC->s.weapon == WP_SABER && NPC->client->ps.fd.forcePowersActive&(1<<FP_SPEED) ) { char buf[128]; float tFVal = 0; trap_Cvar_VariableStringBuffer("timescale", buf, sizeof(buf)); tFVal = atof(buf); yawSpeed *= 1.0f/tFVal; } if( doYaw ) { // decay yaw error error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if( fabs(error) > MIN_ANGLE_ERROR ) { if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; } //FIXME: have a pitchSpeed? if( doPitch ) { // decay pitch error error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( fabs(error) > MIN_ANGLE_ERROR ) { if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; if ( exact && trap_ICARUS_TaskIDPending( NPC, TID_ANGLE_FACE ) ) { trap_ICARUS_TaskIDComplete( NPC, TID_ANGLE_FACE ); } return exact; #else float error; float decay; float targetPitch = 0; float targetYaw = 0; float yawSpeed; //float runningMod = NPCInfo->currentSpeed/100.0f; qboolean exact = qtrue; qboolean doSound = qfalse; // if angle changes are locked; just keep the current angles if ( level.time < NPCInfo->aimTime ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { if(doPitch) targetPitch = NPCInfo->desiredPitch; if(doYaw) targetYaw = NPCInfo->desiredYaw; // NPCInfo->aimTime = level.time + 250; if(doPitch) NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; if(doYaw) NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } yawSpeed = NPCInfo->stats.yawSpeed; if(doYaw) { // decay yaw error error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if( fabs(error) > MIN_ANGLE_ERROR ) { /* if(NPC->client->playerTeam == TEAM_BORG&& NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) {//HACK - borg turn more jittery if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec //Snap to if(fabs(error) > 10) { if(random() > 0.6) { doSound = qtrue; } } if ( error < 0.0)//-10.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else if ( error > 0.0)//10.0 ) { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } else*/ if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; } //FIXME: have a pitchSpeed? if(doPitch) { // decay pitch error error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( fabs(error) > MIN_ANGLE_ERROR ) { /* if(NPC->client->playerTeam == TEAM_BORG&& NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) {//HACK - borg turn more jittery if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec //Snap to if(fabs(error) > 10) { if(random() > 0.6) { doSound = qtrue; } } if ( error < 0.0)//-10.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else if ( error > 0.0)//10.0 ) { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } else*/ if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; /* if(doSound) { G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8)))); } */ return exact; #endif }
void NPC_RunBehavior( int team, int bState ) { if (NPC->s.NPC_class == CLASS_VEHICLE && NPC->m_pVehicle) { //vehicles don't do AI! return; } if ( bState == BS_CINEMATIC ) { NPC_BSCinematic(); } else if ( NPC->client->ps.weapon == WP_EMPLACED_GUN ) { NPC_BSEmplaced(); NPC_CheckCharmed(); return; } else if ( NPC->client->ps.weapon == WP_SABER ) {//jedi NPC_BehaviorSet_Jedi( bState ); } else if ( NPC->client->NPC_class == CLASS_WAMPA ) {//wampa NPC_BSWampa_Default(); } else if ( NPC->client->NPC_class == CLASS_RANCOR ) {//rancor NPC_BehaviorSet_Rancor( bState ); } else if ( NPC->client->NPC_class == CLASS_REMOTE ) { NPC_BehaviorSet_Remote( bState ); } else if ( NPC->client->NPC_class == CLASS_SEEKER ) { NPC_BehaviorSet_Seeker( bState ); } else if ( NPC->client->NPC_class == CLASS_BOBAFETT ) {//bounty hunter if ( Boba_Flying( NPC ) ) { NPC_BehaviorSet_Seeker(bState); } else { NPC_BehaviorSet_Jedi( bState ); } //dontSetAim = qtrue; } else if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) {//being forced to march NPC_BSDefault(); } else { switch( team ) { // case NPCTEAM_SCAVENGERS: // case NPCTEAM_IMPERIAL: // case NPCTEAM_KLINGON: // case NPCTEAM_HIROGEN: // case NPCTEAM_MALON: // not sure if TEAM_ENEMY is appropriate here, I think I should be using NPC_class to check for behavior - dmv case NPCTEAM_ENEMY: // special cases for enemy droids switch( NPC->client->NPC_class) { case CLASS_ATST: NPC_BehaviorSet_ATST( bState ); return; case CLASS_PROBE: NPC_BehaviorSet_ImperialProbe(bState); return; case CLASS_REMOTE: NPC_BehaviorSet_Remote( bState ); return; case CLASS_SENTRY: NPC_BehaviorSet_Sentry(bState); return; case CLASS_INTERROGATOR: NPC_BehaviorSet_Interrogator( bState ); return; case CLASS_MINEMONSTER: NPC_BehaviorSet_MineMonster( bState ); return; case CLASS_HOWLER: NPC_BehaviorSet_Howler( bState ); return; case CLASS_MARK1: NPC_BehaviorSet_Mark1( bState ); return; case CLASS_MARK2: NPC_BehaviorSet_Mark2( bState ); return; case CLASS_GALAKMECH: NPC_BSGM_Default(); return; default: break; } if ( NPC->enemy && NPC->s.weapon == WP_NONE && bState != BS_HUNT_AND_KILL && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) {//if in battle and have no weapon, run away, fixme: when in BS_HUNT_AND_KILL, they just stand there if ( bState != BS_FLEE ) { NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); } else { NPC_BSFlee(); } return; } if ( NPC->client->ps.weapon == WP_SABER ) {//special melee exception NPC_BehaviorSet_Default( bState ); return; } if ( NPC->client->ps.weapon == WP_DISRUPTOR && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) {//a sniper NPC_BehaviorSet_Sniper( bState ); return; } if ( NPC->client->ps.weapon == WP_THERMAL || NPC->client->ps.weapon == WP_STUN_BATON )//FIXME: separate AI for melee fighters {//a grenadier NPC_BehaviorSet_Grenadier( bState ); return; } if ( NPC_CheckSurrender() ) { return; } NPC_BehaviorSet_Stormtrooper( bState ); break; case NPCTEAM_NEUTRAL: // special cases for enemy droids if ( NPC->client->NPC_class == CLASS_PROTOCOL || NPC->client->NPC_class == CLASS_UGNAUGHT || NPC->client->NPC_class == CLASS_JAWA) { NPC_BehaviorSet_Default(bState); } else if ( NPC->client->NPC_class == CLASS_VEHICLE ) { // TODO: Add vehicle behaviors here. NPC_UpdateAngles( qtrue, qtrue );//just face our spawn angles for now } else { // Just one of the average droids NPC_BehaviorSet_Droid( bState ); } break; default: if ( NPC->client->NPC_class == CLASS_SEEKER ) { NPC_BehaviorSet_Seeker(bState); } else { if ( NPCInfo->charmedTime > level.time ) { NPC_BehaviorSet_Charmed( bState ); } else { NPC_BehaviorSet_Default( bState ); } NPC_CheckCharmed(); } break; } } }