123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692 |
- // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
- #include "g_headers.h"
-
- #include "b_local.h"
- // These define the working combat range for these suckers
- #define MIN_DISTANCE 128
- #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
- #define MAX_DISTANCE 1024
- #define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE )
- #define LSTATE_CLEAR 0
- #define LSTATE_WAITING 1
- #define SPF_RANCOR_MUTANT 1
- #define SPF_RANCOR_FASTKILL 2
- extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
- extern cvar_t *g_dismemberment;
- extern cvar_t *g_bobaDebug;
- void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity );
- /*
- -------------------------
- NPC_Rancor_Precache
- -------------------------
- */
- void NPC_Rancor_Precache( void )
- {
- int i;
- for ( i = 1; i < 5; i ++ )
- {
- G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", i) );
- }
- G_SoundIndex( "sound/chars/rancor/swipehit.wav" );
- G_SoundIndex( "sound/chars/rancor/chomp.wav" );
- }
- void NPC_MutantRancor_Precache( void )
- {
- G_SoundIndex( "sound/chars/rancor/breath_start.wav" );
- G_SoundIndex( "sound/chars/rancor/breath_loop.wav" );
- G_EffectIndex( "mrancor/breath" );
- }
- //FIXME: initialize all my timers
- qboolean Rancor_CheckAhead( vec3_t end )
- {
- trace_t trace;
- int clipmask = NPC->clipmask|CONTENTS_BOTCLIP;
- //make sure our goal isn't underground (else the trace will fail)
- vec3_t bottom = {end[0],end[1],end[2]+NPC->mins[2]};
- gi.trace( &trace, end, vec3_origin, vec3_origin, bottom, NPC->s.number, NPC->clipmask );
- if ( trace.fraction < 1.0f )
- {//in the ground, raise it up
- end[2] -= NPC->mins[2]*(1.0f-trace.fraction)-0.125f;
- }
- gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
- if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
- {//started inside do not enter, so ignore them
- clipmask &= ~CONTENTS_BOTCLIP;
- gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
- }
- //Do a simple check
- if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) )
- return qtrue;
- if ( trace.entityNum < ENTITYNUM_WORLD
- && G_EntIsBreakable( trace.entityNum, NPC ) )
- {//breakable brush in our way, break it
- // NPCInfo->blockedEntity = &g_entities[trace.entityNum];
- return qtrue;
- }
- //Aw screw it, always try to go straight at him if we can at all
- if ( trace.fraction >= 0.25f )
- return qtrue;
- //FIXME: if something in the way that's not the world, set blocked ent
- return qfalse;
- }
- /*
- -------------------------
- Rancor_Idle
- -------------------------
- */
- void Rancor_Idle( void )
- {
- NPCInfo->localState = LSTATE_CLEAR;
- //If we have somewhere to go, then do that
- if ( UpdateGoal() )
- {
- ucmd.buttons &= ~BUTTON_WALKING;
- NPC_MoveToGoal( qtrue );
- }
- }
- qboolean Rancor_CheckRoar( gentity_t *self )
- {
- if ( !self->wait )
- {//haven't ever gotten mad yet
- self->wait = 1;//do this only once
- NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( self, "rageTime", self->client->ps.legsAnimTimer );
- return qtrue;
- }
- return qfalse;
- }
- /*
- -------------------------
- Rancor_Patrol
- -------------------------
- */
- 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 );
- }
- if ( NPC_CheckEnemyExt( qtrue ) == qfalse )
- {
- Rancor_Idle();
- return;
- }
- Rancor_CheckRoar( NPC );
- TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
- }
-
- /*
- -------------------------
- Rancor_Move
- -------------------------
- */
- void Rancor_Move( qboolean visible )
- {
- if ( NPCInfo->localState != LSTATE_WAITING )
- {
- NPCInfo->goalEntity = NPC->enemy;
- NPCInfo->goalRadius = NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range
- //FIXME: for some reason, if NPC_MoveToGoal fails, it sets my angles to my lastPathAngles, which I don't want
- float savYaw = NPCInfo->desiredYaw;
- bool savWalking = !!(ucmd.buttons&BUTTON_WALKING);
- if ( !NPC_MoveToGoal( qtrue ) )
- {//can't macro-nav, just head right for him
- //FIXME: if something in the way that's not the world, set blocked ent
- vec3_t dest;
- VectorCopy( NPCInfo->goalEntity->currentOrigin, dest );
- if ( Rancor_CheckAhead( dest ) )
- {//use our temp move straight to goal check
- if (!savWalking)
- {
- ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
- }
- STEER::Activate(NPC);
- STEER::Seek(NPC, dest);
- STEER::AvoidCollisions(NPC);
- STEER::DeActivate(NPC, &ucmd);
- /*
- VectorSubtract( dest, NPC->currentOrigin, NPC->client->ps.moveDir );
- NPC->client->ps.speed = VectorNormalize( NPC->client->ps.moveDir );
- NPCInfo->desiredYaw = vectoyaw( NPC->client->ps.moveDir );
- if ( (ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed > NPCInfo->stats.walkSpeed )
- {
- NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
- }
- else
- {
- if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
- {
- NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
- }
- if ( !(ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed < NPCInfo->stats.runSpeed )
- {
- NPC->client->ps.speed = NPCInfo->stats.runSpeed;
- }
- else if ( NPC->client->ps.speed > NPCInfo->stats.runSpeed )
- {
- NPC->client->ps.speed = NPCInfo->stats.runSpeed;
- }
- }
- */
- }
- else
- {//all else fails, look at him
- // gi.Printf("Fail\n");
- NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = savYaw;
- /* if ( !NPCInfo->blockedEntity )
- {//not already trying to break a breakable somewhere
- if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum < ENTITYNUM_WORLD )
- {//hmm, maybe he's on a breakable brush?
- if ( G_EntIsBreakable( NPC->enemy->client->ps.groundEntityNum, NPC ) )
- {//break it!
- gentity_t *breakable = &g_entities[NPC->enemy->client->ps.groundEntityNum];
- int sanityCheck = 0;
- //FiXME: and if he's on a stack of 3 breakables?
- //FIXME: See if the breakable has a targetname, if so see if the thing targeting it is a breakable, if so, etc...
- while ( sanityCheck < 20 && breakable && breakable->targetname )
- {
- gentity_t *breakableNext = NULL;
- while ( sanityCheck < 20 && (breakableNext = G_Find( breakableNext, FOFS(target), breakable->targetname )) != NULL )
- {
- if ( breakableNext && G_EntIsBreakable( breakableNext->s.number, NPC ) )
- {
- breakable = breakableNext;
- break;
- }
- else
- {
- sanityCheck++;
- }
- }
- if ( !breakableNext )
- {//not targetted by another breakable that we can break
- break;
- }
- else
- {
- sanityCheck++;
- }
- }
- NPCInfo->blockedEntity = breakable;
- }
- }
- }*/
- if ( !NPCInfo->blockedEntity && NPC->enemy && gi.inPVS(NPC->currentOrigin, NPC->enemy->currentOrigin))
- {//nothing to destroy? just go straight at goal dest
- qboolean horzClose = qfalse;
- if (!savWalking)
- {
- ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
- }
- if ( DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin ) < (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) )
- {//close, just look at him
- horzClose = qtrue;
- NPC_FaceEnemy( qtrue );
- }
- else
- {//try to move towards him
- STEER::Activate(NPC);
- STEER::Seek(NPC, dest);
- STEER::AvoidCollisions(NPC);
- STEER::DeActivate(NPC, &ucmd);
- }
- //let him know he should attack at random out of frustration?
- if ( NPCInfo->goalEntity == NPC->enemy )
- {
- if ( TIMER_Done( NPC, "attacking" )
- && TIMER_Done( NPC, "frustrationAttack" ) )
- {
- float enemyDist = Distance( dest, NPC->currentOrigin );
- if ( (!horzClose||!Q_irand(0,5))
- && Q_irand( 0, 1 ) )
- {
- Rancor_Attack( enemyDist, qtrue, qfalse );
- }
- else
- {
- Rancor_Attack( enemyDist, qfalse, qfalse );
- }
- if ( horzClose )
- {
- TIMER_Set( NPC, "frustrationAttack", Q_irand( 2000, 5000 ) );
- }
- else
- {
- TIMER_Set( NPC, "frustrationAttack", Q_irand( 5000, 15000 ) );
- }
- }
- }
- }
- }
- }
- }
- }
- //---------------------------------------------------------
- extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
- extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse );
- extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex );
- extern int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg );
- void Rancor_DropVictim( gentity_t *self )
- {
- //FIXME: if Rancor dies, it should drop its victim.
- //FIXME: if Rancor is removed, it must remove its victim.
- //FIXME: if in BOTH_HOLD_DROP, throw them a little, too?
- if ( self->activator )
- {
- if ( self->activator->client )
- {
- self->activator->client->ps.eFlags &= ~EF_HELD_BY_RANCOR;
- }
- self->activator->activator = NULL;
- if ( self->activator->health <= 0 )
- {
- if ( self->activator->s.number )
- {//never free player
- if ( self->count == 1 )
- {//in my hand, just drop them
- if ( self->activator->client )
- {
- self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0;
- //FIXME: ragdoll?
- }
- }
- else
- {
- G_FreeEntity( self->activator );
- }
- }
- else
- {
- self->activator->s.eFlags |= EF_NODRAW;
- if ( self->activator->client )
- {
- self->activator->client->ps.eFlags |= EF_NODRAW;
- }
- self->activator->clipmask &= ~CONTENTS_BODY;
- }
- }
- else
- {
- if ( self->activator->NPC )
- {//start thinking again
- self->activator->NPC->nextBStateThink = level.time;
- }
- //clear their anim and let them fall
- self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0;
- }
- if ( self->enemy == self->activator )
- {
- self->enemy = NULL;
- }
- if ( self->activator->s.number == 0 )
- {//don't attack the player again for a bit
- TIMER_Set( self, "attackDebounce", Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
- }
- self->activator = NULL;
- }
- self->count = 0;//drop him
- }
- void Rancor_Swing( int boltIndex, qboolean tryGrab )
- {
- gentity_t *radiusEnts[ 128 ];
- int numEnts;
- const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?200:88;
- const float radiusSquared = (radius*radius);
- int i;
- vec3_t boltOrg;
- vec3_t originUp;
- VectorCopy(NPC->currentOrigin, originUp);
- originUp[2] += (NPC->maxs[2]*0.75f);
- numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, boltIndex, boltOrg );
- //if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
- {//attacking a breakable brush
- //HMM... maybe always do this?
- //if boltOrg inside a breakable brush, damage it
- trace_t trace;
- gi.trace( &trace, NPC->pos3, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
- #ifndef FINAL_BUILD
- if ( g_bobaDebug->integer > 0 )
- {
- G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue);
- }
- #endif
- //remember pos3 for the trace from last hand pos to current hand pos next time
- VectorCopy( boltOrg, NPC->pos3 );
- //FIXME: also do a trace TO the bolt from where we are...?
- if ( G_EntIsBreakable( trace.entityNum, NPC ) )
- {
- G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 100, 0, MOD_MELEE );
- }
- else
- {//fuck, do an actual line trace, I guess...
- gi.trace( &trace, originUp, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
- #ifndef FINAL_BUILD
- if ( g_bobaDebug->integer > 0 )
- {
- G_DebugLine(originUp, boltOrg, 1000, 0x000000ff, qtrue);
- }
- #endif
- if ( G_EntIsBreakable( trace.entityNum, NPC ) )
- {
- G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
- }
- }
- }
- for ( i = 0; i < numEnts; i++ )
- {
- if ( !radiusEnts[i]->inuse )
- {
- continue;
- }
-
- if ( radiusEnts[i] == NPC )
- {//Skip the rancor ent
- continue;
- }
-
- if ( radiusEnts[i]->client == NULL )
- {//must be a client
- continue;
- }
- if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR)
- ||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
- {//can't be one already being held
- continue;
- }
- if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
- {//not if invisible
- continue;
- }
- /*
- if ( !radiusEnts[i]->contents )
- {//not if non-solid
- continue;
- }
- */
-
- if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared )
- {
- if ( !gi.inPVS( radiusEnts[i]->currentOrigin, NPC->currentOrigin ) )
- {//don't grab anything that's in another PVS
- continue;
- }
- /*
- qboolean skipGrab = qfalse;
- if ( tryGrab//want to grab
- && (NPC->spawnflags&SPF_RANCOR_FASTKILL)//mutant rancor
- && radiusEnts[i]->s.number >= MAX_CLIENTS //not the player
- && Q_irand( 0, 1 ) )//50% chance
- {//don't grab them, just smack them away
- skipGrab = qtrue;
- }
- */
- if ( tryGrab
- //&& !skipGrab
- && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth!
- && radiusEnts[i]->client->NPC_class != CLASS_RANCOR
- && radiusEnts[i]->client->NPC_class != CLASS_GALAKMECH
- && radiusEnts[i]->client->NPC_class != CLASS_ATST
- && radiusEnts[i]->client->NPC_class != CLASS_GONK
- && radiusEnts[i]->client->NPC_class != CLASS_R2D2
- && radiusEnts[i]->client->NPC_class != CLASS_R5D2
- && radiusEnts[i]->client->NPC_class != CLASS_MARK1
- && radiusEnts[i]->client->NPC_class != CLASS_MARK2
- && radiusEnts[i]->client->NPC_class != CLASS_MOUSE
- && radiusEnts[i]->client->NPC_class != CLASS_PROBE
- && radiusEnts[i]->client->NPC_class != CLASS_SEEKER
- && radiusEnts[i]->client->NPC_class != CLASS_REMOTE
- && radiusEnts[i]->client->NPC_class != CLASS_SENTRY
- && radiusEnts[i]->client->NPC_class != CLASS_INTERROGATOR
- && radiusEnts[i]->client->NPC_class != CLASS_VEHICLE )
- {//grab
- if ( NPC->count == 2 )
- {//have one in my mouth, remove him
- TIMER_Remove( NPC, "clearGrabbed" );
- Rancor_DropVictim( NPC );
- }
- NPC->enemy = radiusEnts[i];//make him my new best friend
- radiusEnts[i]->client->ps.eFlags |= EF_HELD_BY_RANCOR;
- //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something
- radiusEnts[i]->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it.
- NPC->activator = radiusEnts[i];//remember him
- NPC->count = 1;//in my hand
- //wait to attack
- TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand(500, 2500) );
- if ( radiusEnts[i]->health > 0 )
- {//do pain on enemy
- GEntity_PainFunc( radiusEnts[i], NPC, NPC, radiusEnts[i]->currentOrigin, 0, MOD_CRUSH );
- }
- else if ( radiusEnts[i]->client )
- {
- NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- }
- }
- else
- {//smack
- G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
- //actually push the enemy
- vec3_t pushDir;
- /*
- //VectorSubtract( radiusEnts[i]->currentOrigin, boltOrg, pushDir );
- VectorSubtract( radiusEnts[i]->currentOrigin, NPC->currentOrigin, pushDir );
- pushDir[2] = Q_flrand( 100, 200 );
- VectorNormalize( pushDir );
- */
- if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
- && radiusEnts[i]->s.number >= MAX_CLIENTS )
- {
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
- }
- vec3_t angs;
- VectorCopy( NPC->client->ps.viewangles, angs );
- angs[YAW] += Q_flrand( 25, 50 );
- angs[PITCH] = Q_flrand( -25, -15 );
- AngleVectors( angs, pushDir, NULL, NULL );
- if ( radiusEnts[i]->client->NPC_class != CLASS_RANCOR
- && radiusEnts[i]->client->NPC_class != CLASS_ATST
- && !(radiusEnts[i]->flags&FL_NO_KNOCKBACK) )
- {
- G_Throw( radiusEnts[i], pushDir, 250 );
- if ( radiusEnts[i]->health > 0 )
- {//do pain on enemy
- G_Knockdown( radiusEnts[i], NPC, pushDir, 100, qtrue );
- }
- }
- }
- }
- }
- }
- void Rancor_Smash( void )
- {
- gentity_t *radiusEnts[ 128 ];
- int numEnts;
- const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?256:128;
- const float halfRadSquared = ((radius/2)*(radius/2));
- const float radiusSquared = (radius*radius);
- float distSq;
- int i;
- vec3_t boltOrg;
- AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER, qfalse, qtrue );
- numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->handLBolt, boltOrg );
- //if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
- {//attacking a breakable brush
- //HMM... maybe always do this?
- //if boltOrg inside a breakable brush, damage it
- trace_t trace;
- gi.trace( &trace, boltOrg, vec3_origin, vec3_origin, NPC->pos3, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
- #ifndef FINAL_BUILD
- if ( g_bobaDebug->integer > 0 )
- {
- G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue);
- }
- #endif
- //remember pos3 for the trace from last hand pos to current hand pos next time
- VectorCopy( boltOrg, NPC->pos3 );
- //FIXME: also do a trace TO the bolt from where we are...?
- if ( G_EntIsBreakable( trace.entityNum, NPC ) )
- {
- G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
- }
- else
- {//fuck, do an actual line trace, I guess...
- gi.trace( &trace, NPC->currentOrigin, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
- #ifndef FINAL_BUILD
- if ( g_bobaDebug->integer > 0 )
- {
- G_DebugLine(NPC->currentOrigin, boltOrg, 1000, 0x000000ff, qtrue);
- }
- #endif
- if ( G_EntIsBreakable( trace.entityNum, NPC ) )
- {
- G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
- }
- }
- }
- for ( i = 0; i < numEnts; i++ )
- {
- if ( !radiusEnts[i]->inuse )
- {
- continue;
- }
-
- if ( radiusEnts[i] == NPC )
- {//Skip the rancor ent
- continue;
- }
-
- if ( radiusEnts[i]->client == NULL )
- {//must be a client
- if ( G_EntIsBreakable( radiusEnts[i]->s.number, NPC ) )
- {//damage breakables within range, but not as much
- if ( !Q_irand( 0, 1 ) )
- {
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, 100, 0, MOD_MELEE );
- }
- }
- continue;
- }
- if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
- {//can't be one being held
- continue;
- }
- if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
- {//not if invisible
- continue;
- }
-
- distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg );
- if ( distSq <= radiusSquared )
- {
- if ( distSq < halfRadSquared )
- {//close enough to do damage, too
- G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
- if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
- && radiusEnts[i]->s.number >= MAX_CLIENTS )
- {
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
- }
- else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better
- {//more damage
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 40, 55 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- }
- else
- {
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 10, 25 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- }
- }
- if ( radiusEnts[i]->health > 0
- && radiusEnts[i]->client
- && radiusEnts[i]->client->NPC_class != CLASS_RANCOR
- && radiusEnts[i]->client->NPC_class != CLASS_ATST )
- {
- if ( distSq < halfRadSquared
- || radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {//within range of my fist or withing ground-shaking range and not in the air
- if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
- {
- G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qtrue );
- }
- else
- {
- G_Knockdown( radiusEnts[i], NPC, vec3_origin, Q_irand( 200, 350), qtrue );
- }
- }
- }
- }
- }
- }
- void Rancor_Bite( void )
- {
- gentity_t *radiusEnts[ 128 ];
- int numEnts;
- const float radius = 100;
- const float radiusSquared = (radius*radius);
- int i;
- vec3_t boltOrg;
- numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->gutBolt, boltOrg );
- for ( i = 0; i < numEnts; i++ )
- {
- if ( !radiusEnts[i]->inuse )
- {
- continue;
- }
-
- if ( radiusEnts[i] == NPC )
- {//Skip the rancor ent
- continue;
- }
-
- if ( radiusEnts[i]->client == NULL )
- {//must be a client
- continue;
- }
- if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
- {//can't be one already being held
- continue;
- }
- if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
- {//not if invisible
- continue;
- }
-
- if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared )
- {
- if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
- && radiusEnts[i]->s.number >= MAX_CLIENTS )
- {
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
- }
- else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
- {//more damage
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 35, 50 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- }
- else
- {
- G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 15, 30 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- }
- if ( radiusEnts[i]->health <= 0 && radiusEnts[i]->client )
- {//killed them, chance of dismembering
- if ( !Q_irand( 0, 1 ) )
- {//bite something off
- int hitLoc = HL_WAIST;
- if ( g_dismemberment->integer < 11381138 )
- {
- hitLoc = Q_irand( HL_WAIST, HL_HAND_LT );
- }
- else
- {
- hitLoc = Q_irand( HL_WAIST, HL_HEAD );
- }
- if ( hitLoc == HL_HEAD )
- {
- NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- }
- else if ( hitLoc == HL_WAIST )
- {
- NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- }
- radiusEnts[i]->client->dismembered = false;
- //FIXME: the limb should just disappear, cuz I ate it
- G_DoDismemberment( radiusEnts[i], radiusEnts[i]->currentOrigin, MOD_SABER, 1000, hitLoc, qtrue );
- }
- }
- G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
- }
- }
- }
- //------------------------------
- extern gentity_t *TossClientItems( gentity_t *self );
- void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity )
- {
- if ( !TIMER_Exists( NPC, "attacking" )
- && TIMER_Done( NPC, "attackDebounce" ) )
- {
- if ( NPC->count == 2 && NPC->activator )
- {
- }
- else if ( NPC->count == 1 && NPC->activator )
- {//holding enemy
- if ( (!(NPC->spawnflags&SPF_RANCOR_FASTKILL) ||NPC->activator->s.number<MAX_CLIENTS)
- && NPC->activator->health > 0
- && Q_irand( 0, 1 ) )
- {//quick bite
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "attack_dmg", 450 );
- }
- else
- {//full eat
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "attack_dmg", 900 );
- //Make victim scream in fright
- if ( NPC->activator->health > 0 && NPC->activator->client )
- {
- G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 );
- NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- if ( NPC->activator->NPC )
- {//no more thinking for you
- TossClientItems( NPC );
- NPC->activator->NPC->nextBStateThink = Q3_INFINITE;
- }
- }
- }
- }
- else if ( NPC->enemy->health > 0 && doCharge )
- {//charge
- if ( !Q_irand( 0, 3 ) )
- {
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK5, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "attack_dmg", 1250 );
- if ( NPC->enemy && NPC->enemy->s.number == 0 )
- {//don't attack the player again for a bit
- TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
- }
- }
- else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
- {//breath attack
- int breathAnim = BOTH_ATTACK4;
- gentity_t *checkEnt = NULL;
- vec3_t center;
- if ( NPC->enemy && NPC->enemy->inuse )
- {
- checkEnt = NPC->enemy;
- VectorCopy( NPC->enemy->currentOrigin, center );
- }
- else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse )
- {
- checkEnt = NPCInfo->blockedEntity;
- //if it has an origin brush, use it...
- if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
- {//no origin brush, calc center
- VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
- VectorScale( center, 0.5f, center );
- }
- else
- {//use origin brush as center
- VectorCopy( NPCInfo->blockedEntity->s.origin, center );
- }
- }
- if ( checkEnt )
- {
- float zHeightRelative = center[2]-NPC->currentOrigin[2];
- if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) )
- {
- breathAnim = BOTH_ATTACK7;
- }
- else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) )
- {
- breathAnim = BOTH_ATTACK6;
- }
- }
- NPC_SetAnim( NPC, SETANIM_BOTH, breathAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- //start effect here
- G_PlayEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number, NPC->currentOrigin, (NPC->client->ps.legsAnimTimer-500), qfalse );
- TIMER_Set( NPC, "breathAttack", NPC->client->ps.legsAnimTimer-500 );
- G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/chars/rancor/breath_start.wav" );
- NPC->s.loopSound = G_SoundIndex( "sound/chars/rancor/breath_loop.wav" );
- if ( NPC->enemy && NPC->enemy->s.number == 0 )
- {//don't attack the player again for a bit
- TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
- }
- }
- else
- {
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "attack_dmg", 1250 );
- vec3_t fwd, yawAng ={0, NPC->client->ps.viewangles[YAW], 0};
- AngleVectors( yawAng, fwd, NULL, NULL );
- VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity );
- NPC->client->ps.velocity[2] = 150;
- NPC->client->ps.groundEntityNum = ENTITYNUM_NONE;
- if ( NPC->enemy && NPC->enemy->s.number == 0 )
- {//don't attack the player again for a bit
- TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
- }
- }
- }
- else if ( !Q_irand(0, 1)
- /*&& (NPC->spawnflags&SPF_RANCOR_MUTANT)*/ )
- {//mutant rancor can smash
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "attack_dmg", 900 );
- //init pos3 for the trace from last hand pos to current hand pos
- VectorCopy( NPC->currentOrigin, NPC->pos3 );
- }
- else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
- || distance >= NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])-64.0f )
- {//try to grab
- int grabAnim = BOTH_ATTACK2;
- gentity_t *checkEnt = NULL;
- vec3_t center;
- if ( (!aimAtBlockedEntity||!NPCInfo->blockedEntity) && NPC->enemy && NPC->enemy->inuse )
- {
- checkEnt = NPC->enemy;
- VectorCopy( NPC->enemy->currentOrigin, center );
- }
- else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse )
- {
- checkEnt = NPCInfo->blockedEntity;
- //if it has an origin brush, use it...
- if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
- {//no origin brush, calc center
- VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
- VectorScale( center, 0.5f, center );
- }
- else
- {//use origin brush as center
- VectorCopy( NPCInfo->blockedEntity->s.origin, center );
- }
- }
- if ( checkEnt )
- {
- float zHeightRelative = center[2]-NPC->currentOrigin[2];
- if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) )
- {
- grabAnim = BOTH_ATTACK11;
- }
- else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) )
- {
- grabAnim = BOTH_ATTACK10;
- }
- }
- NPC_SetAnim( NPC, SETANIM_BOTH, grabAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "attack_dmg", 800 );
- if ( NPC->enemy && NPC->enemy->s.number == 0 )
- {//don't attack the player again for a bit
- TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
- }
- //init pos3 for the trace from last hand pos to current hand pos
- VectorCopy( NPC->currentOrigin, NPC->pos3 );
- }
- else
- {
- //FIXME: back up?
- ucmd.forwardmove = -64;
- //FIXME: check for walls/ledges?
- return;
- }
- TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + random() * 200 );
- }
- // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks
- float playerDist;
- if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) )
- {
- switch ( NPC->client->ps.legsAnim )
- {
- case BOTH_MELEE1:
- Rancor_Smash();
- playerDist = NPC_EntRangeFromBolt( player, NPC->handLBolt );
- if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
- {
- if ( playerDist < 512 )
- {
- CGCam_Shake( 1.0f*playerDist/256, 1000 );
- }
- }
- else
- {
- if ( playerDist < 256 )
- {
- CGCam_Shake( 1.0f*playerDist/128.0f, 1000 );
- }
- }
- break;
- case BOTH_MELEE2:
- Rancor_Bite();
- TIMER_Set( NPC, "attack_dmg2", 450 );
- break;
- case BOTH_ATTACK1:
- if ( NPC->count == 1 && NPC->activator )
- {
- if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
- && NPC->activator->s.number >= MAX_CLIENTS )
- {
- G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->activator->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
- }
- else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better
- {//more damage
- G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 55, 70 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- }
- else
- {
- G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
- }
- if ( NPC->activator->health <= 0 )
- {//killed him
- if ( g_dismemberment->integer >= 11381138 )
- {//make it look like we bit his head off
- NPC->activator->client->dismembered = false;
- G_DoDismemberment( NPC->activator, NPC->activator->currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue );
- }
- NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- }
- G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
- }
- break;
- case BOTH_ATTACK2:
- case BOTH_ATTACK10:
- case BOTH_ATTACK11:
- //try to grab
- Rancor_Swing( NPC->handRBolt, qtrue );
- break;
- case BOTH_ATTACK3:
- if ( NPC->count == 1 && NPC->activator )
- {
- //cut in half
- if ( NPC->activator->client )
- {
- NPC->activator->client->dismembered = false;
- G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
- }
- //KILL
- G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->enemy->health+1000, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE, HL_NONE );
- if ( NPC->activator->client )
- {
- NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- }
- TIMER_Set( NPC, "attack_dmg2", 1350 );
- G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
- G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
- }
- break;
- }
- }
- else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) )
- {
- switch ( NPC->client->ps.legsAnim )
- {
- case BOTH_MELEE1:
- break;
- case BOTH_MELEE2:
- Rancor_Bite();
- break;
- case BOTH_ATTACK1:
- break;
- case BOTH_ATTACK2:
- break;
- case BOTH_ATTACK3:
- if ( NPC->count == 1 && NPC->activator )
- {//swallow victim
- G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
- //FIXME: sometimes end up with a live one in our mouths?
- //just make sure they're dead
- if ( NPC->activator->health > 0 )
- {
- //cut in half
- NPC->activator->client->dismembered = false;
- G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
- //KILL
- G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->enemy->health+1000, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE, HL_NONE );
- NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
- G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
- }
- NPC->count = 2;
- TIMER_Set( NPC, "clearGrabbed", 2600 );
- }
- break;
- }
- }
- // Just using this to remove the attacking flag at the right time
- TIMER_Done2( NPC, "attacking", qtrue );
- }
- //----------------------------------
- void Rancor_Combat( void )
- {
- if ( NPC->count )
- {//holding my enemy
- NPCInfo->enemyLastSeenTime = level.time;
- if ( TIMER_Done2( NPC, "takingPain", qtrue ))
- {
- NPCInfo->localState = LSTATE_CLEAR;
- }
- else if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
- && NPC->activator
- && NPC->activator->s.number >= MAX_CLIENTS )
- {
- Rancor_Attack( 0, qfalse, qfalse );
- }
- else if ( NPC->useDebounceTime >= level.time
- && NPC->activator )
- {//just sniffing the guy
- if ( NPC->useDebounceTime <= level.time + 100
- && NPC->client->ps.legsAnim != BOTH_HOLD_DROP)
- {//just about done, drop him
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer+(Q_irand(500,1000)*(3-g_spskill->integer)) );
- }
- }
- else
- {
- if ( !NPC->useDebounceTime
- && NPC->activator
- && NPC->activator->s.number < MAX_CLIENTS )
- {//first time I pick the player, just sniff them
- if ( TIMER_Done(NPC,"attacking") )
- {//ready to attack
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_SNIFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- NPC->useDebounceTime = level.time + NPC->client->ps.legsAnimTimer + Q_irand( 500, 2000 );
- }
- }
- else
- {
- Rancor_Attack( 0, qfalse, qfalse );
- }
- }
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- NPCInfo->goalRadius = NPC->maxs[0]+(MAX_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range
- // If we cannot see our target or we have somewhere to go, then do that
- if ( !NPC_ClearLOS( NPC->enemy ) || UpdateGoal( ))
- {
- NPCInfo->combatMove = qtrue;
- NPCInfo->goalEntity = NPC->enemy;
- Rancor_Move( qfalse );
- return;
- }
- NPCInfo->enemyLastSeenTime = level.time;
- // 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 = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin );
- qboolean advance = (qboolean)( distance > (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) ? qtrue : qfalse );
- qboolean doCharge = qfalse;
- if ( advance )
- {//have to get closer
- if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
- && (!NPC->enemy||!NPC->enemy->client) )
- {//don't do breath attack vs. bbrushes
- }
- else
- {
- vec3_t yawOnlyAngles = {0, NPC->currentAngles[YAW], 0};
- if ( NPC->enemy->health > 0
- && fabs(distance-(250.0f*NPC->s.modelScale[0])) <= (80.0f*NPC->s.modelScale[0])
- && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, yawOnlyAngles, 30, 30 ) )
- {
- int chance = 9;
- if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
- {//higher chance of doing breath attack
- chance = 5-g_spskill->integer;
- }
- if ( !Q_irand( 0, chance ) )
- {//go for the charge
- doCharge = qtrue;
- advance = 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
- {
- Rancor_Move( 1 );
- }
- }
- else
- {
- Rancor_Attack( distance, doCharge, qfalse );
- }
- }
- /*
- -------------------------
- NPC_Rancor_Pain
- -------------------------
- */
- void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
- {
- qboolean hitByRancor = qfalse;
-
- if ( self->NPC && self->NPC->ignorePain )
- {
- return;
- }
- if ( !TIMER_Done( self, "breathAttack" ) )
- {//nothing interrupts breath attack
- return;
- }
- TIMER_Remove( self, "confusionTime" );
- if ( other&&other->client&&other->client->NPC_class==CLASS_RANCOR )
- {
- hitByRancor = qtrue;
- }
- if ( other
- && other->inuse
- && other != self->enemy
- && !(other->flags&FL_NOTARGET) )
- {
- if ( !self->count )
- {
- if ( (!other->s.number&&!Q_irand(0,3))
- || !self->enemy
- || self->enemy->health == 0
- || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR)
- || (!Q_irand(0, 4 ) && DistanceSquared( other->currentOrigin, self->currentOrigin ) < DistanceSquared( self->enemy->currentOrigin, self->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
- self->lastEnemy = self->enemy;
- G_SetEnemy( self, other );
- if ( self->enemy != self->lastEnemy )
- {//clear this so that we only sniff the player the first time we pick them up
- self->useDebounceTime = 0;
- }
- TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
- if ( hitByRancor )
- {//stay mad at this Rancor for 2-5 secs before looking for other 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
- && self->client->ps.legsAnim != BOTH_ATTACK10
- && self->client->ps.legsAnim != BOTH_ATTACK11 )
- {//cant interrupt one of the big attack anims
- /*
- if ( self->count != 1
- || other == 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.legsAnimTimer+Q_irand(0, 500*(2-g_spskill->integer)) );
- if ( self->NPC )
- {
- self->NPC->localState = LSTATE_WAITING;
- }
- }
- }
- }
- }
- //let go
- /*
- if ( !Q_irand( 0, 3 ) && self->count == 1 )
- {
- Rancor_DropVictim( self );
- }
- */
- }
- }
- void Rancor_CheckDropVictim( void )
- {
- if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
- && NPC->activator->s.number >= MAX_CLIENTS )
- {
- return;
- }
- vec3_t mins={NPC->activator->mins[0]-1,NPC->activator->mins[1]-1,0};
- vec3_t maxs={NPC->activator->maxs[0]+1,NPC->activator->maxs[1]+1,1};
- vec3_t start={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmin[2]};
- vec3_t end={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmax[2]-1};
- trace_t trace;
- gi.trace( &trace, start, mins, maxs, end, NPC->activator->s.number, NPC->activator->clipmask );
- // First, if the held person is outside the world, eat them instead!
- extern int CM_LeafCluster( int );
- extern int CM_PointLeafnum( const vec3_t p );
- if( CM_LeafCluster( CM_PointLeafnum( NPC->activator->currentOrigin ) ) == -1 )
- {
- Rancor_Attack( 0, qfalse, qfalse );
- }
- else if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f )
- {
- Rancor_DropVictim( NPC );
- }
- }
- qboolean Rancor_AttackBBrush( void )
- {
- trace_t trace;
- vec3_t center;
- vec3_t dir2Brush, end;
- float checkDist = 64.0f;//32.0f;
- if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
- {//no origin brush, calc center
- VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
- VectorScale( center, 0.5f, center );
- }
- else
- {
- VectorCopy( NPCInfo->blockedEntity->s.origin, center );
- }
- if ( NAVDEBUG_showCollision )
- {
- CG_DrawEdge( NPC->currentOrigin, center, EDGE_IMPACT_POSSIBLE );
- }
- center[2] = NPC->currentOrigin[2];//we can't fly, so let's ignore z diff
- NPC_FacePosition( center, qfalse );
- //see if we're close to it
- VectorSubtract( center, NPC->currentOrigin, dir2Brush );
- float brushSize = ((NPCInfo->blockedEntity->maxs[0] - NPCInfo->blockedEntity->mins[0])*0.5f+(NPCInfo->blockedEntity->maxs[1] - NPCInfo->blockedEntity->mins[1])*0.5f) * 0.5f;
- float dist2Brush = VectorNormalize( dir2Brush )-(NPC->maxs[0])-brushSize;
- if ( dist2Brush < (MIN_DISTANCE*NPC->s.modelScale[0]) )
- {//close enough to just hit it
- trace.fraction = 0.0f;
- trace.entityNum = NPCInfo->blockedEntity->s.number;
- }
- else
- {
- VectorMA( NPC->currentOrigin, checkDist, dir2Brush, end );
- gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask );
- if ( trace.allsolid || trace.startsolid )
- {//wtf?
- NPCInfo->blockedEntity = NULL;
- return qfalse;
- }
- }
- if ( trace.fraction >= 1.0f //too far away
- || trace.entityNum != NPCInfo->blockedEntity->s.number )//OR blocked by something else
- {//keep moving towards it
- ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
- STEER::Activate(NPC);
- STEER::Seek(NPC, center);
- STEER::AvoidCollisions(NPC);
- STEER::DeActivate(NPC, &ucmd);
- /*
- VectorCopy( dir2Brush, NPC->client->ps.moveDir );
- if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
- {
- NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
- ucmd.buttons |= BUTTON_WALKING;
- }
- */
- //NPCInfo->enemyLastSeenTime = level.time;
- //let the function that called us know that we called NAV ourselves
- }
- else if ( trace.entityNum == NPCInfo->blockedEntity->s.number )
- {//close enough, smash it!
- Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through?
- TIMER_Remove( NPC, "attackDebounce" );//don't wait on these
- NPCInfo->enemyLastSeenTime = level.time;
- }
- else
- {
- //Com_Printf( S_COLOR_RED"RANCOR cannot reach intended breakable %s, blocked by %s\n", NPC->blockedEntity->targetname, g_entities[trace.entityNum].classname );
- if ( G_EntIsBreakable( trace.entityNum, NPC ) )
- {//oh, well, smash that, then
- //G_SetEnemy( NPC, &g_entities[trace.entityNum] );
- gentity_t* prevblockedEnt = NPCInfo->blockedEntity;
- NPCInfo->blockedEntity = &g_entities[trace.entityNum];
- Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through?
- TIMER_Remove( NPC, "attackDebounce" );//don't wait on these
- NPCInfo->enemyLastSeenTime = level.time;
- NPCInfo->blockedEntity = prevblockedEnt;
- }
- else
- {
- NPCInfo->blockedEntity = NULL;
- return qfalse;
- }
- }
- return qtrue;
- }
- void Rancor_FireBreathAttack( void )
- {
- int damage = Q_irand( 10, 15 );
- trace_t tr;
- gentity_t *traceEnt = NULL;
- mdxaBone_t boltMatrix;
- vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4};
- vec3_t rancAngles = {0,NPC->client->ps.viewangles[YAW],0};
- gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->gutBolt,
- &boltMatrix, rancAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
- NULL, NPC->s.modelScale );
- gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, start );
- gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, dir );
- VectorMA( start, 512, dir, end );
- gi.trace( &tr, start, traceMins, traceMaxs, end, NPC->s.number, MASK_SHOT );
- traceEnt = &g_entities[tr.entityNum];
- if ( tr.entityNum < ENTITYNUM_WORLD
- && traceEnt->takedamage
- && traceEnt->client )
- {//breath attack only does damage to living things
- G_Damage( traceEnt, NPC, NPC, dir, tr.endpos, damage*2, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC|DAMAGE_IGNORE_TEAM, MOD_LAVA, HL_NONE );
- }
- if ( tr.fraction < 1.0f )
- {//hit something, do radius damage
- G_RadiusDamage( tr.endpos, NPC, damage, 250, NPC, MOD_LAVA );
- }
- }
- void Rancor_CheckAnimDamage( void )
- {
- if ( NPC->client->ps.legsAnim == BOTH_ATTACK2
- || NPC->client->ps.legsAnim == BOTH_ATTACK10
- || NPC->client->ps.legsAnim == BOTH_ATTACK11 )
- {
- if ( NPC->client->ps.legsAnimTimer >= 1200 && NPC->client->ps.legsAnimTimer <= 1350 )
- {
- if ( Q_irand( 0, 2 ) )
- {
- Rancor_Swing( NPC->handRBolt, qfalse );
- }
- else
- {
- Rancor_Swing( NPC->handRBolt, qtrue );
- }
- }
- else if ( NPC->client->ps.legsAnimTimer >= 1100 && NPC->client->ps.legsAnimTimer <= 1550 )
- {
- Rancor_Swing( NPC->handRBolt, qtrue );
- }
- }
- else if ( NPC->client->ps.legsAnim == BOTH_ATTACK5 )
- {
- if ( NPC->client->ps.legsAnimTimer >= 750 && NPC->client->ps.legsAnimTimer <= 1300 )
- {
- Rancor_Swing( NPC->handLBolt, qfalse );
- }
- else if ( NPC->client->ps.legsAnimTimer >= 1700 && NPC->client->ps.legsAnimTimer <= 2300 )
- {
- Rancor_Swing( NPC->handRBolt, qfalse );
- }
- }
- }
- /*
- -------------------------
- NPC_BSRancor_Default
- -------------------------
- */
- void NPC_BSRancor_Default( void )
- {
- AddSightEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, 50 );
- if (NPCInfo->blockedEntity && TIMER_Done(NPC, "blockedEntityIgnore"))
- {
- if (!TIMER_Exists(NPC, "blockedEntityTimeOut"))
- {
- TIMER_Set(NPC, "blockedEntityTimeOut", 5000);
- }
- else if (TIMER_Done(NPC, "blockedEntityTimeOut"))
- {
- TIMER_Remove(NPC, "blockedEntityTimeOut");
- TIMER_Set(NPC, "blockedEntityIgnore", 25000);
- NPCInfo->blockedEntity = NULL;
- }
- }
- else
- {
- TIMER_Remove(NPC, "blockedEntityTimeOut");
- TIMER_Remove(NPC, "blockedEntityIgnore");
- }
- Rancor_CheckAnimDamage();
- if ( !TIMER_Done( NPC, "breathAttack" ) )
- {//doing breath attack, just do damage
- Rancor_FireBreathAttack();
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- else if ( NPC->client->ps.legsAnim == BOTH_ATTACK4
- || NPC->client->ps.legsAnim == BOTH_ATTACK6
- || NPC->client->ps.legsAnim == BOTH_ATTACK7 )
- {
- G_StopEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number );
- NPC->s.loopSound = 0;
- }
- if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) )
- {
- Rancor_DropVictim( NPC );
- }
- else if ( (NPC->client->ps.legsAnim == BOTH_PAIN2 || NPC->client->ps.legsAnim == BOTH_HOLD_DROP )
- && NPC->count == 1
- && NPC->activator )
- {
- Rancor_CheckDropVictim();
- }
- if ( !TIMER_Done( NPC, "rageTime" ) )
- {//do nothing but roar first time we see an enemy
- AddSoundEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, qfalse, qfalse );
- NPC_FaceEnemy( qtrue );
- return;
- }
- if ( NPCInfo->localState == LSTATE_WAITING
- && TIMER_Done2( NPC, "takingPain", qtrue ) )
- {//was not doing anything because we were taking pain, but pain is done now, so clear it...
- NPCInfo->localState = LSTATE_CLEAR;
- }
- if ( !TIMER_Done( NPC, "confusionTime" ) )
- {
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- if ( 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(NPC,"angrynoise") )
- {
- G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/anger%d.wav", Q_irand(1, 3)) );
- TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) );
- }
- else
- {
- AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER_GREAT, qfalse, qfalse );
- }
- if ( NPC->count == 2 && 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( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR )
- {//got mad at another Rancor, look for a valid enemy
- if ( TIMER_Done( NPC, "rancorInfight" ) )
- {
- NPC_CheckEnemyExt( qtrue );
- }
- }
- else if ( !NPC->count )
- {
- if ( NPCInfo->blockedEntity )
- {//something in our way
- if ( !NPCInfo->blockedEntity->inuse )
- {//was destroyed
- NPCInfo->blockedEntity = NULL;
- }
- else
- {
- //a breakable?
- if ( G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
- {//breakable brush
- if ( !Rancor_AttackBBrush() )
- {//didn't move inside that func, so call move here...?
- Rancor_Move( 1 );
- }
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- else
- {//if it's a client and in our way, get mad at it!
- if ( NPCInfo->blockedEntity != NPC->enemy
- && NPCInfo->blockedEntity->client
- && NPC_ValidEnemy( NPCInfo->blockedEntity )
- && !Q_irand( 0, 9 ) )
- {
- G_SetEnemy( NPC, NPCInfo->blockedEntity );
- //look again in 2-5 secs
- TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
- NPCInfo->blockedEntity = NULL;
- }
- }
- }
- }
- if ( NPC_ValidEnemy( NPC->enemy ) == qfalse )
- {
- TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now
- if ( !NPC->enemy->inuse
- || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 )
- || (NPC->spawnflags&SPF_RANCOR_FASTKILL) )//don't linger on dead bodies
- {//it's been a while since the enemy died, or enemy is completely gone, get bored with him
- if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
- && player && player->health >= 0 )
- {//all else failing, always go after the player
- NPC->lastEnemy = NPC->enemy;
- G_SetEnemy( NPC, player );
- if ( NPC->enemy != NPC->lastEnemy )
- {//clear this so that we only sniff the player the first time we pick them up
- NPC->useDebounceTime = 0;
- }
- }
- else
- {
- NPC->enemy = NULL;
- Rancor_Patrol();
- NPC_UpdateAngles( qtrue, qtrue );
- return;
- }
- }
- }
- if ( TIMER_Done( NPC, "lookForNewEnemy" ) )
- {
- gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy?
- NPC->enemy = NULL;
- gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse );
- NPC->enemy = sav_enemy;
- if ( newEnemy && newEnemy != sav_enemy )
- {//picked up a new enemy!
- NPC->lastEnemy = NPC->enemy;
- G_SetEnemy( NPC, newEnemy );
- if ( NPC->enemy != NPC->lastEnemy )
- {//clear this so that we only sniff the player the first time we pick them up
- NPC->useDebounceTime = 0;
- }
- //hold this one for at least 5-15 seconds
- TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
- }
- else
- {//look again in 2-5 secs
- TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
- }
- }
- }
- Rancor_Combat();
- if ( TIMER_Done( NPC, "attacking" )
- && TIMER_Done( NPC, "takingpain" )
- && TIMER_Done( NPC, "confusionDebounce" )
- && NPCInfo->localState == LSTATE_CLEAR
- && !NPC->count )
- {//not busy
- if ( !ucmd.forwardmove
- && !ucmd.rightmove
- && VectorCompare( NPC->client->ps.moveDir, vec3_origin ) )
- {//not moving
- if ( level.time - NPCInfo->enemyLastSeenTime > 5000 )
- {//haven't seen an enemy in a while
- if ( !Q_irand( 0, 20 ) )
- {
- if ( Q_irand( 0, 1 ) )
- {
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- else
- {
- NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
- }
- TIMER_Set( NPC, "confusionTime", NPC->client->ps.legsAnimTimer );
- TIMER_Set( NPC, "confusionDebounce", NPC->client->ps.legsAnimTimer+Q_irand( 4000, 8000 ) );
- }
- }
- }
- }
- }
- else
- {
- if ( TIMER_Done(NPC,"idlenoise") )
- {
- G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 4)) );
- TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) );
- AddSoundEvent( NPC, NPC->currentOrigin, 384, AEL_DANGER, qfalse, qfalse );
- }
- if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
- {
- Rancor_Patrol();
- if ( !NPC->enemy && NPC->wait )
- {//we've been mad before and can't find an enemy
- if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
- && player && player->health >= 0 )
- {//all else failing, always go after the player
- NPC->lastEnemy = NPC->enemy;
- G_SetEnemy( NPC, player );
- if ( NPC->enemy != NPC->lastEnemy )
- {//clear this so that we only sniff the player the first time we pick them up
- NPC->useDebounceTime = 0;
- }
- }
- }
- }
- else
- {
- Rancor_Idle();
- }
- }
- NPC_UpdateAngles( qtrue, qtrue );
- }
|