/* ------------------------- Remote_Ranged ------------------------- */ void Remote_Ranged( qboolean visible, qboolean advance, qboolean retreat ) { if ( TIMER_Done( NPCS.NPC, "attackDelay" ) ) // Attack? { TIMER_Set( NPCS.NPC, "attackDelay", Q_irand( 500, 3000 ) ); Remote_Fire(); } if ( NPCS.NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) { Remote_Hunt( visible, advance, retreat ); } }
/* ------------------------- Mark1_RocketAttack ------------------------- */ void Mark1_RocketAttack( qboolean advance ) { if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? { TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000) ); NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); Mark1_FireRocket(); } else if (advance) { Mark1_Hunt(); } }
/* ------------------------- void Droid_Spin( void ) ------------------------- */ void Droid_Spin( void ) { vec3_t dir = {0,0,1}; R2D2_TurnAnims(); // Head is gone, spin and spark if ( NPC->client->NPC_class == CLASS_R5D2 ) { // No head? if (gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "head" )) { if (TIMER_Done(NPC,"smoke") && !TIMER_Done(NPC,"droidsmoketotal")) { TIMER_Set( NPC, "smoke", 100); G_PlayEffect( "droid_smoke" , NPC->currentOrigin,dir); } if (TIMER_Done(NPC,"droidspark")) { TIMER_Set( NPC, "droidspark", Q_irand(100,500)); G_PlayEffect( "spark", NPC->currentOrigin,dir); } ucmd.forwardmove = Q_irand( -64, 64); if (TIMER_Done(NPC,"roam")) { TIMER_Set( NPC, "roam", Q_irand( 250, 1000 ) ); NPCInfo->desiredYaw = Q_irand( 0, 360 ); // Go in random directions } } else { if (TIMER_Done(NPC,"roam")) { NPCInfo->localState = LSTATE_NONE; } else { NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around } } } else { if (TIMER_Done(NPC,"roam")) { NPCInfo->localState = LSTATE_NONE; } else { NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around } } NPC_UpdateAngles( qtrue, qtrue ); }
//////////////////////////////////////////////////////////////////////////////////////// // Tactics // // This function is called right after Update() // If returns true, Jedi and Seeker AI not used for movement // //////////////////////////////////////////////////////////////////////////////////////// bool Boba_Tactics() { if (!NPC->enemy) { return false; } // Think About Changing Tactics //------------------------------ if (TIMER_Done(NPC, "Boba_TacticsSelect")) { Boba_TacticsSelect(); } // These Tactics Require Seeker & Jedi Movement //---------------------------------------------- if (!NPCInfo->localState || NPCInfo->localState==BTS_RIFLE || NPCInfo->localState==BTS_MISSILE) { return false; } // Flame Thrower - Locked In Place //--------------------------------- if (NPCInfo->localState==BTS_FLAMETHROW) { Boba_DoFlameThrower( NPC ); } // Sniper - Move Around, And Take Shots //-------------------------------------- else if (NPCInfo->localState==BTS_SNIPER) { Boba_DoSniper( NPC ); } // Ambush Wait //------------ else if (NPCInfo->localState==BTS_AMBUSHWAIT) { Boba_DoAmbushWait( NPC ); } NPC_FacePosition( NPC->enemy->currentOrigin, qtrue); NPC_UpdateAngles(qtrue, qtrue); return true; // Do Not Use Normal Jedi Or Seeker Movement }
/* ------------------------- ATST_Ranged ------------------------- */ void ATST_Ranged( qboolean visible, qboolean advance, qboolean altAttack ) { if ( TIMER_Done( NPC, "atkDelay" ) && visible ) // Attack? { TIMER_Set( NPC, "atkDelay", Q_irand( 500, 3000 ) ); ucmd.buttons |= BUTTON_ATTACK; } if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) { ATST_Hunt( visible, advance ); } }
//------------------------------------ void Seeker_Ranged( qboolean visible, qboolean advance ) { if ( NPC->client->NPC_class != CLASS_BOBAFETT ) { //racc - boba fett doesn't run out of ammo. if ( NPC->count > 0 || NPC->count == -1) { //[SeekerItemNpc] //better than using the timer, and if the dynamic music is ever used, then we can apply it to them using the shootTime //meh, just using TIMER_ stuff for now, in case standard npc ai messes it up //if (NPCInfo->shotTime < level.time) // Attack? if ( TIMER_Done( NPC, "attackDelay" )) // Attack? { //NPCInfo->shotTime = level.time + NPC->delay + Q_irand(0, NPC->random); TIMER_Set( NPC, "attackDelay", Q_irand(NPC->genericValue1, NPC->genericValue2)); Seeker_Fire(); if(NPC->count != -1) NPC->count--; } /* if ( TIMER_Done( NPC, "attackDelay" )) // Attack? { TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 )); Seeker_Fire(); NPC->count--; } */ //[/SeekerItemNpc] } else { //[SeekerItemNpc] what is wrong with this? re-enabling... //hmm, somewhere I saw code that handles the final death, but I cant find it anymore... //meh, disabling again // out of ammo, so let it die...give it a push up so it can fall more and blow up on impact //NPC->client->ps.gravity = 900; //NPC->svFlags &= ~SVF_CUSTOM_GRAVITY; //NPC->client->ps.velocity[2] += 16; G_Damage( NPC, NPC, NPC, NULL, NULL, NPC->health/*999*/, 0, MOD_UNKNOWN ); //[/SeekerItemNpc] } } if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) { Seeker_Hunt( visible, advance ); } }
void NPC_SandCreature_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) { if ( TIMER_Done( self, "pain" ) ) { //FIXME: effect and sound //FIXME: shootable during this anim? NPC_SetAnim( self, SETANIM_LEGS, Q_irand(BOTH_ATTACK1,BOTH_ATTACK2), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); G_AddEvent( self, EV_PAIN, Q_irand( 0, 100 ) ); TIMER_Set( self, "pain", self->client->ps.legsAnimTimer + Q_irand( 500, 2000 ) ); float playerDist = Distance( player->currentOrigin, self->currentOrigin ); if ( playerDist < 256 ) { CGCam_Shake( 1.0f*playerDist/128.0f, self->client->ps.legsAnimTimer ); } } self->enemy = self->NPC->goalEntity = NULL; }
void Mark2_BlasterAttack( qboolean advance ) { if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? { if ( NPCInfo->localState == LSTATE_NONE ) // He's up so shoot less often. { TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2000 ) ); } else { TIMER_Set( NPC, "attackDelay", Q_irand( 100, 500 ) ); } Mark2_FireBlaster( advance ); return; } else if ( advance ) { Mark2_Hunt(); } }
/* ------------------------- Remote_Attack ------------------------- */ void Remote_Attack( void ) { float distance; qboolean visible; float idealDist; qboolean advance; qboolean retreat; if ( TIMER_Done(NPC,"spin") ) { TIMER_Set( NPC, "spin", Q_irand( 250, 1500 ) ); NPCInfo->desiredYaw += Q_irand( -200, 200 ); } // Always keep a good height off the ground Remote_MaintainHeight(); // If we don't have an enemy, just idle if ( NPC_CheckEnemyExt(qfalse) == qfalse ) { Remote_Idle(); return; } // Rate our distance to the target, and our visibilty distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); visible = NPC_ClearLOS4( NPC->enemy ); //[CoOp] idealDist = MIN_DISTANCE_SQR+(MIN_DISTANCE_SQR*Q_flrand( 0, 1 )); //idealDist = MIN_DISTANCE_SQR+(MIN_DISTANCE_SQR*flrand( 0, 1 )); //[/CoOp] advance = (qboolean)(distance > idealDist*1.25); retreat = (qboolean)(distance < idealDist*0.75); // If we cannot see our target, move to see it if ( visible == qfalse ) { if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) { Remote_Hunt( visible, advance, retreat ); return; } } Remote_Ranged( visible, advance, retreat ); }
/* ------------------------- R2D2_PartsMove ------------------------- */ void R2D2_PartsMove(void) { // Front 'eye' lense if ( TIMER_Done(NPC,"eyeDelay") ) { NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); NPC->pos1[0]+=Q_irand( -20, 20 ); // Roll NPC->pos1[1]=Q_irand( -20, 20 ); NPC->pos1[2]=Q_irand( -20, 20 ); if (NPC->genericBone1) { gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 ); } TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) ); } }
void Boba_DoSniper( gentity_t *self) { if (TIMER_Done(NPC, "PickNewSniperPoint")) { TIMER_Set(NPC, "PickNewSniperPoint", Q_irand(15000, 25000)); int SniperPoint = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_SNIPE|CP_CLEAR|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1); if (SniperPoint!=-1) { NPC_SetCombatPoint(SniperPoint); NPC_SetMoveGoal( NPC, level.combatPoints[SniperPoint].origin, 20, qtrue, SniperPoint ); } } if (Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<50.0f) { Boba_FireDecide(); } bool IsOnAPath = !!NPC_MoveToGoal(qtrue); // Resolve Blocked Problems //-------------------------- if (NPCInfo->aiFlags&NPCAI_BLOCKED && NPC->client->moveType!=MT_FLYSWIM && ((level.time - NPCInfo->blockedDebounceTime)>3000) ) { Boba_Printf("BLOCKED: Attempting Jump"); if (IsOnAPath) { if (!NPC_TryJump(NPCInfo->blockedTargetPosition)) { Boba_Printf(" Failed"); } } } NPC_FaceEnemy(qtrue); NPC_UpdateAngles( qtrue, qtrue ); }
//------------------------------------ void Seeker_FollowPlayer( void ) { Seeker_MaintainHeight(); float dis = DistanceHorizontalSquared( NPC->currentOrigin, g_entities[0].currentOrigin ); vec3_t pt, dir; if ( dis < MIN_DISTANCE_SQR ) { // generally circle the player closely till we take an enemy..this is our target point pt[0] = g_entities[0].currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56; pt[1] = g_entities[0].currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56; pt[2] = g_entities[0].currentOrigin[2] + 40; VectorSubtract( pt, NPC->currentOrigin, dir ); VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity ); } else { if ( TIMER_Done( NPC, "seekerhiss" )) { TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 ); G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); } // Hey come back! NPCInfo->goalEntity = &g_entities[0]; NPCInfo->goalRadius = 32; NPC_MoveToGoal( qtrue ); NPC->owner = &g_entities[0]; } if ( NPCInfo->enemyCheckDebounceTime < level.time ) { // check twice a second to find a new enemy Seeker_FindEnemy(); NPCInfo->enemyCheckDebounceTime = level.time + 500; } NPC_UpdateAngles( qtrue, qtrue ); }
void Rancor_Patrol( void ) { NPCInfo->localState = LSTATE_CLEAR; //If we have somewhere to go, then do that if ( UpdateGoal() ) { ucmd.buttons &= ~BUTTON_WALKING; NPC_MoveToGoal( qtrue ); } else { if ( TIMER_Done( NPC, "patrolTime" ) ) { TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); } } if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) { Rancor_Idle(); return; } Rancor_CheckRoar( NPC ); TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); }
void R2D2_PartsMove( void ) { // Front 'eye' lense if ( TIMER_Done( NPC, "eyeDelay" ) ) { NPC->pos1.yaw = AngleNormalize360( NPC->pos1.yaw ); NPC->pos1.pitch += Q_irand( -20, 20 ); // Roll NPC->pos1.yaw = Q_irand( -20, 20 ); NPC->pos1.roll = Q_irand( -20, 20 ); /* if (NPC->genericBone1) { gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); } */ NPC_SetBoneAngles( NPC, "f_eye", &NPC->pos1 ); TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) ); } }
//---------------------------------- void MineMonster_Combat( void ) { float distance; qboolean advance; // If we cannot see our target or we have somewhere to go, then do that if ( !NPC_ClearLOS4( NPC->enemy ) || UpdateGoal( )) { NPCInfo->combatMove = qtrue; NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range NPC_MoveToGoal( qtrue ); return; } // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb NPC_FaceEnemy( qtrue ); distance = DistanceHorizontalSquared( &NPC->r.currentOrigin, &NPC->enemy->r.currentOrigin ); advance = (qboolean)( distance > MIN_DISTANCE_SQR ? qtrue : qfalse ); if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack { if ( TIMER_Done2( NPC, "takingPain", qtrue )) { NPCInfo->localState = LSTATE_CLEAR; } else { MineMonster_Move( qtrue ); } } else { MineMonster_Attack(); } }
/* ------------------------- NPC_BSImperialProbe_Patrol ------------------------- */ void ImperialProbe_Patrol( void ) { ImperialProbe_MaintainHeight(); if ( NPC_CheckPlayerTeamStealth() ) { NPC_UpdateAngles( qtrue, qtrue ); return; } //If we have somewhere to go, then do that if (!NPC->enemy) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL ); if ( UpdateGoal() ) { //start loop sound once we move NPC->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); } //randomly talk if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } else // He's got an enemy. Make him angry. { G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/probe/misc/anger1" ); TIMER_Set( NPC, "angerNoise", Q_irand( 2000, 4000 ) ); //NPCInfo->behaviorState = BS_HUNT_AND_KILL; } NPC_UpdateAngles( qtrue, qtrue ); }
//////////////////////////////////////////////////////////////////////////////////////// // Call this function to see if Fett should fire his current weapon //////////////////////////////////////////////////////////////////////////////////////// void Boba_FireDecide( void ) { // Any Reason Not To Shoot? //-------------------------- if (!NPC || // Only NPCs !NPC->client || // Only Clients NPC->client->NPC_class!=CLASS_BOBAFETT || // Only Boba !NPC->enemy || // Only If There Is An Enemy NPC->s.weapon==WP_NONE || // Only If Using A Valid Weapon !TIMER_Done(NPC, "nextAttackDelay") || // Only If Ready To Shoot Again !Boba_CanSeeEnemy(NPC) // Only If Enemy Recently Seen ) { return; } // Now Check Weapon Specific Parameters To See If We Should Shoot Or Not //----------------------------------------------------------------------- switch (NPC->s.weapon) { case WP_ROCKET_LAUNCHER: if (Distance(NPC->currentOrigin, NPC->enemy->currentOrigin)>400.0f) { Boba_Fire(); } break; case WP_DISRUPTOR: // TODO: Add Conditions Here Boba_Fire(); break; case WP_BLASTER: // TODO: Add Conditions Here Boba_Fire(); break; } }
void Mark2_Patrol( void ) { if ( NPC_CheckPlayerTeamStealth() ) { // G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/anger.wav")); NPC_UpdateAngles( qtrue, qtrue ); return; } //If we have somewhere to go, then do that if ( !NPC->enemy ) { if ( UpdateGoal() ) { ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); NPC_UpdateAngles( qtrue, qtrue ); } //randomly talk if ( TIMER_Done( NPC, "patrolNoise" ) ) { // G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } }
void NPC_BSFlee( void ) {//FIXME: keep checking for danger if ( TIMER_Done( NPC, "flee" ) && NPCInfo->tempBehavior == BS_FLEE ) { NPCInfo->tempBehavior = BS_DEFAULT; NPCInfo->squadState = SQUAD_IDLE; //FIXME: should we set some timer to make him stay in this spot for a bit, //so he doesn't just suddenly turn around and come back at the enemy? //OR, just stop running toward goal for last second or so of flee? } if ( NPC_CheckSurrender() ) { return; } gentity_t *goal = NPCInfo->goalEntity; if ( !goal ) { goal = NPCInfo->lastGoalEntity; if ( !goal ) {//???!!! goal = NPCInfo->tempGoal; } } if ( goal ) { qboolean reverseCourse = qtrue; //FIXME: if no weapon, find one and run to pick it up? //Let's try to find a waypoint that gets me away from this thing if ( NPC->waypoint == WAYPOINT_NONE ) { NPC->waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint ); } if ( NPC->waypoint != WAYPOINT_NONE ) { int numEdges = navigator.GetNodeNumEdges( NPC->waypoint ); if ( numEdges != WAYPOINT_NONE ) { vec3_t dangerDir; int nextWp; VectorSubtract( NPCInfo->investigateGoal, NPC->currentOrigin, dangerDir ); VectorNormalize( dangerDir ); for ( int branchNum = 0; branchNum < numEdges; branchNum++ ) { vec3_t branchPos, runDir; nextWp = navigator.GetNodeEdge( NPC->waypoint, branchNum ); navigator.GetNodePosition( nextWp, branchPos ); VectorSubtract( branchPos, NPC->currentOrigin, runDir ); VectorNormalize( runDir ); if ( DotProduct( runDir, dangerDir ) > Q_flrand( 0, 0.5 ) ) {//don't run toward danger continue; } //FIXME: don't want to ping-pong back and forth NPC_SetMoveGoal( NPC, branchPos, 0, qtrue ); reverseCourse = qfalse; break; } } } qboolean moved = NPC_MoveToGoal( qfalse );//qtrue? (do try to move straight to (away from) goal) if ( NPC->s.weapon == WP_NONE && (moved == qfalse || reverseCourse) ) {//No weapon and no escape route... Just cower? Need anim. NPC_Surrender(); NPC_UpdateAngles( qtrue, qtrue ); return; } //If our move failed, then just run straight away from our goal //FIXME: We really shouldn't do this. if ( moved == qfalse ) { vec3_t dir; float dist; if ( reverseCourse ) { VectorSubtract( NPC->currentOrigin, goal->currentOrigin, dir ); } else { VectorSubtract( goal->currentOrigin, NPC->currentOrigin, dir ); } NPCInfo->distToGoal = dist = VectorNormalize( dir ); NPCInfo->desiredYaw = vectoyaw( dir ); NPCInfo->desiredPitch = 0; ucmd.forwardmove = 127; } else if ( reverseCourse ) { //ucmd.forwardmove *= -1; //ucmd.rightmove *= -1; //VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); NPCInfo->desiredYaw *= -1; } //FIXME: can stop after a safe distance? ucmd.upmove = 0; ucmd.buttons &= ~BUTTON_WALKING; //FIXME: what do we do once we've gotten to our goal? } NPC_UpdateAngles( qtrue, qtrue ); NPC_CheckGetNewWeapon(); }
/* ------------------------- Mark2_AttackDecision ------------------------- */ void Mark2_AttackDecision( void ) { float distance; qboolean visible; qboolean advance; NPC_FaceEnemy( qtrue ); distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); visible = NPC_ClearLOS4( NPC->enemy ); advance = (qboolean)(distance > MIN_DISTANCE_SQR); // He's been ordered to get up if (NPCInfo->localState == LSTATE_RISINGUP) { NPC->flags &= ~FL_SHIELDED; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); if ((NPC->client->ps.legsTimer<=0) && NPC->client->ps.torsoAnim == BOTH_RUN1START ) { NPCInfo->localState = LSTATE_NONE; // He's up again. } return; } // If we cannot see our target, move to see it if ((!visible) || (!NPC_FaceEnemy(qtrue))) { // If he's going down or is down, make him get up if ((NPCInfo->localState == LSTATE_DOWN) || (NPCInfo->localState == LSTATE_DROPPINGDOWN)) { if ( TIMER_Done( NPC, "downTime" ) ) // Down being down?? (The delay is so he doesn't pop up and down when the player goes in and out of range) { NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. } } else { Mark2_Hunt(); } return; } // He's down but he could advance if he wants to. if ((advance) && (TIMER_Done( NPC, "downTime" )) && (NPCInfo->localState == LSTATE_DOWN)) { NPCInfo->localState = LSTATE_RISINGUP; NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. } NPC_FaceEnemy( qtrue ); // Dropping down to shoot if (NPCInfo->localState == LSTATE_DROPPINGDOWN) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); TIMER_Set( NPC, "downTime", Q_irand( 3000, 9000) ); if ((NPC->client->ps.legsTimer<=0) && NPC->client->ps.torsoAnim == BOTH_RUN1STOP ) { NPC->flags |= FL_SHIELDED; NPCInfo->localState = LSTATE_DOWN; } } // He's down and shooting else if (NPCInfo->localState == LSTATE_DOWN) { NPC->flags |= FL_SHIELDED;//only damagable by lightsabers and missiles Mark2_BlasterAttack(qfalse); } else if (TIMER_Done( NPC, "runTime" )) // Lowering down to attack. But only if he's done running at you. { NPCInfo->localState = LSTATE_DROPPINGDOWN; } else if (advance) { // We can see enemy so shoot him if timer lets you. Mark2_BlasterAttack(advance); } }
void Pilot_Update(void) { mActivePilotCount = 0; mRegistered.clear(); for (int i=0; i<ENTITYNUM_WORLD; i++) { if (g_entities[i].inuse && g_entities[i].client && g_entities[i].NPC && g_entities[i].NPC->greetEnt && g_entities[i].NPC->greetEnt->owner==(&g_entities[i]) ) { mActivePilotCount++; } if ( g_entities[i].inuse && g_entities[i].client && g_entities[i].m_pVehicle && !g_entities[i].owner && g_entities[i].health>0 && g_entities[i].m_pVehicle->m_pVehicleInfo->type==VH_SPEEDER && !mRegistered.full()) { mRegistered.push_back(&g_entities[i]); } } if (player && player->inuse && TIMER_Done(player, "FlybySoundArchitectureDebounce")) { TIMER_Set(player, "FlybySoundArchitectureDebounce", 300); Vehicle_t* pVeh = G_IsRidingVehicle(player); if (pVeh && (pVeh->m_pVehicleInfo->soundFlyBy || pVeh->m_pVehicleInfo->soundFlyBy2) && //fabsf(pVeh->m_pParentEntity->currentAngles[2])<15.0f && VectorLength(pVeh->m_pParentEntity->client->ps.velocity)>500.0f) { vec3_t projectedPosition; vec3_t projectedDirection; vec3_t projectedRight; vec3_t anglesNoRoll; VectorCopy(pVeh->m_pParentEntity->currentAngles, anglesNoRoll); anglesNoRoll[2] = 0; AngleVectors(anglesNoRoll, projectedDirection, projectedRight, 0); VectorMA(player->currentOrigin, 1.2f, pVeh->m_pParentEntity->client->ps.velocity, projectedPosition); VectorMA(projectedPosition, Q_flrand(-200.0f, 200.0f), projectedRight, projectedPosition); gi.trace(&mPilotViewTrace, player->currentOrigin, 0, 0, projectedPosition, player->s.number, MASK_SHOT, G2_NOCOLLIDE, 0); if ((mPilotViewTrace.allsolid==qfalse) && (mPilotViewTrace.startsolid==qfalse) && (mPilotViewTrace.fraction<0.99f) && (mPilotViewTrace.plane.normal[2]<0.5f) && (DotProduct(projectedDirection, mPilotViewTrace.plane.normal)<-0.5f) ) { // CG_DrawEdge(player->currentOrigin, mPilotViewTrace.endpos, EDGE_IMPACT_POSSIBLE); TIMER_Set(player, "FlybySoundArchitectureDebounce", Q_irand(1000, 2000)); int soundFlyBy = pVeh->m_pVehicleInfo->soundFlyBy; if (pVeh->m_pVehicleInfo->soundFlyBy2 && (!soundFlyBy || !Q_irand(0,1))) { soundFlyBy = pVeh->m_pVehicleInfo->soundFlyBy2; } G_SoundAtSpot(mPilotViewTrace.endpos, soundFlyBy, qtrue); } else { // CG_DrawEdge(player->currentOrigin, mPilotViewTrace.endpos, EDGE_IMPACT_SAFE); } } } }
void Pilot_Steer_Vehicle() { if (!NPC->enemy || !NPC->enemy->client) { return; } // SETUP //======= // Setup Actor Data //------------------ CVec3 ActorPos(NPC->currentOrigin); CVec3 ActorAngles(NPC->currentAngles); ActorAngles[2] = 0; Vehicle_t* ActorVeh = NPCInfo->greetEnt->m_pVehicle; bool ActorInTurbo = (ActorVeh->m_iTurboTime>level.time); float ActorSpeed = (ActorVeh)?(VectorLength(ActorVeh->m_pParentEntity->client->ps.velocity)):(NPC->client->ps.speed); // If my vehicle is spinning out of control, just hold on, we're going to die!!!!! //--------------------------------------------------------------------------------- if (ActorVeh && (ActorVeh->m_ulFlags & VEH_OUTOFCONTROL)) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons &=~BUTTON_ATTACK; ucmd.buttons &=~BUTTON_ALT_ATTACK; return; } CVec3 ActorDirection; AngleVectors(ActorAngles.v, ActorDirection.v, 0, 0); CVec3 ActorFuturePos(ActorPos); ActorFuturePos.ScaleAdd(ActorDirection, FUTURE_PRED_DIST); bool ActorDoTurbo = false; bool ActorAccelerate = false; bool ActorAimAtTarget= true; float ActorYawOffset = 0.0f; // Setup Enemy Data //------------------ CVec3 EnemyPos(NPC->enemy->currentOrigin); CVec3 EnemyAngles(NPC->enemy->currentAngles); EnemyAngles[2] = 0; Vehicle_t* EnemyVeh = (NPC->enemy->s.m_iVehicleNum)?(g_entities[NPC->enemy->s.m_iVehicleNum].m_pVehicle):(0); bool EnemyInTurbo = (EnemyVeh && EnemyVeh->m_iTurboTime>level.time); float EnemySpeed = (EnemyVeh)?(EnemyVeh->m_pParentEntity->client->ps.speed):(NPC->enemy->resultspeed); bool EnemySlideBreak = (EnemyVeh && (EnemyVeh->m_ulFlags&VEH_SLIDEBREAKING || EnemyVeh->m_ulFlags&VEH_STRAFERAM)); bool EnemyDead = (NPC->enemy->health<=0); bool ActorFlank = (NPCInfo->lastAvoidSteerSideDebouncer>level.time && EnemyVeh && EnemySpeed>10.0f); CVec3 EnemyDirection; CVec3 EnemyRight; AngleVectors(EnemyAngles.v, EnemyDirection.v, EnemyRight.v, 0); CVec3 EnemyFuturePos(EnemyPos); EnemyFuturePos.ScaleAdd(EnemyDirection, FUTURE_PRED_DIST); ESide EnemySide = ActorPos.LRTest(EnemyPos, EnemyFuturePos); CVec3 EnemyFlankPos(EnemyFuturePos); EnemyFlankPos.ScaleAdd(EnemyRight, (EnemySide==Side_Right)?(FUTURE_SIDE_DIST):(-FUTURE_SIDE_DIST)); // Debug Draw Enemy Data //----------------------- if (false) { CG_DrawEdge(EnemyPos.v, EnemyFuturePos.v, EDGE_IMPACT_SAFE); CG_DrawEdge(EnemyFuturePos.v, EnemyFlankPos.v, EDGE_IMPACT_SAFE); } // Setup Move And Aim Directions //------------------------------- CVec3 MoveDirection((ActorFlank)?(EnemyFlankPos):(EnemyFuturePos)); MoveDirection -= ActorPos; float MoveDistance = MoveDirection.SafeNorm(); float MoveAccuracy = MoveDirection.Dot(ActorDirection); CVec3 AimDirection(EnemyPos); AimDirection -= ActorPos; float AimDistance = AimDirection.SafeNorm(); float AimAccuracy = AimDirection.Dot(ActorDirection); if (!ActorFlank && TIMER_Done(NPC, "FlankAttackCheck")) { TIMER_Set(NPC, "FlankAttackCheck", Q_irand(1000, 3000)); if (MoveDistance<4000 && Q_irand(0, 1)==0) { NPCInfo->lastAvoidSteerSideDebouncer = level.time + Q_irand(8000, 14000); } } // Fly By Sounds //--------------- if ((ActorVeh->m_pVehicleInfo->soundFlyBy || ActorVeh->m_pVehicleInfo->soundFlyBy2) && EnemyVeh && MoveDistance<800 && ActorSpeed>500.0f && TIMER_Done(NPC, "FlybySoundDebouncer") ) { if (EnemySpeed<100.0f || (ActorDirection.Dot(EnemyDirection)*(MoveDistance/800.0f))<-0.5f) { TIMER_Set(NPC, "FlybySoundDebouncer", 2000); int soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy; if (ActorVeh->m_pVehicleInfo->soundFlyBy2 && (!soundFlyBy || !Q_irand(0,1))) { soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy2; } G_Sound(ActorVeh->m_pParentEntity, soundFlyBy); } } // FLY PAST BEHAVIOR //=================== if (EnemySlideBreak || !TIMER_Done(NPC, "MinHoldDirectionTime")) { if (TIMER_Done(NPC, "MinHoldDirectionTime")) { TIMER_Set(NPC, "MinHoldDirectionTime", 500); // Hold For At Least 500 ms } ActorAccelerate = true; // Go ActorAimAtTarget = false; // Don't Alter Our Aim Direction ucmd.buttons &=~BUTTON_VEH_SPEED; // Let Normal Vehicle Controls Go } // FLANKING BEHAVIOR //=================== else if (ActorFlank) { ActorAccelerate = true; ActorDoTurbo = (MoveDistance>2500 || EnemyInTurbo); ucmd.buttons |= BUTTON_VEH_SPEED; // Tells PMove to use the ps.speed we calculate here, not the one from g_vehicles.c // For Flanking, We Calculate The Speed By Hand, Rather Than Using Pure Accelerate / No Accelerate Functionality //--------------------------------------------------------------------------------------------------------------- NPC->client->ps.speed = ActorVeh->m_pVehicleInfo->speedMax * ((ActorInTurbo)?(1.35f):(1.15f)); // If In Slowing Distance, Scale Down The Speed As We Approach Our Move Target //----------------------------------------------------------------------------- if (MoveDistance<ATTACK_FLANK_SLOWING) { NPC->client->ps.speed *= (MoveDistance/ATTACK_FLANK_SLOWING); NPC->client->ps.speed += EnemySpeed; // Match Enemy Speed //------------------- if (NPC->client->ps.speed<5.0f && EnemySpeed<5.0f) { NPC->client->ps.speed = EnemySpeed; } // Extra Slow Down When Out In Front //----------------------------------- if (MoveAccuracy<0.0f) { NPC->client->ps.speed *= (MoveAccuracy + 1.0f); } MoveDirection *= (MoveDistance/ATTACK_FLANK_SLOWING); EnemyDirection *= 1.0f - (MoveDistance/ATTACK_FLANK_SLOWING); MoveDirection += EnemyDirection; if (TIMER_Done(NPC, "RamCheck")) { TIMER_Set(NPC, "RamCheck", Q_irand(1000, 3000)); if (MoveDistance<RAM_DIST && Q_irand(0, 2)==0) { VEH_StartStrafeRam(ActorVeh, (EnemySide==Side_Left)); } } } } // NORMAL CHASE BEHAVIOR //======================= else { if (!EnemyVeh && AimAccuracy>0.99f && MoveDistance<500 && !EnemyDead) { ActorAccelerate = true; ActorDoTurbo = false; } else { ActorAccelerate = ((MoveDistance>500 && EnemySpeed>20.0f) || MoveDistance>1000); ActorDoTurbo = (MoveDistance>3000 && EnemySpeed>20.0f); } ucmd.buttons &=~BUTTON_VEH_SPEED; } // APPLY RESULTS //======================= // Decide Turbo //-------------- if (ActorDoTurbo || ActorInTurbo) { ucmd.buttons |= BUTTON_ALT_ATTACK; } else { ucmd.buttons &=~BUTTON_ALT_ATTACK; } // Decide Acceleration //--------------------- ucmd.forwardmove = (ActorAccelerate)?(127):(0); // Decide To Shoot //----------------- ucmd.buttons &=~BUTTON_ATTACK; ucmd.rightmove = 0; if (AimDistance<2000 && !EnemyDead) { // If Doing A Ram Attack //----------------------- if (ActorYawOffset!=0) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons &=~BUTTON_ATTACK; } else if (AimAccuracy>ATTACK_FWD) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons |= BUTTON_ATTACK; } else if (AimAccuracy<AIM_SIDE && AimAccuracy>-AIM_SIDE) { if (NPC->client->ps.weapon!=WP_BLASTER) { NPC_ChangeWeapon(WP_BLASTER); } if (AimAccuracy<ATTACK_SIDE && AimAccuracy>-ATTACK_SIDE) { //if (!TIMER_Done(NPC, "RiderAltAttack")) //{ // ucmd.buttons |= BUTTON_ALT_ATTACK; //} //else //{ ucmd.buttons |= BUTTON_ATTACK; /* if (TIMER_Done(NPC, "RiderAltAttackCheck")) { TIMER_Set(NPC, "RiderAltAttackCheck", Q_irand(1000, 3000)); if (Q_irand(0, 2)==0) { TIMER_Set(NPC, "RiderAltAttack", 300); } }*/ //} WeaponThink(true); } ucmd.rightmove = (EnemySide==Side_Left)?( 127):(-127); } else { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } } } else { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } } // Aim At Target //--------------- if (ActorAimAtTarget) { MoveDirection.VecToAng(); NPCInfo->desiredPitch = AngleNormalize360(MoveDirection[PITCH]); NPCInfo->desiredYaw = AngleNormalize360(MoveDirection[YAW] + ActorYawOffset); } NPC_UpdateAngles(qtrue, qtrue); }
/* ------------------------- NPC_BSRancor_Default ------------------------- */ void NPC_BSRancor_Default( void ) { AddSightEvent( NPCS.NPC, NPCS.NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, 50 ); Rancor_Crush(); NPCS.NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); if ( NPCS.NPC->count ) {//holding someone NPCS.NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM; if ( NPCS.NPC->count == 2 ) {//in my mouth NPCS.NPC->client->ps.eFlags2 |= EF2_GENERIC_NPC_FLAG; } } else { NPCS.NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); } if ( TIMER_Done2( NPCS.NPC, "clearGrabbed", qtrue ) ) { Rancor_DropVictim( NPCS.NPC ); } else if ( NPCS.NPC->client->ps.legsAnim == BOTH_PAIN2 && NPCS.NPC->count == 1 && NPCS.NPC->activator ) { if ( !Q_irand( 0, 3 ) ) { Rancor_CheckDropVictim(); } } if ( !TIMER_Done( NPCS.NPC, "rageTime" ) ) {//do nothing but roar first time we see an enemy AddSoundEvent( NPCS.NPC, NPCS.NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, qfalse );//, qfalse ); NPC_FaceEnemy( qtrue ); return; } if ( NPCS.NPC->enemy ) { /* if ( NPC->enemy->client //enemy is a client && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught && NPC->enemy->enemy != NPC//enemy's enemy is not me && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway) {//they should be scared of ME and no-one else G_SetEnemy( NPC->enemy, NPC ); } */ if ( TIMER_Done(NPCS.NPC,"angrynoise") ) { G_Sound( NPCS.NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/misc/anger%d.wav", Q_irand(1, 3))) ); TIMER_Set( NPCS.NPC, "angrynoise", Q_irand( 5000, 10000 ) ); } else { AddSoundEvent( NPCS.NPC, NPCS.NPC->r.currentOrigin, 512, AEL_DANGER_GREAT, qfalse );//, qfalse ); } if ( NPCS.NPC->count == 2 && NPCS.NPC->client->ps.legsAnim == BOTH_ATTACK3 ) {//we're still chewing our enemy up NPC_UpdateAngles( qtrue, qtrue ); return; } //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while if( NPCS.NPC->enemy->client && NPCS.NPC->enemy->client->NPC_class == CLASS_RANCOR ) {//got mad at another Rancor, look for a valid enemy if ( TIMER_Done( NPCS.NPC, "rancorInfight" ) ) { NPC_CheckEnemyExt( qtrue ); } } else if ( !NPCS.NPC->count ) { if ( ValidEnemy( NPCS.NPC->enemy ) == qfalse ) { TIMER_Remove( NPCS.NPC, "lookForNewEnemy" );//make them look again right now if ( !NPCS.NPC->enemy->inuse || level.time - NPCS.NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) {//it's been a while since the enemy died, or enemy is completely gone, get bored with him NPCS.NPC->enemy = NULL; Rancor_Patrol(); NPC_UpdateAngles( qtrue, qtrue ); return; } } if ( TIMER_Done( NPCS.NPC, "lookForNewEnemy" ) ) { gentity_t *newEnemy, *sav_enemy = NPCS.NPC->enemy;//FIXME: what about NPC->lastEnemy? NPCS.NPC->enemy = NULL; newEnemy = NPC_CheckEnemy( NPCS.NPCInfo->confusionTime < level.time, qfalse, qfalse ); NPCS.NPC->enemy = sav_enemy; if ( newEnemy && newEnemy != sav_enemy ) {//picked up a new enemy! NPCS.NPC->lastEnemy = NPCS.NPC->enemy; G_SetEnemy( NPCS.NPC, newEnemy ); //hold this one for at least 5-15 seconds TIMER_Set( NPCS.NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); } else {//look again in 2-5 secs TIMER_Set( NPCS.NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); } } } Rancor_Combat(); } else { if ( TIMER_Done(NPCS.NPC,"idlenoise") ) { G_Sound( NPCS.NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 2))) ); TIMER_Set( NPCS.NPC, "idlenoise", Q_irand( 2000, 4000 ) ); AddSoundEvent( NPCS.NPC, NPCS.NPC->r.currentOrigin, 384, AEL_DANGER, qfalse );//, qfalse ); } if ( NPCS.NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) { Rancor_Patrol(); } else { Rancor_Idle(); } } NPC_UpdateAngles( qtrue, qtrue ); }
/* ------------------------- NPC_Rancor_Pain ------------------------- */ void NPC_Rancor_Pain( gentity_t *self, gentity_t *attacker, int damage ) { qboolean hitByRancor = qfalse; if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_RANCOR ) { hitByRancor = qtrue; } if ( attacker && attacker->inuse && attacker != self->enemy && !(attacker->flags&FL_NOTARGET) ) { if ( !self->count ) { if ( (!attacker->s.number&&!Q_irand(0,3)) || !self->enemy || self->enemy->health == 0 || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR) || (self->NPC && self->NPC->consecutiveBlockedMoves>=10 && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker //FIXME: if can't nav to my enemy, take this guy if I can nav to him G_SetEnemy( self, attacker ); TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); if ( hitByRancor ) {//stay mad at this Rancor for 2-5 secs before looking for attacker enemies TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) ); } } } } if ( (hitByRancor|| (self->count==1&&self->activator&&!Q_irand(0,4)) || Q_irand( 0, 200 ) < damage )//hit by rancor, hit while holding live victim, or took a lot of damage && self->client->ps.legsAnim != BOTH_STAND1TO2 && TIMER_Done( self, "takingPain" ) ) { if ( !Rancor_CheckRoar( self ) ) { if ( self->client->ps.legsAnim != BOTH_MELEE1 && self->client->ps.legsAnim != BOTH_MELEE2 && self->client->ps.legsAnim != BOTH_ATTACK2 ) {//cant interrupt one of the big attack anims /* if ( self->count != 1 || attacker == self->activator || (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) ) */ {//if going to bite our victim, only victim can interrupt that anim if ( self->health > 100 || hitByRancor ) { TIMER_Remove( self, "attacking" ); VectorCopy( self->NPC->lastPathAngles, self->s.angles ); if ( self->count == 1 ) { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } else { NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); } TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) ); if ( self->NPC ) { self->NPC->localState = LSTATE_WAITING; } } } } } //let go /* if ( !Q_irand( 0, 3 ) && self->count == 1 ) { Rancor_DropVictim( self ); } */ } }
//---------------------------------- void Rancor_Combat( void ) { if ( NPCS.NPC->count ) {//holding my enemy if ( TIMER_Done2( NPCS.NPC, "takingPain", qtrue )) { NPCS.NPCInfo->localState = LSTATE_CLEAR; } else { Rancor_Attack( 0, qfalse ); } NPC_UpdateAngles( qtrue, qtrue ); return; } // If we cannot see our target or we have somewhere to go, then do that if ( !NPC_ClearLOS4( NPCS.NPC->enemy ) )//|| UpdateGoal( )) { NPCS.NPCInfo->combatMove = qtrue; NPCS.NPCInfo->goalEntity = NPCS.NPC->enemy; NPCS.NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range if ( !NPC_MoveToGoal( qtrue ) ) {//couldn't go after him? Look for a new one TIMER_Set( NPCS.NPC, "lookForNewEnemy", 0 ); NPCS.NPCInfo->consecutiveBlockedMoves++; } else { NPCS.NPCInfo->consecutiveBlockedMoves = 0; } return; } // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb NPC_FaceEnemy( qtrue ); { float distance; qboolean advance; qboolean doCharge; distance = Distance( NPCS.NPC->r.currentOrigin, NPCS.NPC->enemy->r.currentOrigin ); advance = (qboolean)( distance > (NPCS.NPC->r.maxs[0]+MIN_DISTANCE) ? qtrue : qfalse ); doCharge = qfalse; if ( advance ) {//have to get closer vec3_t yawOnlyAngles; VectorSet( yawOnlyAngles, 0, NPCS.NPC->r.currentAngles[YAW], 0 ); if ( NPCS.NPC->enemy->health > 0 && fabs(distance-250) <= 80 && InFOV3( NPCS.NPC->enemy->r.currentOrigin, NPCS.NPC->r.currentOrigin, yawOnlyAngles, 30, 30 ) ) { if ( !Q_irand( 0, 9 ) ) {//go for the charge doCharge = qtrue; advance = qfalse; } } } if (( advance /*|| NPCInfo->localState == LSTATE_WAITING*/ ) && TIMER_Done( NPCS.NPC, "attacking" )) // waiting monsters can't attack { if ( TIMER_Done2( NPCS.NPC, "takingPain", qtrue )) { NPCS.NPCInfo->localState = LSTATE_CLEAR; } else { Rancor_Move( qtrue ); } } else { Rancor_Attack( distance, doCharge ); } } }
qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member ) { //Validate ents if ( member == NULL ) return qfalse; //Validate clients if ( member->client == NULL ) return qfalse; //Validate NPCs if ( member->NPC == NULL ) return qfalse; //must be aware if ( member->NPC->confusionTime > level.time ) return qfalse; //must be allowed to join groups if ( member->NPC->scriptFlags&SCF_NO_GROUPS ) return qfalse; //Must not be in another group if ( member->NPC->group != NULL && member->NPC->group != group ) {//FIXME: if that group's enemy is mine, why not absorb that group into mine? return qfalse; } //Must be alive if ( member->health <= 0 ) return qfalse; //can't be in an emplaced gun if( member->s.eFlags & EF_LOCKED_TO_WEAPON ) return qfalse; if( member->s.eFlags & EF_HELD_BY_RANCOR ) return qfalse; if( member->s.eFlags & EF_HELD_BY_SAND_CREATURE ) return qfalse; if( member->s.eFlags & EF_HELD_BY_WAMPA ) return qfalse; //Must be on the same team if ( member->client->playerTeam != group->team ) return qfalse; if ( member->client->ps.weapon == WP_SABER ||//!= self->s.weapon ) member->client->ps.weapon == WP_THERMAL || member->client->ps.weapon == WP_DISRUPTOR || member->client->ps.weapon == WP_EMPLACED_GUN || member->client->ps.weapon == WP_BOT_LASER || // Probe droid - Laser blast member->client->ps.weapon == WP_MELEE || member->client->ps.weapon == WP_TURRET || // turret guns member->client->ps.weapon == WP_ATST_MAIN || member->client->ps.weapon == WP_ATST_SIDE || member->client->ps.weapon == WP_TIE_FIGHTER ) {//not really a squad-type guy return qfalse; } if ( member->client->NPC_class == CLASS_ATST || member->client->NPC_class == CLASS_PROBE || member->client->NPC_class == CLASS_SEEKER || member->client->NPC_class == CLASS_REMOTE || member->client->NPC_class == CLASS_SENTRY || member->client->NPC_class == CLASS_INTERROGATOR || member->client->NPC_class == CLASS_MINEMONSTER || member->client->NPC_class == CLASS_HOWLER || member->client->NPC_class == CLASS_RANCOR || member->client->NPC_class == CLASS_MARK1 || member->client->NPC_class == CLASS_MARK2 ) {//these kinds of enemies don't actually use this group AI return qfalse; } //should have same enemy if ( member->enemy != group->enemy ) { if ( member->enemy != NULL ) {//he's fighting someone else, leave him out return qfalse; } if ( !gi.inPVS( member->currentOrigin, group->enemy->currentOrigin ) ) {//not within PVS of the group enemy return qfalse; } } else if ( group->enemy == NULL ) {//if the group is a patrol group, only take those within the room and radius if ( !AI_ValidateNoEnemyGroupMember( group, member ) ) { return qfalse; } } //must be actually in combat mode if ( !TIMER_Done( member, "interrogating" ) ) return qfalse; //FIXME: need to have a route to enemy and/or clear shot? return qtrue; }
/* ------------------------- Mark1_AttackDecision ------------------------- */ void Mark1_AttackDecision( void ) { int blasterTest,rocketTest; float distance; distance_e distRate; qboolean visible; qboolean advance; //randomly talk if ( TIMER_Done(NPC,"patrolNoise") ) { if (TIMER_Done(NPC,"angerNoise")) { TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); } } // Enemy is dead or he has no enemy. if ((NPC->enemy->health<1) || ( NPC_CheckEnemyExt(qfalse) == qfalse )) { NPC->enemy = NULL; return; } // Rate our distance to the target and visibility distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; visible = NPC_ClearLOS4( NPC->enemy ); advance = (qboolean)(distance > MIN_DISTANCE_SQR); // If we cannot see our target, move to see it if ((!visible) || (!NPC_FaceEnemy(qtrue))) { Mark1_Hunt(); return; } // See if the side weapons are there blasterTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "l_arm" ); rocketTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "r_arm" ); // It has both side weapons if (!blasterTest && !rocketTest) { ; // So do nothing. } else if (blasterTest!=-1 &&blasterTest) { distRate = DIST_LONG; } else if (rocketTest!=-1 &&rocketTest) { distRate = DIST_MELEE; } else // It should never get here, but just in case { NPC->health = 0; NPC->client->ps.stats[STAT_HEALTH] = 0; if (NPC->die) { NPC->die(NPC, NPC, NPC, 100, MOD_UNKNOWN); } } // We can see enemy so shoot him if timers let you. NPC_FaceEnemy( qtrue ); if (distRate == DIST_MELEE) { Mark1_BlasterAttack(advance); } else if (distRate == DIST_LONG) { Mark1_RocketAttack(advance); } }
void RT_Flying_Hunt( qboolean visible, qboolean advance ) { float distance, speed; vec3_t forward; if ( NPC->forcePushTime >= level.time ) //|| (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ) {//if being pushed, we don't have control over our movement NPC->delay = 0; return; } NPC_FaceEnemy( qtrue ); // If we're not supposed to stand still, pursue the player if ( NPCInfo->standTime < level.time ) { // Only strafe when we can see the player if ( visible ) { NPC->delay = 0; RT_Flying_Strafe(); return; } } // If we don't want to advance, stop here if ( advance ) { // Only try and navigate if the player is visible if ( visible == qfalse ) { // Move towards our goal NPCInfo->goalEntity = NPC->enemy; NPCInfo->goalRadius = 24; NPC->delay = 0; NPC_MoveToGoal(qtrue); return; } } //else move straight at/away from him VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); forward[2] *= 0.1f; distance = VectorNormalize( forward ); speed = RT_FLYING_FORWARD_BASE_SPEED + RT_FLYING_FORWARD_MULTIPLIER * g_spskill->integer; if ( advance && distance < Q_flrand( 256, 3096 ) ) { NPC->delay = 0; VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); } else if ( distance < Q_flrand( 0, 128 ) ) { if ( NPC->health <= 50 ) {//always back off NPC->delay = 0; } else if ( !TIMER_Done( NPC, "backoffTime" ) ) {//still backing off from end of last delay NPC->delay = 0; } else if ( !NPC->delay ) {//start a new delay NPC->delay = Q_irand( 0, 10+(20*(2-g_spskill->integer)) ); } else {//continue the current delay NPC->delay--; } if ( !NPC->delay ) {//delay done, now back off for a few seconds! TIMER_Set( NPC, "backoffTime", Q_irand( 2000, 5000 ) ); VectorMA( NPC->client->ps.velocity, speed*-2, forward, NPC->client->ps.velocity ); } } else { NPC->delay = 0; } }
void RT_FireDecide( void ) { qboolean enemyLOS = qfalse; qboolean enemyCS = qfalse; qboolean enemyInFOV = qfalse; //qboolean move = qtrue; qboolean faceEnemy = qfalse; qboolean shoot = qfalse; qboolean hitAlly = qfalse; vec3_t impactPos; float enemyDist; if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE && NPC->client->ps.forceJumpZStart && !PM_FlippingAnim( NPC->client->ps.legsAnim ) && !Q_irand( 0, 10 ) ) {//take off RT_FlyStart( NPC ); } if ( !NPC->enemy ) { return; } VectorClear( impactPos ); enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); vec3_t enemyDir, shootDir; VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir ); VectorNormalize( enemyDir ); AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); float dot = DotProduct( enemyDir, shootDir ); if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) {//enemy is in front of me or they're very close and not behind me enemyInFOV = qtrue; } if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 {//enemy within 128 if ( (NPC->client->ps.weapon == WP_FLECHETTE || 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; //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... } } //can we see our target? if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) ) { if ( NPC_ClearLOS( NPC->enemy ) ) { NPCInfo->enemyLastSeenTime = level.time; enemyLOS = qtrue; if ( NPC->client->ps.weapon == WP_NONE ) { enemyCS = qfalse;//not true, but should stop us from firing } else {//can we shoot our target? if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_CONCUSSION && !(NPCInfo->scriptFlags&SCF_ALT_FIRE)) || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128 { enemyCS = qfalse;//not true, but should stop us from firing hitAlly = qtrue;//us! //FIXME: if too close, run away! } else if ( enemyInFOV ) {//if enemy is FOV, go ahead and check for shooting 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 && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), 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 { enemyCS = qfalse;//not true, but should stop us from firing } } } else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) { NPCInfo->enemyLastSeenTime = level.time; faceEnemy = qtrue; //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy } if ( NPC->client->ps.weapon == WP_NONE ) { faceEnemy = qfalse; shoot = qfalse; } else { if ( enemyLOS ) {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? faceEnemy = qtrue; } if ( enemyCS ) { shoot = qtrue; } } if ( !enemyCS ) {//if have a clear shot, always try //See if we should continue to fire on their last position //!TIMER_Done( NPC, "stick" ) || if ( !hitAlly //we're not going to hit an ally && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy { if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds { if ( !Q_irand( 0, 10 ) ) { //Fire on the last known position vec3_t muzzle, dir, angles; qboolean tooClose = qfalse; qboolean tooFar = qfalse; CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); if ( VectorCompare( impactPos, vec3_origin ) ) {//never checked ShotEntity this frame, so must do a trace... trace_t tr; //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; vec3_t forward, end; AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); VectorMA( muzzle, 8192, forward, end ); gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); VectorCopy( tr.endpos, impactPos ); } //see if impact would be too close to me float distThreshold = 16384/*128*128*/;//default switch ( NPC->s.weapon ) { case WP_ROCKET_LAUNCHER: case WP_FLECHETTE: case WP_THERMAL: case WP_TRIP_MINE: case WP_DET_PACK: distThreshold = 65536/*256*256*/; break; case WP_REPEATER: if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) { distThreshold = 65536/*256*256*/; } break; case WP_CONCUSSION: if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) { distThreshold = 65536/*256*256*/; } break; default: break; } float dist = DistanceSquared( impactPos, muzzle ); if ( dist < distThreshold ) {//impact would be too close to me tooClose = qtrue; } else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) {//we've haven't seen them in the last 5 seconds //see if it's too far from where he is distThreshold = 65536/*256*256*/;//default switch ( NPC->s.weapon ) { case WP_ROCKET_LAUNCHER: case WP_FLECHETTE: case WP_THERMAL: case WP_TRIP_MINE: case WP_DET_PACK: distThreshold = 262144/*512*512*/; break; case WP_REPEATER: if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) { distThreshold = 262144/*512*512*/; } break; case WP_CONCUSSION: if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) { distThreshold = 262144/*512*512*/; } break; default: break; } dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); if ( dist > distThreshold ) {//impact would be too far from enemy tooFar = qtrue; } } if ( !tooClose && !tooFar ) {//okay too shoot at last pos VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); VectorNormalize( dir ); vectoangles( dir, angles ); NPCInfo->desiredYaw = angles[YAW]; NPCInfo->desiredPitch = angles[PITCH]; shoot = qtrue; faceEnemy = qfalse; } } } } } //FIXME: don't shoot right away! if ( NPC->client->fireDelay ) { if ( NPC->s.weapon == WP_ROCKET_LAUNCHER || (NPC->s.weapon == WP_CONCUSSION&&!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) ) { if ( !enemyLOS || !enemyCS ) {//cancel it NPC->client->fireDelay = 0; } else {//delay our next attempt TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1000, 3000 ) );//FIXME: base on g_spskill } } } else if ( shoot ) {//try to shoot if it's time if ( TIMER_Done( NPC, "nextAttackDelay" ) ) { if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here { WeaponThink( qtrue ); } //NASTY int altChance = 6;//FIXME: base on g_spskill if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) { if ( (ucmd.buttons&BUTTON_ATTACK) && !Q_irand( 0, altChance ) ) {//every now and then, shoot a homing rocket ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons |= BUTTON_ALT_ATTACK; NPC->client->fireDelay = Q_irand( 1000, 3000 );//FIXME: base on g_spskill } } else if ( NPC->s.weapon == WP_CONCUSSION ) { if ( (ucmd.buttons&BUTTON_ATTACK) && Q_irand( 0, altChance*5 ) ) {//fire the beam shot ucmd.buttons &= ~BUTTON_ATTACK; ucmd.buttons |= BUTTON_ALT_ATTACK; TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1500, 2500 ) );//FIXME: base on g_spskill } else {//fire the rocket-like shot TIMER_Set( NPC, "nextAttackDelay", Q_irand( 3000, 5000 ) );//FIXME: base on g_spskill } } } } } }
void RT_Flying_MaintainHeight( void ) { float dif = 0; // Update our angles regardless NPC_UpdateAngles( qtrue, qtrue ); if ( NPC->forcePushTime > level.time ) {//if being pushed, we don't have control over our movement return; } if ( (NPC->client->ps.pm_flags&PMF_TIME_KNOCKBACK) ) {//don't slow down for a bit if ( NPC->client->ps.pm_time > 0 ) { VectorScale( NPC->client->ps.velocity, 0.9f, NPC->client->ps.velocity ); return; } } /* if ( (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ) { RT_Flying_ApplyFriction( 3.0f ); return; } */ // If we have an enemy, we should try to hover at or a little below enemy eye level if ( NPC->enemy && (!Q3_TaskIDPending( NPC, TID_MOVE_NAV ) || !NPCInfo->goalEntity ) ) { if (TIMER_Done( NPC, "heightChange" )) { TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); float enemyZHeight = NPC->enemy->currentOrigin[2]; if ( NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) ) {//so we don't go up when they force jump up at us enemyZHeight = NPC->enemy->client->ps.forceJumpZStart; } // Find the height difference dif = (enemyZHeight + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; float difFactor = 10.0f; // cap to prevent dramatic height shifts if ( fabs( dif ) > 2*difFactor ) { if ( fabs( dif ) > 20*difFactor ) { dif = ( dif < 0 ? -20*difFactor : 20*difFactor ); } NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; } NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 1.25f ); } else {//don't get too far away from height of enemy... float enemyZHeight = NPC->enemy->currentOrigin[2]; if ( NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) ) {//so we don't go up when they force jump up at us enemyZHeight = NPC->enemy->client->ps.forceJumpZStart; } dif = NPC->currentOrigin[2] - (enemyZHeight+64); float maxHeight = 200; float hDist = DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin ); if ( hDist < 512 ) { maxHeight *= hDist/512; } if ( dif > maxHeight ) { if ( NPC->client->ps.velocity[2] > 0 )//FIXME: or: we can't see him anymore {//slow down if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } } else {//start coming back down NPC->client->ps.velocity[2] -= 4; } } else if ( dif < -200 && NPC->client->ps.velocity[2] < 0 )//we're way below him { if ( NPC->client->ps.velocity[2] < 0 )//FIXME: or: we can't see him anymore {//slow down if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) > -2 ) { NPC->client->ps.velocity[2] = 0; } } } else {//start going back up NPC->client->ps.velocity[2] += 4; } } } } else { gentity_t *goal = NULL; if ( NPCInfo->goalEntity ) // Is there a goal? { goal = NPCInfo->goalEntity; } else { goal = NPCInfo->lastGoalEntity; } if ( goal ) { dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; } else if ( VectorCompare( NPC->pos1, vec3_origin ) ) {//have a starting position as a reference point dif = NPC->pos1[2] - NPC->currentOrigin[2]; } if ( fabs( dif ) > 24 ) { ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); } else { if ( NPC->client->ps.velocity[2] ) { NPC->client->ps.velocity[2] *= VELOCITY_DECAY; if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) { NPC->client->ps.velocity[2] = 0; } } } } // Apply friction RT_Flying_ApplyFriction( 1.0f ); }