void NPC_Think ( gentity_t *self)//, int msec ) { vector3 oldMoveDir; int i = 0; gentity_t *player; self->nextthink = level.time + FRAMETIME; SetNPCGlobals( self ); memset( &ucmd, 0, sizeof( ucmd ) ); VectorCopy( &self->client->ps.moveDir, &oldMoveDir ); if (self->s.NPC_class != CLASS_VEHICLE) { //YOU ARE BREAKING MY PREDICTION. Bad clear. VectorClear( &self->client->ps.moveDir ); } if(!self || !self->NPC || !self->client) { return; } // dead NPCs have a special think, don't run scripts (for now) //FIXME: this breaks deathscripts if ( self->health <= 0 ) { DeadThink(); if ( NPCInfo->nextBStateThink <= level.time ) { trap->ICARUS_MaintainTaskManager(self->s.number); } VectorCopy(&self->r.currentOrigin, &self->client->ps.origin); return; } // see if NPC ai is frozen if ( d_npcfreeze.value || (NPC->r.svFlags&SVF_ICARUS_FREEZE) ) { NPC_UpdateAngles( qtrue, qtrue ); ClientThink(self->s.number, &ucmd); //VectorCopy(self->s.origin, self->s.origin2 ); VectorCopy(&self->r.currentOrigin, &self->client->ps.origin); return; } self->nextthink = level.time + FRAMETIME/2; while (i < MAX_CLIENTS) { player = &g_entities[i]; if (player->inuse && player->client && player->client->sess.sessionTeam != TEAM_SPECTATOR && !(player->client->ps.pm_flags & PMF_FOLLOW)) { //if ( player->client->ps.viewEntity == self->s.number ) if (0) //rwwFIXMEFIXME: Allow controlling ents {//being controlled by player G_DroidSounds( self ); //FIXME: might want to at least make sounds or something? //NPC_UpdateAngles(qtrue, qtrue); //Which ucmd should we send? Does it matter, since it gets overridden anyway? NPCInfo->last_ucmd.serverTime = level.time - 50; ClientThink( NPC->s.number, &ucmd ); //VectorCopy(self->s.origin, self->s.origin2 ); VectorCopy(&self->r.currentOrigin, &self->client->ps.origin); return; } } i++; } if ( self->client->NPC_class == CLASS_VEHICLE) { if (self->client->ps.m_iVehicleNum) {//we don't think on our own //well, run scripts, though... trap->ICARUS_MaintainTaskManager(self->s.number); return; } else { VectorClear(&self->client->ps.moveDir); self->client->pers.cmd.forwardmove = 0; self->client->pers.cmd.rightmove = 0; self->client->pers.cmd.upmove = 0; self->client->pers.cmd.buttons = 0; memcpy(&self->m_pVehicle->m_ucmd, &self->client->pers.cmd, sizeof(usercmd_t)); } } else if ( NPC->s.m_iVehicleNum ) {//droid in a vehicle? G_DroidSounds( self ); } if ( NPCInfo->nextBStateThink <= level.time && !NPC->s.m_iVehicleNum )//NPCs sitting in Vehicles do NOTHING { #if AI_TIMERS int startTime = GetTime(0); #endif// AI_TIMERS if ( NPC->s.eType != ET_NPC ) {//Something drastic happened in our script return; } if ( NPC->s.weapon == WP_SABER && g_spSkill.integer >= 2 && NPCInfo->rank > RANK_LT_JG ) {//Jedi think faster on hard difficulty, except low-rank (reborn) NPCInfo->nextBStateThink = level.time + FRAMETIME/2; } else {//Maybe even 200 ms? NPCInfo->nextBStateThink = level.time + FRAMETIME; } //nextthink is set before this so something in here can override it if (self->s.NPC_class != CLASS_VEHICLE || !self->m_pVehicle) { //ok, let's not do this at all for vehicles. NPC_ExecuteBState( self ); } #if AI_TIMERS int addTime = GetTime( startTime ); if ( addTime > 50 ) { Com_Printf( S_COLOR_RED"ERROR: NPC number %d, %s %s at %s, weaponnum: %d, using %d of AI time!!!\n", NPC->s.number, NPC->NPC_type, NPC->targetname, vtos(NPC->r.currentOrigin), NPC->s.weapon, addTime ); } AITime += addTime; #endif// AI_TIMERS } else { VectorCopy( &oldMoveDir, &self->client->ps.moveDir ); //or use client->pers.lastCommand? NPCInfo->last_ucmd.serverTime = level.time - 50; if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. //FIXME: firing angles (no aim offset) or regular angles? NPC_UpdateAngles(qtrue, qtrue); memcpy( &ucmd, &NPCInfo->last_ucmd, sizeof( usercmd_t ) ); ClientThink(NPC->s.number, &ucmd); } else { NPC_ApplyRoff(); } //VectorCopy(self->s.origin, self->s.origin2 ); } //must update icarus *every* frame because of certain animation completions in the pmove stuff that can leave a 50ms gap between ICARUS animation commands trap->ICARUS_MaintainTaskManager(self->s.number); VectorCopy(&self->r.currentOrigin, &self->client->ps.origin); }
/* =============== 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); } }*/ }
/* =============== 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_Think Main NPC AI - called once per frame =============== */ void NPC_Think ( gentity_t *self)//, int msec ) { self->nextthink = level.time + FRAMETIME; SetNPCGlobals( self ); memset( &ucmd, 0, sizeof( ucmd ) ); // see if NPC ai is frozen if ( debugNPCFreeze->value ) { NPC_UpdateAngles( qtrue, qtrue ); ClientThink(self->s.number, &ucmd); VectorCopy(self->s.origin, self->s.origin2 ); return; } if(!self || !self->NPC || !self->client) { return; } // dead NPCs have a special think, don't run scripts (for now) //FIXME: this breaks deathscripts if (self->health <= 0) { DeadThink(); if(NPCInfo->nextBStateThink <= level.time) { if( self->taskManager && !stop_icarus ) { self->taskManager->Update( ); } } return; } self->nextthink = level.time + FRAMETIME/2; if(NPCInfo->nextBStateThink <= level.time) { /* if( self->taskManager && !stop_icarus ) { self->taskManager->Update( ); } */ if(NPC->s.eType != ET_PLAYER) {//Something drastic happened in our script return; } NPC_ExecuteBState( self ); //Maybe even 200 ms? NPCInfo->nextBStateThink = level.time + FRAMETIME; if( self->taskManager && !stop_icarus ) { self->taskManager->Update( ); } } else { //or use client->pers.lastCommand? NPCInfo->last_ucmd.serverTime = level.time - 50; if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) {//If we were following a roff, we don't do normal pmoves. //FIXME: firing angles (no aim offset) or regular angles? NPC_UpdateAngles(qtrue, qtrue); ClientThink(NPC->s.number, &NPCInfo->last_ucmd); } else { NPC_ApplyRoff(); } VectorCopy(self->s.origin, self->s.origin2 ); } }
/* =============== 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(); }