qboolean NPC_FindEnemy( qboolean checkAlerts = qfalse ) { //We're ignoring all enemies for now if( NPC->svFlags & SVF_IGNORE_ENEMIES ) { G_ClearEnemy( NPC ); return qfalse; } //we can't pick up any enemies for now if( NPCInfo->confusionTime > level.time ) { G_ClearEnemy( NPC ); return qfalse; } //Don't want a new enemy if ( ( NPC_ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) ) return qtrue; //See if the player is closer than our current enemy if ( NPC->client->NPC_class != CLASS_RANCOR && NPC->client->NPC_class != CLASS_WAMPA && NPC->client->NPC_class != CLASS_SAND_CREATURE && NPC_CheckPlayerDistance() ) {//rancors, wampas & sand creatures don't care if player is closer, they always go with closest return qtrue; } //Otherwise, turn off the flag NPC->svFlags &= ~SVF_LOCKEDENEMY; //If we've gotten here alright, then our target it still valid if ( NPC_ValidEnemy( NPC->enemy ) ) return qtrue; gentity_t *newenemy = NPC_PickEnemyExt( checkAlerts ); //if we found one, take it as the enemy if( NPC_ValidEnemy( newenemy ) ) { G_SetEnemy( NPC, newenemy ); return qtrue; } G_ClearEnemy( NPC ); return qfalse; }
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_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_LostEnemyDecideChase(void) { switch( NPCInfo->behaviorState ) { case BS_HUNT_AND_KILL: //We were chasing him and lost him, so try to find him if ( NPC->enemy == NPCInfo->goalEntity && NPC->enemy->lastWaypoint != WAYPOINT_NONE ) {//Remember his last valid Wp, then check it out //FIXME: Should we only do this if there's no other enemies or we've got LOCKED_ENEMY on? NPC_BSSearchStart( NPC->enemy->lastWaypoint, BS_SEARCH ); } //If he's not our goalEntity, we're running somewhere else, so lose him break; default: break; } G_ClearEnemy( NPC ); }
qboolean NPC_FindEnemy( qboolean checkAlerts = qfalse ) { //We're ignoring all enemies for now if( NPC->svFlags & SVF_IGNORE_ENEMIES ) { G_ClearEnemy( NPC ); return qfalse; } //we can't pick up any enemies for now if( NPCInfo->confusionTime > level.time ) { return qfalse; } //Don't want a new enemy if ( ( ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) ) return qtrue; //See if the player is closer than our current enemy if ( NPC_CheckPlayerDistance() ) { return qtrue; } //Otherwise, turn off the flag NPC->svFlags &= ~SVF_LOCKEDENEMY; //If we've gotten here alright, then our target it still valid if ( NPC_ValidEnemy( NPC->enemy ) ) return qtrue; gentity_t *newenemy = NPC_PickEnemyExt( checkAlerts ); //if we found one, take it as the enemy if( NPC_ValidEnemy( newenemy ) ) { G_SetEnemy( NPC, newenemy ); return qtrue; } return qfalse; }
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_BSFollowLeader (void) { vec3_t vec; float leaderDist; visibility_t leaderVis; int curAnim; if ( !NPC->client->leader ) {//ok, stand guard until we find an enemy if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) { NPCInfo->tempBehavior = BS_DEFAULT; } else { NPCInfo->tempBehavior = BS_STAND_GUARD; NPC_BSStandGuard(); } return; } if ( !NPC->enemy ) {//no enemy, find one NPC_CheckEnemy( NPCInfo->confusionTime<level.time, qfalse );//don't find new enemy if this is tempbehav if ( NPC->enemy ) {//just found one NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); } else { if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) { int eventID = NPC_CheckAlertEvents( qtrue, qtrue ); if ( level.alertEvents[eventID].level >= AEL_SUSPICIOUS && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) { NPCInfo->lastAlertID = level.alertEvents[eventID].ID; if ( !level.alertEvents[eventID].owner || !level.alertEvents[eventID].owner->client || level.alertEvents[eventID].owner->health <= 0 || level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam ) {//not an enemy } else { //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) G_SetEnemy( NPC, level.alertEvents[eventID].owner ); NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); NPCInfo->enemyLastSeenTime = level.time; TIMER_Set( NPC, "attackDelay", Q_irand( 500, 1000 ) ); } } } } if ( !NPC->enemy ) { if ( NPC->client->leader && NPC->client->leader->enemy && NPC->client->leader->enemy != NPC && ( (NPC->client->leader->enemy->client&&NPC->client->leader->enemy->client->playerTeam==NPC->client->enemyTeam) ||(NPC->client->leader->enemy->svFlags&SVF_NONNPC_ENEMY&&NPC->client->leader->enemy->noDamageTeam==NPC->client->enemyTeam) ) && NPC->client->leader->enemy->health > 0 ) { G_SetEnemy( NPC, NPC->client->leader->enemy ); NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); NPCInfo->enemyLastSeenTime = level.time; } } } else { if ( NPC->enemy->health <= 0 || (NPC->enemy->flags&FL_NOTARGET) ) { G_ClearEnemy( NPC ); if ( NPCInfo->enemyCheckDebounceTime > level.time + 1000 ) { NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 2000 ); } } else if ( NPC->client->ps.weapon && NPCInfo->enemyCheckDebounceTime < level.time ) { NPC_CheckEnemy( (NPCInfo->confusionTime<level.time||NPCInfo->tempBehavior!=BS_FOLLOW_LEADER), qfalse );//don't find new enemy if this is tempbehav } } if ( NPC->enemy && NPC->client->ps.weapon ) {//If have an enemy, face him and fire if ( NPC->client->ps.weapon == WP_SABER )//|| NPCInfo->confusionTime>level.time ) {//lightsaber user or charmed enemy if ( NPCInfo->tempBehavior != BS_FOLLOW_LEADER ) {//not already in a temp bState //go after the guy NPCInfo->tempBehavior = BS_HUNT_AND_KILL; NPC_UpdateAngles(qtrue, qtrue); return; } } enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|CHECK_PVS| if ( enemyVisibility > VIS_PVS ) {//face vec3_t enemy_org, muzzle, delta, angleToEnemy; float distanceToEnemy; CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); NPC_AimWiggle( enemy_org ); CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorSubtract( enemy_org, muzzle, delta); vectoangles( delta, angleToEnemy ); distanceToEnemy = VectorNormalize( delta ); NPCInfo->desiredYaw = angleToEnemy[YAW]; NPCInfo->desiredPitch = angleToEnemy[PITCH]; NPC_UpdateFiringAngles( qtrue, qtrue ); if ( enemyVisibility >= VIS_SHOOT ) {//shoot NPC_AimAdjust( 2 ); if ( NPC_GetHFOVPercentage( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.hfov ) > 0.6f && NPC_GetHFOVPercentage( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.vfov ) > 0.5f ) {//actually withing our front cone WeaponThink( qtrue ); } } else { NPC_AimAdjust( 1 ); } //NPC_CheckCanAttack(1.0, qfalse); } else { NPC_AimAdjust( -1 ); } } else {//FIXME: combine with vector calc below vec3_t head, leaderHead, delta, angleToLeader; CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead ); CalcEntitySpot( NPC, SPOT_HEAD, head ); VectorSubtract (leaderHead, head, delta); vectoangles ( delta, angleToLeader ); VectorNormalize(delta); NPC->NPC->desiredYaw = angleToLeader[YAW]; NPC->NPC->desiredPitch = angleToLeader[PITCH]; NPC_UpdateAngles(qtrue, qtrue); } //leader visible? leaderVis = NPC_CheckVisibility( NPC->client->leader, CHECK_PVS|CHECK_360|CHECK_SHOOT );// ent->e_UseFunc = useF_NULL; //Follow leader, stay within visibility and a certain distance, maintain a distance from. curAnim = NPC->client->ps.legsAnim; if ( curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 ) {//Don't move toward leader if we're in a full-body attack anim //FIXME, use IdealDistance to determine if we need to close distance float followDist = 96.0f;//FIXME: If there are enmies, make this larger? float backupdist, walkdist, minrundist; if ( NPCInfo->followDist ) { followDist = NPCInfo->followDist; } backupdist = followDist/2.0f; walkdist = followDist*0.83; minrundist = followDist*1.33; VectorSubtract(NPC->client->leader->currentOrigin, NPC->currentOrigin, vec); leaderDist = VectorLength( vec );//FIXME: make this just nav distance? //never get within their radius horizontally vec[2] = 0; float leaderHDist = VectorLength( vec ); if( leaderHDist > backupdist && (leaderVis != VIS_SHOOT || leaderDist > walkdist) ) {//We should close in? NPCInfo->goalEntity = NPC->client->leader; NPC_SlideMoveToGoal(); if ( leaderVis == VIS_SHOOT && leaderDist < minrundist ) { ucmd.buttons |= BUTTON_WALKING; } } else if ( leaderDist < backupdist ) {//We should back off? NPCInfo->goalEntity = NPC->client->leader; NPC_SlideMoveToGoal(); //reversing direction ucmd.forwardmove = -ucmd.forwardmove; ucmd.rightmove = -ucmd.rightmove; VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); }//otherwise, stay where we are //check for do not enter and stop if there's one there... if ( ucmd.forwardmove || ucmd.rightmove || VectorCompare( vec3_origin, NPC->client->ps.moveDir ) ) { NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ); } } }
static void NPC_CheckAttacker( gentity_t *other, int mod ) { //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 == NPC ) 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; } //we have an enemy, see if he's dead if ( NPC->enemy->health <= 0 ) { G_ClearEnemy( NPC ); G_SetEnemy( NPC, other ); return; } //Don't take the same enemy again if ( other == NPC->enemy ) return; if ( NPC->client->ps.weapon == WP_SABER ) {//I'm a jedi if ( mod == MOD_SABER ) {//I was hit by a saber FIXME: what if this was a thrown saber? //always switch to this enemy if I'm a jedi and hit by another saber G_ClearEnemy( NPC ); G_SetEnemy( NPC, other ); 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 ( Q_flrand(0.0f, 1.0f) > luckThreshold ) { G_ClearEnemy( other ); other->enemy = NPC; } return; } }
/* =============== NPC_ExecuteBState MCG NPC Behavior state thinking =============== */ void NPC_ExecuteBState ( gentity_t *self)//, int msec ) { bState_t bState; NPC_HandleAIFlags(); //FIXME: these next three bits could be a function call, some sort of setup/cleanup func //Lookmode must be reset every think cycle if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) { G_ActivateBehavior( NPC, BSET_DELAYED); NPC->delayScriptTime = 0; } //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func NPCInfo->combatMove = qfalse; //Execute our bState if(NPCInfo->tempBehavior) {//Overrides normal behavior until cleared bState = NPCInfo->tempBehavior; } else { if(!NPCInfo->behaviorState) NPCInfo->behaviorState = NPCInfo->defaultBehavior; bState = NPCInfo->behaviorState; } //Pick the proper bstate for us and run it NPC_RunBehavior( self->client->playerTeam, bState ); // if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1) // { //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse; //NPCInfo->combatPoint = -1; // } //Here we need to see what the scripted stuff told us to do //Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items // ProcessSnapshot(); //Ignore my needs if I'm under script control- this would set needs for items // CheckSelf(); //Back to normal? All decisions made? //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface //NPCPredict(); if ( NPC->enemy ) { if ( !NPC->enemy->inuse ) {//just in case bState doesn't catch this G_ClearEnemy( NPC ); } } if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE ) { NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 ); } else if ( !NPC_CheckLookTarget( NPC ) ) { if ( NPC->enemy ) { NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); } } if ( NPC->enemy ) { if(NPC->enemy->flags & FL_DONT_SHOOT) { ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } else if ( NPC->client->playerTeam != NPCTEAM_ENEMY && NPC->enemy->NPC && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) ) {//don't shoot someone who's surrendering if you're a good guy ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } if(client->ps.weaponstate == WEAPON_IDLE) { client->ps.weaponstate = WEAPON_READY; } } else { if(client->ps.weaponstate == WEAPON_READY) { client->ps.weaponstate = WEAPON_IDLE; } } if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) {//We just shot but aren't still shooting, so hold the gun up for a while if(client->ps.weapon == WP_SABER ) {//One-handed NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); } else if(client->ps.weapon == WP_BRYAR_PISTOL) {//Sniper pose NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } /*//FIXME: What's the proper solution here? else {//heavy weapon NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } */ } else if ( !NPC->enemy )//HACK! { // if(client->ps.weapon != WP_TRICORDER) { if( NPC->s.torsoAnim == TORSO_WEAPONREADY1 || NPC->s.torsoAnim == TORSO_WEAPONREADY3 ) {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); } } } NPC_CheckAttackHold(); NPC_ApplyScriptFlags(); //cliff and wall avoidance NPC_AvoidWallsAndCliffs(); // run the bot through the server like it was a real client //=== Save the ucmd for the second no-think Pmove ============================ ucmd.serverTime = level.time - 50; memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) ); if ( !NPCInfo->attackHoldTime ) { NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);//so we don't fire twice in one think } //============================================================================ NPC_CheckAttackScript(); NPC_KeepCurrentFacing(); if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. ClientThink( NPC->s.number, &ucmd ); } else { NPC_ApplyRoff(); } // end of thinking cleanup NPCInfo->touchedByPlayer = NULL; NPC_CheckPlayerAim(); NPC_CheckAllClear(); /*if( ucmd.forwardmove || ucmd.rightmove ) { int i, la = -1, ta = -1; for(i = 0; i < MAX_ANIMATIONS; i++) { if( NPC->client->ps.legsAnim == i ) { la = i; } if( NPC->client->ps.torsoAnim == i ) { ta = i; } if(la != -1 && ta != -1) { break; } } if(la != -1 && ta != -1) {//FIXME: should never play same frame twice or restart an anim before finishing it Com_Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame); } }*/ }
qboolean NPC_FindEnemy( qboolean checkAlerts ) {//RACC - checks to see if our enemy is still valid. Updates if it is not. gentity_t *newenemy; //[CoOp] SP Code //reenabling the IGNORE_ENEMIES flag if( NPC->NPC->scriptFlags & SCF_IGNORE_ENEMIES ) //We're ignoring all enemies for now //if( NPC->svFlags & SVF_IGNORE_ENEMIES ) //if (0) //rwwFIXMEFIXME: support for flag //[/CoOp] { G_ClearEnemy( NPC ); return qfalse; } //we can't pick up any enemies for now if( NPCInfo->confusionTime > level.time ) { //[CoOp] SP Code G_ClearEnemy( NPC ); //[/CoOp] return qfalse; } //[CoOp] SP Code //Don't want a new enemy if ( ( ValidEnemy( NPC->enemy ) ) && ( NPC->NPC->aiFlags & NPCAI_LOCKEDENEMY ) ) return qtrue; //See if the player is closer than our current enemy if ( NPC->client->NPC_class != CLASS_RANCOR && NPC->client->NPC_class != CLASS_WAMPA && NPC->client->NPC_class != CLASS_SAND_CREATURE && NPC_CheckPlayerDistance() ) {//rancors, wampas & sand creatures don't care if player is closer, they always go with closest return qtrue; } /* This shouldn't be here. SP Code //See if the player is closer than our current enemy if ( NPC_CheckPlayerDistance() ) { return qtrue; }*/ //Otherwise, turn off the flag since if we have a locked enemy at this point, //the enemy is invalid. NPC->NPC->aiFlags &= ~NPCAI_LOCKEDENEMY; // NPC->svFlags &= ~SVF_LOCKEDENEMY; /* Moved up. SP Code //See if the player is closer than our current enemy if ( NPC->client->NPC_class != CLASS_RANCOR && NPC->client->NPC_class != CLASS_WAMPA //&& NPC->client->NPC_class != CLASS_SAND_CREATURE && NPC_CheckPlayerDistance() ) {//rancors, wampas & sand creatures don't care if player is closer, they always go with closest return qtrue; } */ //[/CoOp] //If we've gotten here alright, then our target it still valid if ( NPC_ValidEnemy( NPC->enemy ) ) return qtrue; newenemy = NPC_PickEnemyExt( checkAlerts ); //if we found one, take it as the enemy if( NPC_ValidEnemy( newenemy ) ) { G_SetEnemy( NPC, newenemy ); return qtrue; } //[CoOp] SP Code. Remove enemy since they're not valid at this point. G_ClearEnemy( NPC ); //[/CoOp] return qfalse; }
/* =============== NPC_ExecuteBState MCG NPC Behavior state thinking =============== */ void NPC_ExecuteBState ( gentity_t *self)//, int msec ) { bState_t bState; NPC_HandleAIFlags(); //FIXME: these next three bits could be a function call, some sort of setup/cleanup func //Lookmode must be reset every think cycle if(NPC->aimDebounceTime < level.time) { NPCInfo->lookMode = LT_NONE; } if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) { G_ActivateBehavior( NPC, BSET_DELAYED); NPC->delayScriptTime = 0; } //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func NPCInfo->combatMove = qfalse; //Execute our bState if(NPCInfo->tempBehavior) {//Overrides normal behavior until cleared bState = NPCInfo->tempBehavior; } else { if(!NPCInfo->behaviorState) NPCInfo->behaviorState = NPCInfo->defaultBehavior; bState = NPCInfo->behaviorState; } //Pick the proper bstate for us and run it NPC_RunBehavior( self->client->playerTeam, bState ); //FIXME: Make these a func call if(bState != BS_FORMATION) {//So we know to re-acquire our closest squadpath point self->NPC->lastSquadPoint = -1; // NPCInfo->aiFlags |= NPCAI_OFF_PATH; } if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1) { //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse; //NPCInfo->combatPoint = -1; } //Here we need to see what the scripted stuff told us to do //Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items // ProcessSnapshot(); //Ignore my needs if I'm under script control- this would set needs for items // CheckSelf(); //Back to normal? All decisions made? //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface //NPCPredict(); if ( NPC->enemy ) { if ( !NPC->enemy->inuse ) {//just in case bState doesn't catch this G_ClearEnemy( NPC ); } } if ( !NPC_CheckLookTarget( NPC ) ) { if ( NPC->enemy ) { if ( NPC->client->ps.weapon != WP_IMPERIAL_BLADE && NPC->client->ps.weapon != WP_KLINGON_BLADE ) {//looking right at enemy during melee looks odd NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); } } } if ( NPC->enemy ) { if(NPC->enemy->flags & FL_DONT_SHOOT) { ucmd.buttons &= ~BUTTON_ATTACK; } if(client->ps.weaponstate == WEAPON_IDLE) { client->ps.weaponstate = WEAPON_READY; } } else { if(client->ps.weaponstate == WEAPON_READY) { client->ps.weaponstate = WEAPON_IDLE; } } if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) {//We just shot but aren't still shooting, so hold the gun up for a while if(client->ps.weapon == WP_PHASER ) {//One-handed NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); } else if(client->ps.weapon == WP_COMPRESSION_RIFLE) {//Sniper pose NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); } /*//FIXME: What's the proper solution here? else {//heavy weapon NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } */ } else if (!NPC->enemy && bState != BS_FORMATION)//HACK! { if(client->ps.weapon != WP_TRICORDER) { if((NPC->s.torsoAnim&~ANIM_TOGGLEBIT) == TORSO_WEAPONREADY1 || (NPC->s.torsoAnim&~ANIM_TOGGLEBIT) == TORSO_WEAPONREADY2) {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE1,SETANIM_FLAG_NORMAL); } } } NPC_CheckAttackHold(); NPC_ApplyScriptFlags(); //cliff and wall avoidance NPC_AvoidWallsAndCliffs(); // run the bot through the server like it was a real client //=== Save the ucmd for the second no-think Pmove ============================ ucmd.serverTime = level.time - 50; NPCInfo->last_ucmd = ucmd; if ( !NPCInfo->attackHoldTime ) { NPCInfo->last_ucmd.buttons &= ~BUTTON_ATTACK;//so we don't fire twice in one think } //============================================================================ NPC_CheckAttackScript(); NPC_KeepCurrentFacing(); if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. ClientThink( NPC->s.number, &ucmd ); } else { NPC_ApplyRoff(); } //Had to leave this in, some legacy code must still be using s.angles //Shouldn't interfere with interpolation of angles, should it? VectorCopy( client->ps.viewangles, NPC->currentAngles ); // end of thinking cleanup NPCInfo->touchedByPlayer = NULL; NPC_CheckPlayerAim(); NPC_CheckAllClear(); /*if( ucmd.forwardmove || ucmd.rightmove ) { int i, la = -1, ta = -1; for(i = 0; i < MAX_ANIMATIONS; i++) { if((NPC->client->ps.legsAnim&~ANIM_TOGGLEBIT) == i) { la = i; } if((NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT) == i) { ta = i; } if(la != -1 && ta != -1) { break; } } if(la != -1 && ta != -1) {//FIXME: should never play same frame twice or restart an anim before finishing it gi.Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame); } }*/ }
/* =============== NPC_ExecuteBState MCG NPC Behavior state thinking =============== */ void NPC_ExecuteBState ( gentity_t *self)//, int msec ) { bState_t bState; NPC_HandleAIFlags(); //FIXME: these next three bits could be a function call, some sort of setup/cleanup func //Lookmode must be reset every think cycle if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) { G_ActivateBehavior( NPC, BSET_DELAYED); NPC->delayScriptTime = 0; } //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func NPCInfo->combatMove = qfalse; //Execute our bState if(NPCInfo->tempBehavior) {//Overrides normal behavior until cleared bState = NPCInfo->tempBehavior; } else { if(!NPCInfo->behaviorState) NPCInfo->behaviorState = NPCInfo->defaultBehavior; bState = NPCInfo->behaviorState; } //Pick the proper bstate for us and run it NPC_RunBehavior( self->client->playerTeam, bState ); if ( NPC->enemy ) { if ( !NPC->enemy->inuse ) {//just in case bState doesn't catch this G_ClearEnemy( NPC ); } } if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE ) { NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 ); } else if ( !NPC_CheckLookTarget( NPC ) ) { if ( NPC->enemy ) { NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); } } if ( NPC->enemy ) { if(NPC->enemy->flags & FL_DONT_SHOOT) { ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } else if ( NPC->client->playerTeam != NPCTEAM_ENEMY && NPC->enemy->NPC && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) ) {//don't shoot someone who's surrendering if you're a good guy ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons &= ~BUTTON_ALT_ATTACK; } if(client->ps.weaponstate == WEAPON_IDLE) { client->ps.weaponstate = WEAPON_READY; } } else { if(client->ps.weaponstate == WEAPON_READY) { client->ps.weaponstate = WEAPON_IDLE; } } if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) {//We just shot but aren't still shooting, so hold the gun up for a while if(client->ps.weapon == WP_SABER ) {//One-handed NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); } else if(client->ps.weapon == WP_BRYAR_PISTOL) {//Sniper pose NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); } } else if ( !NPC->enemy )//HACK! { { if( NPC->s.torsoAnim == TORSO_WEAPONREADY1 || NPC->s.torsoAnim == TORSO_WEAPONREADY3 ) {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); } } } NPC_CheckAttackHold(); NPC_ApplyScriptFlags(); //cliff and wall avoidance NPC_AvoidWallsAndCliffs(); // run the bot through the server like it was a real client //=== Save the ucmd for the second no-think Pmove ============================ ucmd.serverTime = level.time - 50; memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) ); if ( !NPCInfo->attackHoldTime ) { NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);//so we don't fire twice in one think } //============================================================================ NPC_CheckAttackScript(); NPC_KeepCurrentFacing(); if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. ClientThink( NPC->s.number, &ucmd ); } else { NPC_ApplyRoff(); } // end of thinking cleanup NPCInfo->touchedByPlayer = NULL; NPC_CheckPlayerAim(); NPC_CheckAllClear(); }