123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
- #include "g_headers.h"
- #include "b_local.h"
- #include "g_nav.h"
- #include "anims.h"
- #include "g_navigator.h"
- extern void CG_DrawAlert( vec3_t origin, float rating );
- extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
- extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
- extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
- extern void NPC_AimAdjust( int change );
- extern qboolean FlyingCreature( gentity_t *ent );
- extern int PM_AnimLength( int index, animNumber_t anim );
- #define MAX_VIEW_DIST 1024
- #define MAX_VIEW_SPEED 250
- #define MAX_LIGHT_INTENSITY 255
- #define MIN_LIGHT_THRESHOLD 0.1
- #define DISTANCE_SCALE 0.25f
- #define DISTANCE_THRESHOLD 0.075f
- #define SPEED_SCALE 0.25f
- #define FOV_SCALE 0.5f
- #define LIGHT_SCALE 0.25f
- #define REALIZE_THRESHOLD 0.6f
- #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 )
- qboolean NPC_CheckPlayerTeamStealth( void );
- static qboolean enemyLOS;
- static qboolean enemyCS;
- static qboolean faceEnemy;
- static qboolean move;
- static qboolean shoot;
- static float enemyDist;
- //Local state enums
- enum
- {
- LSTATE_NONE = 0,
- LSTATE_UNDERFIRE,
- LSTATE_INVESTIGATE,
- };
- /*
- -------------------------
- NPC_Tusken_Precache
- -------------------------
- */
- void NPC_Tusken_Precache( void )
- {
- int i;
- for ( i = 1; i < 5; i ++ )
- {
- G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", i ) );
- }
- }
- void Tusken_ClearTimers( gentity_t *ent )
- {
- TIMER_Set( ent, "chatter", 0 );
- TIMER_Set( ent, "duck", 0 );
- TIMER_Set( ent, "stand", 0 );
- TIMER_Set( ent, "shuffleTime", 0 );
- TIMER_Set( ent, "sleepTime", 0 );
- TIMER_Set( ent, "enemyLastVisible", 0 );
- TIMER_Set( ent, "roamTime", 0 );
- TIMER_Set( ent, "hideTime", 0 );
- TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels
- TIMER_Set( ent, "stick", 0 );
- TIMER_Set( ent, "scoutTime", 0 );
- TIMER_Set( ent, "flee", 0 );
- TIMER_Set( ent, "taunting", 0 );
- }
- void NPC_Tusken_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;
- }
- /*
- -------------------------
- NPC_ST_Pain
- -------------------------
- */
- void NPC_Tusken_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod )
- {
- self->NPC->localState = LSTATE_UNDERFIRE;
- TIMER_Set( self, "duck", -1 );
- TIMER_Set( self, "stand", 2000 );
- NPC_Pain( self, inflictor, other, point, damage, mod );
- if ( !damage && self->health > 0 )
- {//FIXME: better way to know I was pushed
- G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
- }
- }
- /*
- -------------------------
- ST_HoldPosition
- -------------------------
- */
- static void Tusken_HoldPosition( void )
- {
- NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
- NPCInfo->goalEntity = NULL;
- }
- /*
- -------------------------
- ST_Move
- -------------------------
- */
- static qboolean Tusken_Move( void )
- {
- NPCInfo->combatMove = qtrue;//always move straight toward our goal
- qboolean moved = NPC_MoveToGoal( qtrue );
- //If our move failed, then reset
- if ( moved == qfalse )
- {//couldn't get to enemy
- //just hang here
- Tusken_HoldPosition();
- }
- return moved;
- }
- /*
- -------------------------
- NPC_BSTusken_Patrol
- -------------------------
- */
- void NPC_BSTusken_Patrol( void )
- {//FIXME: pick up on bodies of dead buddies?
- if ( NPCInfo->confusionTime < level.time )
- {
- //Look for any enemies
- if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
- {
- if ( NPC_CheckPlayerTeamStealth() )
- {
- //NPC_AngerSound();
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- }
- if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
- {
- //Is there danger nearby
- int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
- if ( NPC_CheckForDanger( alertEvent ) )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- else
- {//check for other alert events
- //There is an event to look at
- if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
- {
- //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
- if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
- {
- if ( level.alertEvents[alertEvent].owner &&
- level.alertEvents[alertEvent].owner->client &&
- level.alertEvents[alertEvent].owner->health >= 0 &&
- level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
- {//an enemy
- G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
- //NPCInfo->enemyLastSeenTime = level.time;
- TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
- }
- }
- else
- {//FIXME: get more suspicious over time?
- //Save the position for movement (if necessary)
- VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
- NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
- if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
- {//suspicious looks longer
- NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
- }
- }
- }
- }
- if ( NPCInfo->investigateDebounceTime > level.time )
- {//FIXME: walk over to it, maybe? Not if not chase enemies
- //NOTE: stops walking or doing anything else below
- vec3_t dir, angles;
- float o_yaw, o_pitch;
-
- VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
- vectoangles( dir, angles );
-
- o_yaw = NPCInfo->desiredYaw;
- o_pitch = NPCInfo->desiredPitch;
- NPCInfo->desiredYaw = angles[YAW];
- NPCInfo->desiredPitch = angles[PITCH];
-
- NPC_UpdateAngles( qtrue, qtrue );
- NPCInfo->desiredYaw = o_yaw;
- NPCInfo->desiredPitch = o_pitch;
- return;
- }
- }
- }
- //If we have somewhere to go, then do that
- if ( UpdateGoal() )
- {
- ucmd.buttons |= BUTTON_WALKING;
- NPC_MoveToGoal( qtrue );
- }
- NPC_UpdateAngles( qtrue, qtrue );
- }
- void NPC_Tusken_Taunt( void )
- {
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TUSKENTAUNT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "taunting", NPC->client->ps.torsoAnimTimer );
- TIMER_Set( NPC, "duck", -1 );
- }
- /*
- -------------------------
- NPC_BSTusken_Attack
- -------------------------
- */
- void NPC_BSTusken_Attack( void )
- {
- // IN PAIN
- //---------
- if ( NPC->painDebounceTime > level.time )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- // IN FLEE
- //---------
- if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- // UPDATE OUR ENEMY
- //------------------
- if (NPC_CheckEnemyExt()==qfalse || !NPC->enemy)
- {
- NPC_BSTusken_Patrol();
- return;
- }
- enemyDist = Distance(NPC->enemy->currentOrigin, NPC->currentOrigin);
- // Is The Current Enemy A Jawa?
- //------------------------------
- if (NPC->enemy->client && NPC->enemy->client->NPC_class==CLASS_JAWA)
- {
- // Make Sure His Enemy Is Me
- //---------------------------
- if (NPC->enemy->enemy!=NPC)
- {
- G_SetEnemy(NPC->enemy, NPC);
- }
- // Should We Forget About Our Current Enemy And Go After The Player?
- //-------------------------------------------------------------------
- if ((player) && // If There Is A Player Pointer
- (player!=NPC->enemy) && // The Player Is Not Currently My Enemy
- (Distance(player->currentOrigin, NPC->currentOrigin)<130.0f) && // The Player Is Close Enough
- (NAV::InSameRegion(NPC, player)) // And In The Same Region
- )
- {
- G_SetEnemy(NPC, player);
- }
- }
- // Update Our Last Seen Time
- //---------------------------
- if (NPC_ClearLOS(NPC->enemy))
- {
- NPCInfo->enemyLastSeenTime = level.time;
- }
- // Check To See If We Are In Attack Range
- //----------------------------------------
- float boundsMin = (NPC->maxs[0]+NPC->enemy->maxs[0]);
- float lungeRange = (boundsMin + 65.0f);
- float strikeRange = (boundsMin + 40.0f);
- bool meleeRange = (enemyDist<lungeRange);
- bool meleeWeapon = (NPC->client->ps.weapon!=WP_TUSKEN_RIFLE);
- bool canSeeEnemy = ((level.time - NPCInfo->enemyLastSeenTime)<3000);
- // Check To Start Taunting
- //-------------------------
- if (canSeeEnemy && !meleeRange && TIMER_Done(NPC, "tuskenTauntCheck"))
- {
- TIMER_Set(NPC, "tuskenTauntCheck", Q_irand(2000, 6000));
- if (!Q_irand(0,3))
- {
- NPC_Tusken_Taunt();
- }
- }
- if (TIMER_Done(NPC, "taunting"))
- {
- // Should I Attack?
- //------------------
- if (meleeRange || (!meleeWeapon && canSeeEnemy))
- {
- if (!(NPCInfo->scriptFlags&SCF_FIRE_WEAPON) && // If This Flag Is On, It Calls Attack From Elsewhere
- !(NPCInfo->scriptFlags&SCF_DONT_FIRE) && // If This Flag Is On, Don't Fire At All
- (TIMER_Done(NPC, "attackDelay"))
- )
- {
- ucmd.buttons &= ~BUTTON_ALT_ATTACK;
- // If Not In Strike Range, Do Lunge, Or If We Don't Have The Staff, Just Shoot Normally
- //--------------------------------------------------------------------------------------
- if (enemyDist > strikeRange)
- {
- ucmd.buttons |= BUTTON_ALT_ATTACK;
- }
- WeaponThink( qtrue );
- TIMER_Set(NPC, "attackDelay", NPCInfo->shotTime-level.time);
- }
- if ( !TIMER_Done( NPC, "duck" ) )
- {
- ucmd.upmove = -127;
- }
- }
- // Or Should I Move?
- //-------------------
- else if (NPCInfo->scriptFlags & SCF_CHASE_ENEMIES)
- {
- NPCInfo->goalEntity = NPC->enemy;
- NPCInfo->goalRadius = lungeRange;
- Tusken_Move();
- }
- }
- // UPDATE ANGLES
- //---------------
- if (canSeeEnemy)
- {
- NPC_FaceEnemy(qtrue);
- }
- NPC_UpdateAngles(qtrue, qtrue);
- }
- extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
- void Tusken_StaffTrace( void )
- {
- if ( !NPC->ghoul2.size()
- || NPC->weaponModel[0] <= 0 )
- {
- return;
- }
- int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[0]], "*weapon");
- if ( boltIndex != -1 )
- {
- int curTime = (cg.time?cg.time:level.time);
- qboolean hit = qfalse;
- int lastHit = ENTITYNUM_NONE;
- for ( int time = curTime-25; time <= curTime+25&&!hit; time += 25 )
- {
- mdxaBone_t boltMatrix;
- vec3_t tip, dir, base, angles={0,NPC->currentAngles[YAW],0};
- vec3_t mins={-2,-2,-2},maxs={2,2,2};
- trace_t trace;
- gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[0],
- boltIndex,
- &boltMatrix, angles, NPC->currentOrigin, time,
- NULL, NPC->s.modelScale );
- gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, base );
- gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
- VectorMA( base, -20, dir, base );
- VectorMA( base, 78, dir, tip );
- #ifndef FINAL_BUILD
- if ( d_saberCombat->integer > 1 )
- {
- G_DebugLine(base, tip, 1000, 0x000000ff, qtrue);
- }
- #endif
- gi.trace( &trace, base, mins, maxs, tip, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
- if ( trace.fraction < 1.0f && trace.entityNum != lastHit )
- {//hit something
- gentity_t *traceEnt = &g_entities[trace.entityNum];
- if ( traceEnt->takedamage
- && (!traceEnt->client || traceEnt == NPC->enemy || traceEnt->client->NPC_class != NPC->client->NPC_class) )
- {//smack
- int dmg = Q_irand( 5, 10 ) * (g_spskill->integer+1);
- //FIXME: debounce?
- G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", Q_irand( 1, 4 ) ) ) );
- G_Damage( traceEnt, NPC, NPC, vec3_origin, trace.endpos, dmg, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- if ( traceEnt->health > 0
- && ( (traceEnt->client&&traceEnt->client->NPC_class==CLASS_JAWA&&!Q_irand(0,1))
- || dmg > 19 ) )//FIXME: base on skill!
- {//do pain on enemy
- G_Knockdown( traceEnt, NPC, dir, 300, qtrue );
- }
- lastHit = trace.entityNum;
- hit = qtrue;
- }
- }
- }
- }
- }
- qboolean G_TuskenAttackAnimDamage( gentity_t *self )
- {
- if (self->client->ps.torsoAnim==BOTH_TUSKENATTACK1 ||
- self->client->ps.torsoAnim==BOTH_TUSKENATTACK2 ||
- self->client->ps.torsoAnim==BOTH_TUSKENATTACK3 ||
- self->client->ps.torsoAnim==BOTH_TUSKENLUNGE1)
- {
- float current = 0.0f;
- int end = 0;
- int start = 0;
- if (!!gi.G2API_GetBoneAnimIndex(&
- self->ghoul2[self->playerModel],
- self->lowerLumbarBone,
- level.time,
- ¤t,
- &start,
- &end,
- NULL,
- NULL,
- NULL))
- {
- float percentComplete = (current-start)/(end-start);
- //gi.Printf("%f\n", percentComplete);
- switch (self->client->ps.torsoAnim)
- {
- case BOTH_TUSKENATTACK1: return (percentComplete>0.3 && percentComplete<0.7);
- case BOTH_TUSKENATTACK2: return (percentComplete>0.3 && percentComplete<0.7);
- case BOTH_TUSKENATTACK3: return (percentComplete>0.1 && percentComplete<0.5);
- case BOTH_TUSKENLUNGE1: return (percentComplete>0.3 && percentComplete<0.5);
- }
- }
- }
- return qfalse;
- }
- void NPC_BSTusken_Default( void )
- {
- if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
- {
- WeaponThink( qtrue );
- }
- if ( G_TuskenAttackAnimDamage( NPC ) )
- {
- Tusken_StaffTrace();
- }
- if( !NPC->enemy )
- {//don't have an enemy, look for one
- NPC_BSTusken_Patrol();
- }
- else//if ( NPC->enemy )
- {//have an enemy
- NPC_BSTusken_Attack();
- }
- }
|