AI_Rancor.cpp 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685
  1. // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
  2. #include "g_headers.h"
  3. #include "b_local.h"
  4. // These define the working combat range for these suckers
  5. #define MIN_DISTANCE 128
  6. #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
  7. #define MAX_DISTANCE 1024
  8. #define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE )
  9. #define LSTATE_CLEAR 0
  10. #define LSTATE_WAITING 1
  11. #define SPF_RANCOR_MUTANT 1
  12. #define SPF_RANCOR_FASTKILL 2
  13. extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
  14. extern cvar_t *g_dismemberment;
  15. extern cvar_t *g_bobaDebug;
  16. void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity );
  17. /*
  18. -------------------------
  19. NPC_Rancor_Precache
  20. -------------------------
  21. */
  22. void NPC_Rancor_Precache( void )
  23. {
  24. int i;
  25. for ( i = 1; i < 5; i ++ )
  26. {
  27. G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", i) );
  28. }
  29. G_SoundIndex( "sound/chars/rancor/swipehit.wav" );
  30. G_SoundIndex( "sound/chars/rancor/chomp.wav" );
  31. }
  32. void NPC_MutantRancor_Precache( void )
  33. {
  34. G_SoundIndex( "sound/chars/rancor/breath_start.wav" );
  35. G_SoundIndex( "sound/chars/rancor/breath_loop.wav" );
  36. G_EffectIndex( "mrancor/breath" );
  37. }
  38. //FIXME: initialize all my timers
  39. qboolean Rancor_CheckAhead( vec3_t end )
  40. {
  41. trace_t trace;
  42. int clipmask = NPC->clipmask|CONTENTS_BOTCLIP;
  43. //make sure our goal isn't underground (else the trace will fail)
  44. vec3_t bottom = {end[0],end[1],end[2]+NPC->mins[2]};
  45. gi.trace( &trace, end, vec3_origin, vec3_origin, bottom, NPC->s.number, NPC->clipmask );
  46. if ( trace.fraction < 1.0f )
  47. {//in the ground, raise it up
  48. end[2] -= NPC->mins[2]*(1.0f-trace.fraction)-0.125f;
  49. }
  50. gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
  51. if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
  52. {//started inside do not enter, so ignore them
  53. clipmask &= ~CONTENTS_BOTCLIP;
  54. gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask );
  55. }
  56. //Do a simple check
  57. if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) )
  58. return qtrue;
  59. if ( trace.entityNum < ENTITYNUM_WORLD
  60. && G_EntIsBreakable( trace.entityNum, NPC ) )
  61. {//breakable brush in our way, break it
  62. // NPCInfo->blockedEntity = &g_entities[trace.entityNum];
  63. return qtrue;
  64. }
  65. //Aw screw it, always try to go straight at him if we can at all
  66. if ( trace.fraction >= 0.25f )
  67. return qtrue;
  68. //FIXME: if something in the way that's not the world, set blocked ent
  69. return qfalse;
  70. }
  71. /*
  72. -------------------------
  73. Rancor_Idle
  74. -------------------------
  75. */
  76. void Rancor_Idle( void )
  77. {
  78. NPCInfo->localState = LSTATE_CLEAR;
  79. //If we have somewhere to go, then do that
  80. if ( UpdateGoal() )
  81. {
  82. ucmd.buttons &= ~BUTTON_WALKING;
  83. NPC_MoveToGoal( qtrue );
  84. }
  85. }
  86. qboolean Rancor_CheckRoar( gentity_t *self )
  87. {
  88. if ( !self->wait )
  89. {//haven't ever gotten mad yet
  90. self->wait = 1;//do this only once
  91. NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  92. TIMER_Set( self, "rageTime", self->client->ps.legsAnimTimer );
  93. return qtrue;
  94. }
  95. return qfalse;
  96. }
  97. /*
  98. -------------------------
  99. Rancor_Patrol
  100. -------------------------
  101. */
  102. void Rancor_Patrol( void )
  103. {
  104. NPCInfo->localState = LSTATE_CLEAR;
  105. //If we have somewhere to go, then do that
  106. if ( UpdateGoal() )
  107. {
  108. ucmd.buttons &= ~BUTTON_WALKING;
  109. NPC_MoveToGoal( qtrue );
  110. }
  111. if ( NPC_CheckEnemyExt( qtrue ) == qfalse )
  112. {
  113. Rancor_Idle();
  114. return;
  115. }
  116. Rancor_CheckRoar( NPC );
  117. TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
  118. }
  119. /*
  120. -------------------------
  121. Rancor_Move
  122. -------------------------
  123. */
  124. void Rancor_Move( qboolean visible )
  125. {
  126. if ( NPCInfo->localState != LSTATE_WAITING )
  127. {
  128. NPCInfo->goalEntity = NPC->enemy;
  129. NPCInfo->goalRadius = NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range
  130. //FIXME: for some reason, if NPC_MoveToGoal fails, it sets my angles to my lastPathAngles, which I don't want
  131. float savYaw = NPCInfo->desiredYaw;
  132. bool savWalking = !!(ucmd.buttons&BUTTON_WALKING);
  133. if ( !NPC_MoveToGoal( qtrue ) )
  134. {//can't macro-nav, just head right for him
  135. //FIXME: if something in the way that's not the world, set blocked ent
  136. vec3_t dest;
  137. VectorCopy( NPCInfo->goalEntity->currentOrigin, dest );
  138. if ( Rancor_CheckAhead( dest ) )
  139. {//use our temp move straight to goal check
  140. if (!savWalking)
  141. {
  142. ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
  143. }
  144. STEER::Activate(NPC);
  145. STEER::Seek(NPC, dest);
  146. STEER::AvoidCollisions(NPC);
  147. STEER::DeActivate(NPC, &ucmd);
  148. /*
  149. VectorSubtract( dest, NPC->currentOrigin, NPC->client->ps.moveDir );
  150. NPC->client->ps.speed = VectorNormalize( NPC->client->ps.moveDir );
  151. NPCInfo->desiredYaw = vectoyaw( NPC->client->ps.moveDir );
  152. if ( (ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed > NPCInfo->stats.walkSpeed )
  153. {
  154. NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
  155. }
  156. else
  157. {
  158. if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
  159. {
  160. NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
  161. }
  162. if ( !(ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed < NPCInfo->stats.runSpeed )
  163. {
  164. NPC->client->ps.speed = NPCInfo->stats.runSpeed;
  165. }
  166. else if ( NPC->client->ps.speed > NPCInfo->stats.runSpeed )
  167. {
  168. NPC->client->ps.speed = NPCInfo->stats.runSpeed;
  169. }
  170. }
  171. */
  172. }
  173. else
  174. {//all else fails, look at him
  175. // gi.Printf("Fail\n");
  176. NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = savYaw;
  177. /* if ( !NPCInfo->blockedEntity )
  178. {//not already trying to break a breakable somewhere
  179. if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum < ENTITYNUM_WORLD )
  180. {//hmm, maybe he's on a breakable brush?
  181. if ( G_EntIsBreakable( NPC->enemy->client->ps.groundEntityNum, NPC ) )
  182. {//break it!
  183. gentity_t *breakable = &g_entities[NPC->enemy->client->ps.groundEntityNum];
  184. int sanityCheck = 0;
  185. //FiXME: and if he's on a stack of 3 breakables?
  186. //FIXME: See if the breakable has a targetname, if so see if the thing targeting it is a breakable, if so, etc...
  187. while ( sanityCheck < 20 && breakable && breakable->targetname )
  188. {
  189. gentity_t *breakableNext = NULL;
  190. while ( sanityCheck < 20 && (breakableNext = G_Find( breakableNext, FOFS(target), breakable->targetname )) != NULL )
  191. {
  192. if ( breakableNext && G_EntIsBreakable( breakableNext->s.number, NPC ) )
  193. {
  194. breakable = breakableNext;
  195. break;
  196. }
  197. else
  198. {
  199. sanityCheck++;
  200. }
  201. }
  202. if ( !breakableNext )
  203. {//not targetted by another breakable that we can break
  204. break;
  205. }
  206. else
  207. {
  208. sanityCheck++;
  209. }
  210. }
  211. NPCInfo->blockedEntity = breakable;
  212. }
  213. }
  214. }*/
  215. if ( !NPCInfo->blockedEntity && NPC->enemy && gi.inPVS(NPC->currentOrigin, NPC->enemy->currentOrigin))
  216. {//nothing to destroy? just go straight at goal dest
  217. qboolean horzClose = qfalse;
  218. if (!savWalking)
  219. {
  220. ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
  221. }
  222. if ( DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin ) < (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) )
  223. {//close, just look at him
  224. horzClose = qtrue;
  225. NPC_FaceEnemy( qtrue );
  226. }
  227. else
  228. {//try to move towards him
  229. STEER::Activate(NPC);
  230. STEER::Seek(NPC, dest);
  231. STEER::AvoidCollisions(NPC);
  232. STEER::DeActivate(NPC, &ucmd);
  233. }
  234. //let him know he should attack at random out of frustration?
  235. if ( NPCInfo->goalEntity == NPC->enemy )
  236. {
  237. if ( TIMER_Done( NPC, "attacking" )
  238. && TIMER_Done( NPC, "frustrationAttack" ) )
  239. {
  240. float enemyDist = Distance( dest, NPC->currentOrigin );
  241. if ( (!horzClose||!Q_irand(0,5))
  242. && Q_irand( 0, 1 ) )
  243. {
  244. Rancor_Attack( enemyDist, qtrue, qfalse );
  245. }
  246. else
  247. {
  248. Rancor_Attack( enemyDist, qfalse, qfalse );
  249. }
  250. if ( horzClose )
  251. {
  252. TIMER_Set( NPC, "frustrationAttack", Q_irand( 2000, 5000 ) );
  253. }
  254. else
  255. {
  256. TIMER_Set( NPC, "frustrationAttack", Q_irand( 5000, 15000 ) );
  257. }
  258. }
  259. }
  260. }
  261. }
  262. }
  263. }
  264. }
  265. //---------------------------------------------------------
  266. extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
  267. extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse );
  268. extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex );
  269. extern int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg );
  270. void Rancor_DropVictim( gentity_t *self )
  271. {
  272. //FIXME: if Rancor dies, it should drop its victim.
  273. //FIXME: if Rancor is removed, it must remove its victim.
  274. //FIXME: if in BOTH_HOLD_DROP, throw them a little, too?
  275. if ( self->activator )
  276. {
  277. if ( self->activator->client )
  278. {
  279. self->activator->client->ps.eFlags &= ~EF_HELD_BY_RANCOR;
  280. }
  281. self->activator->activator = NULL;
  282. if ( self->activator->health <= 0 )
  283. {
  284. if ( self->activator->s.number )
  285. {//never free player
  286. if ( self->count == 1 )
  287. {//in my hand, just drop them
  288. if ( self->activator->client )
  289. {
  290. self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0;
  291. //FIXME: ragdoll?
  292. }
  293. }
  294. else
  295. {
  296. G_FreeEntity( self->activator );
  297. }
  298. }
  299. else
  300. {
  301. self->activator->s.eFlags |= EF_NODRAW;
  302. if ( self->activator->client )
  303. {
  304. self->activator->client->ps.eFlags |= EF_NODRAW;
  305. }
  306. self->activator->clipmask &= ~CONTENTS_BODY;
  307. }
  308. }
  309. else
  310. {
  311. if ( self->activator->NPC )
  312. {//start thinking again
  313. self->activator->NPC->nextBStateThink = level.time;
  314. }
  315. //clear their anim and let them fall
  316. self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0;
  317. }
  318. if ( self->enemy == self->activator )
  319. {
  320. self->enemy = NULL;
  321. }
  322. if ( self->activator->s.number == 0 )
  323. {//don't attack the player again for a bit
  324. TIMER_Set( self, "attackDebounce", Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
  325. }
  326. self->activator = NULL;
  327. }
  328. self->count = 0;//drop him
  329. }
  330. void Rancor_Swing( int boltIndex, qboolean tryGrab )
  331. {
  332. gentity_t *radiusEnts[ 128 ];
  333. int numEnts;
  334. const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?200:88;
  335. const float radiusSquared = (radius*radius);
  336. int i;
  337. vec3_t boltOrg;
  338. vec3_t originUp;
  339. VectorCopy(NPC->currentOrigin, originUp);
  340. originUp[2] += (NPC->maxs[2]*0.75f);
  341. numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, boltIndex, boltOrg );
  342. //if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
  343. {//attacking a breakable brush
  344. //HMM... maybe always do this?
  345. //if boltOrg inside a breakable brush, damage it
  346. trace_t trace;
  347. gi.trace( &trace, NPC->pos3, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
  348. #ifndef FINAL_BUILD
  349. if ( g_bobaDebug->integer > 0 )
  350. {
  351. G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue);
  352. }
  353. #endif
  354. //remember pos3 for the trace from last hand pos to current hand pos next time
  355. VectorCopy( boltOrg, NPC->pos3 );
  356. //FIXME: also do a trace TO the bolt from where we are...?
  357. if ( G_EntIsBreakable( trace.entityNum, NPC ) )
  358. {
  359. G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 100, 0, MOD_MELEE );
  360. }
  361. else
  362. {//fuck, do an actual line trace, I guess...
  363. gi.trace( &trace, originUp, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
  364. #ifndef FINAL_BUILD
  365. if ( g_bobaDebug->integer > 0 )
  366. {
  367. G_DebugLine(originUp, boltOrg, 1000, 0x000000ff, qtrue);
  368. }
  369. #endif
  370. if ( G_EntIsBreakable( trace.entityNum, NPC ) )
  371. {
  372. G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
  373. }
  374. }
  375. }
  376. for ( i = 0; i < numEnts; i++ )
  377. {
  378. if ( !radiusEnts[i]->inuse )
  379. {
  380. continue;
  381. }
  382. if ( radiusEnts[i] == NPC )
  383. {//Skip the rancor ent
  384. continue;
  385. }
  386. if ( radiusEnts[i]->client == NULL )
  387. {//must be a client
  388. continue;
  389. }
  390. if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR)
  391. ||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
  392. {//can't be one already being held
  393. continue;
  394. }
  395. if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
  396. {//not if invisible
  397. continue;
  398. }
  399. /*
  400. if ( !radiusEnts[i]->contents )
  401. {//not if non-solid
  402. continue;
  403. }
  404. */
  405. if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared )
  406. {
  407. if ( !gi.inPVS( radiusEnts[i]->currentOrigin, NPC->currentOrigin ) )
  408. {//don't grab anything that's in another PVS
  409. continue;
  410. }
  411. /*
  412. qboolean skipGrab = qfalse;
  413. if ( tryGrab//want to grab
  414. && (NPC->spawnflags&SPF_RANCOR_FASTKILL)//mutant rancor
  415. && radiusEnts[i]->s.number >= MAX_CLIENTS //not the player
  416. && Q_irand( 0, 1 ) )//50% chance
  417. {//don't grab them, just smack them away
  418. skipGrab = qtrue;
  419. }
  420. */
  421. if ( tryGrab
  422. //&& !skipGrab
  423. && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth!
  424. && radiusEnts[i]->client->NPC_class != CLASS_RANCOR
  425. && radiusEnts[i]->client->NPC_class != CLASS_GALAKMECH
  426. && radiusEnts[i]->client->NPC_class != CLASS_ATST
  427. && radiusEnts[i]->client->NPC_class != CLASS_GONK
  428. && radiusEnts[i]->client->NPC_class != CLASS_R2D2
  429. && radiusEnts[i]->client->NPC_class != CLASS_R5D2
  430. && radiusEnts[i]->client->NPC_class != CLASS_MARK1
  431. && radiusEnts[i]->client->NPC_class != CLASS_MARK2
  432. && radiusEnts[i]->client->NPC_class != CLASS_MOUSE
  433. && radiusEnts[i]->client->NPC_class != CLASS_PROBE
  434. && radiusEnts[i]->client->NPC_class != CLASS_SEEKER
  435. && radiusEnts[i]->client->NPC_class != CLASS_REMOTE
  436. && radiusEnts[i]->client->NPC_class != CLASS_SENTRY
  437. && radiusEnts[i]->client->NPC_class != CLASS_INTERROGATOR
  438. && radiusEnts[i]->client->NPC_class != CLASS_VEHICLE )
  439. {//grab
  440. if ( NPC->count == 2 )
  441. {//have one in my mouth, remove him
  442. TIMER_Remove( NPC, "clearGrabbed" );
  443. Rancor_DropVictim( NPC );
  444. }
  445. NPC->enemy = radiusEnts[i];//make him my new best friend
  446. radiusEnts[i]->client->ps.eFlags |= EF_HELD_BY_RANCOR;
  447. //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something
  448. radiusEnts[i]->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it.
  449. NPC->activator = radiusEnts[i];//remember him
  450. NPC->count = 1;//in my hand
  451. //wait to attack
  452. TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand(500, 2500) );
  453. if ( radiusEnts[i]->health > 0 )
  454. {//do pain on enemy
  455. GEntity_PainFunc( radiusEnts[i], NPC, NPC, radiusEnts[i]->currentOrigin, 0, MOD_CRUSH );
  456. }
  457. else if ( radiusEnts[i]->client )
  458. {
  459. NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  460. }
  461. }
  462. else
  463. {//smack
  464. G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
  465. //actually push the enemy
  466. vec3_t pushDir;
  467. /*
  468. //VectorSubtract( radiusEnts[i]->currentOrigin, boltOrg, pushDir );
  469. VectorSubtract( radiusEnts[i]->currentOrigin, NPC->currentOrigin, pushDir );
  470. pushDir[2] = Q_flrand( 100, 200 );
  471. VectorNormalize( pushDir );
  472. */
  473. if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
  474. && radiusEnts[i]->s.number >= MAX_CLIENTS )
  475. {
  476. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
  477. }
  478. vec3_t angs;
  479. VectorCopy( NPC->client->ps.viewangles, angs );
  480. angs[YAW] += Q_flrand( 25, 50 );
  481. angs[PITCH] = Q_flrand( -25, -15 );
  482. AngleVectors( angs, pushDir, NULL, NULL );
  483. if ( radiusEnts[i]->client->NPC_class != CLASS_RANCOR
  484. && radiusEnts[i]->client->NPC_class != CLASS_ATST
  485. && !(radiusEnts[i]->flags&FL_NO_KNOCKBACK) )
  486. {
  487. G_Throw( radiusEnts[i], pushDir, 250 );
  488. if ( radiusEnts[i]->health > 0 )
  489. {//do pain on enemy
  490. G_Knockdown( radiusEnts[i], NPC, pushDir, 100, qtrue );
  491. }
  492. }
  493. }
  494. }
  495. }
  496. }
  497. void Rancor_Smash( void )
  498. {
  499. gentity_t *radiusEnts[ 128 ];
  500. int numEnts;
  501. const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?256:128;
  502. const float halfRadSquared = ((radius/2)*(radius/2));
  503. const float radiusSquared = (radius*radius);
  504. float distSq;
  505. int i;
  506. vec3_t boltOrg;
  507. AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER, qfalse, qtrue );
  508. numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->handLBolt, boltOrg );
  509. //if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
  510. {//attacking a breakable brush
  511. //HMM... maybe always do this?
  512. //if boltOrg inside a breakable brush, damage it
  513. trace_t trace;
  514. gi.trace( &trace, boltOrg, vec3_origin, vec3_origin, NPC->pos3, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
  515. #ifndef FINAL_BUILD
  516. if ( g_bobaDebug->integer > 0 )
  517. {
  518. G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue);
  519. }
  520. #endif
  521. //remember pos3 for the trace from last hand pos to current hand pos next time
  522. VectorCopy( boltOrg, NPC->pos3 );
  523. //FIXME: also do a trace TO the bolt from where we are...?
  524. if ( G_EntIsBreakable( trace.entityNum, NPC ) )
  525. {
  526. G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
  527. }
  528. else
  529. {//fuck, do an actual line trace, I guess...
  530. gi.trace( &trace, NPC->currentOrigin, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY );
  531. #ifndef FINAL_BUILD
  532. if ( g_bobaDebug->integer > 0 )
  533. {
  534. G_DebugLine(NPC->currentOrigin, boltOrg, 1000, 0x000000ff, qtrue);
  535. }
  536. #endif
  537. if ( G_EntIsBreakable( trace.entityNum, NPC ) )
  538. {
  539. G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE );
  540. }
  541. }
  542. }
  543. for ( i = 0; i < numEnts; i++ )
  544. {
  545. if ( !radiusEnts[i]->inuse )
  546. {
  547. continue;
  548. }
  549. if ( radiusEnts[i] == NPC )
  550. {//Skip the rancor ent
  551. continue;
  552. }
  553. if ( radiusEnts[i]->client == NULL )
  554. {//must be a client
  555. if ( G_EntIsBreakable( radiusEnts[i]->s.number, NPC ) )
  556. {//damage breakables within range, but not as much
  557. if ( !Q_irand( 0, 1 ) )
  558. {
  559. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, 100, 0, MOD_MELEE );
  560. }
  561. }
  562. continue;
  563. }
  564. if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
  565. {//can't be one being held
  566. continue;
  567. }
  568. if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
  569. {//not if invisible
  570. continue;
  571. }
  572. distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg );
  573. if ( distSq <= radiusSquared )
  574. {
  575. if ( distSq < halfRadSquared )
  576. {//close enough to do damage, too
  577. G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
  578. if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
  579. && radiusEnts[i]->s.number >= MAX_CLIENTS )
  580. {
  581. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
  582. }
  583. else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better
  584. {//more damage
  585. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 40, 55 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  586. }
  587. else
  588. {
  589. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 10, 25 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  590. }
  591. }
  592. if ( radiusEnts[i]->health > 0
  593. && radiusEnts[i]->client
  594. && radiusEnts[i]->client->NPC_class != CLASS_RANCOR
  595. && radiusEnts[i]->client->NPC_class != CLASS_ATST )
  596. {
  597. if ( distSq < halfRadSquared
  598. || radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE )
  599. {//within range of my fist or withing ground-shaking range and not in the air
  600. if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
  601. {
  602. G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qtrue );
  603. }
  604. else
  605. {
  606. G_Knockdown( radiusEnts[i], NPC, vec3_origin, Q_irand( 200, 350), qtrue );
  607. }
  608. }
  609. }
  610. }
  611. }
  612. }
  613. void Rancor_Bite( void )
  614. {
  615. gentity_t *radiusEnts[ 128 ];
  616. int numEnts;
  617. const float radius = 100;
  618. const float radiusSquared = (radius*radius);
  619. int i;
  620. vec3_t boltOrg;
  621. numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->gutBolt, boltOrg );
  622. for ( i = 0; i < numEnts; i++ )
  623. {
  624. if ( !radiusEnts[i]->inuse )
  625. {
  626. continue;
  627. }
  628. if ( radiusEnts[i] == NPC )
  629. {//Skip the rancor ent
  630. continue;
  631. }
  632. if ( radiusEnts[i]->client == NULL )
  633. {//must be a client
  634. continue;
  635. }
  636. if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) )
  637. {//can't be one already being held
  638. continue;
  639. }
  640. if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) )
  641. {//not if invisible
  642. continue;
  643. }
  644. if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared )
  645. {
  646. if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
  647. && radiusEnts[i]->s.number >= MAX_CLIENTS )
  648. {
  649. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
  650. }
  651. else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
  652. {//more damage
  653. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 35, 50 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  654. }
  655. else
  656. {
  657. G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 15, 30 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  658. }
  659. if ( radiusEnts[i]->health <= 0 && radiusEnts[i]->client )
  660. {//killed them, chance of dismembering
  661. if ( !Q_irand( 0, 1 ) )
  662. {//bite something off
  663. int hitLoc = HL_WAIST;
  664. if ( g_dismemberment->integer < 3 )
  665. {
  666. hitLoc = Q_irand( HL_BACK_RT, HL_HAND_LT );
  667. }
  668. else
  669. {
  670. hitLoc = Q_irand( HL_WAIST, HL_HEAD );
  671. }
  672. if ( hitLoc == HL_HEAD )
  673. {
  674. NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  675. }
  676. else if ( hitLoc == HL_WAIST )
  677. {
  678. NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  679. }
  680. radiusEnts[i]->client->dismembered = false;
  681. //FIXME: the limb should just disappear, cuz I ate it
  682. G_DoDismemberment( radiusEnts[i], radiusEnts[i]->currentOrigin, MOD_SABER, 1000, hitLoc, qtrue );
  683. }
  684. }
  685. G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
  686. }
  687. }
  688. }
  689. //------------------------------
  690. extern gentity_t *TossClientItems( gentity_t *self );
  691. void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity )
  692. {
  693. if ( !TIMER_Exists( NPC, "attacking" )
  694. && TIMER_Done( NPC, "attackDebounce" ) )
  695. {
  696. if ( NPC->count == 2 && NPC->activator )
  697. {
  698. }
  699. else if ( NPC->count == 1 && NPC->activator )
  700. {//holding enemy
  701. if ( (!(NPC->spawnflags&SPF_RANCOR_FASTKILL) ||NPC->activator->s.number<MAX_CLIENTS)
  702. && NPC->activator->health > 0
  703. && Q_irand( 0, 1 ) )
  704. {//quick bite
  705. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  706. TIMER_Set( NPC, "attack_dmg", 450 );
  707. }
  708. else
  709. {//full eat
  710. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  711. TIMER_Set( NPC, "attack_dmg", 900 );
  712. //Make victim scream in fright
  713. if ( NPC->activator->health > 0 && NPC->activator->client )
  714. {
  715. G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 );
  716. NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  717. if ( NPC->activator->NPC )
  718. {//no more thinking for you
  719. TossClientItems( NPC );
  720. NPC->activator->NPC->nextBStateThink = Q3_INFINITE;
  721. }
  722. }
  723. }
  724. }
  725. else if ( NPC->enemy->health > 0 && doCharge )
  726. {//charge
  727. if ( !Q_irand( 0, 3 ) )
  728. {
  729. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK5, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  730. TIMER_Set( NPC, "attack_dmg", 1250 );
  731. if ( NPC->enemy && NPC->enemy->s.number == 0 )
  732. {//don't attack the player again for a bit
  733. TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
  734. }
  735. }
  736. else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
  737. {//breath attack
  738. int breathAnim = BOTH_ATTACK4;
  739. gentity_t *checkEnt = NULL;
  740. vec3_t center;
  741. if ( NPC->enemy && NPC->enemy->inuse )
  742. {
  743. checkEnt = NPC->enemy;
  744. VectorCopy( NPC->enemy->currentOrigin, center );
  745. }
  746. else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse )
  747. {
  748. checkEnt = NPCInfo->blockedEntity;
  749. //if it has an origin brush, use it...
  750. if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
  751. {//no origin brush, calc center
  752. VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
  753. VectorScale( center, 0.5f, center );
  754. }
  755. else
  756. {//use origin brush as center
  757. VectorCopy( NPCInfo->blockedEntity->s.origin, center );
  758. }
  759. }
  760. if ( checkEnt )
  761. {
  762. float zHeightRelative = center[2]-NPC->currentOrigin[2];
  763. if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) )
  764. {
  765. breathAnim = BOTH_ATTACK7;
  766. }
  767. else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) )
  768. {
  769. breathAnim = BOTH_ATTACK6;
  770. }
  771. }
  772. NPC_SetAnim( NPC, SETANIM_BOTH, breathAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  773. //start effect here
  774. G_PlayEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number, NPC->currentOrigin, (NPC->client->ps.legsAnimTimer-500), qfalse );
  775. TIMER_Set( NPC, "breathAttack", NPC->client->ps.legsAnimTimer-500 );
  776. G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/chars/rancor/breath_start.wav" );
  777. NPC->s.loopSound = G_SoundIndex( "sound/chars/rancor/breath_loop.wav" );
  778. if ( NPC->enemy && NPC->enemy->s.number == 0 )
  779. {//don't attack the player again for a bit
  780. TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
  781. }
  782. }
  783. else
  784. {
  785. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  786. TIMER_Set( NPC, "attack_dmg", 1250 );
  787. vec3_t fwd, yawAng ={0, NPC->client->ps.viewangles[YAW], 0};
  788. AngleVectors( yawAng, fwd, NULL, NULL );
  789. VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity );
  790. NPC->client->ps.velocity[2] = 150;
  791. NPC->client->ps.groundEntityNum = ENTITYNUM_NONE;
  792. if ( NPC->enemy && NPC->enemy->s.number == 0 )
  793. {//don't attack the player again for a bit
  794. TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
  795. }
  796. }
  797. }
  798. else if ( !Q_irand(0, 1)
  799. /*&& (NPC->spawnflags&SPF_RANCOR_MUTANT)*/ )
  800. {//mutant rancor can smash
  801. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  802. TIMER_Set( NPC, "attack_dmg", 900 );
  803. //init pos3 for the trace from last hand pos to current hand pos
  804. VectorCopy( NPC->currentOrigin, NPC->pos3 );
  805. }
  806. else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
  807. || distance >= NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])-64.0f )
  808. {//try to grab
  809. int grabAnim = BOTH_ATTACK2;
  810. gentity_t *checkEnt = NULL;
  811. vec3_t center;
  812. if ( (!aimAtBlockedEntity||!NPCInfo->blockedEntity) && NPC->enemy && NPC->enemy->inuse )
  813. {
  814. checkEnt = NPC->enemy;
  815. VectorCopy( NPC->enemy->currentOrigin, center );
  816. }
  817. else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse )
  818. {
  819. checkEnt = NPCInfo->blockedEntity;
  820. //if it has an origin brush, use it...
  821. if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
  822. {//no origin brush, calc center
  823. VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
  824. VectorScale( center, 0.5f, center );
  825. }
  826. else
  827. {//use origin brush as center
  828. VectorCopy( NPCInfo->blockedEntity->s.origin, center );
  829. }
  830. }
  831. if ( checkEnt )
  832. {
  833. float zHeightRelative = center[2]-NPC->currentOrigin[2];
  834. if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) )
  835. {
  836. grabAnim = BOTH_ATTACK11;
  837. }
  838. else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) )
  839. {
  840. grabAnim = BOTH_ATTACK10;
  841. }
  842. }
  843. NPC_SetAnim( NPC, SETANIM_BOTH, grabAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  844. TIMER_Set( NPC, "attack_dmg", 800 );
  845. if ( NPC->enemy && NPC->enemy->s.number == 0 )
  846. {//don't attack the player again for a bit
  847. TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) );
  848. }
  849. //init pos3 for the trace from last hand pos to current hand pos
  850. VectorCopy( NPC->currentOrigin, NPC->pos3 );
  851. }
  852. else
  853. {
  854. //FIXME: back up?
  855. ucmd.forwardmove = -64;
  856. //FIXME: check for walls/ledges?
  857. return;
  858. }
  859. TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + random() * 200 );
  860. }
  861. // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks
  862. float playerDist;
  863. if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) )
  864. {
  865. switch ( NPC->client->ps.legsAnim )
  866. {
  867. case BOTH_MELEE1:
  868. Rancor_Smash();
  869. playerDist = NPC_EntRangeFromBolt( player, NPC->handLBolt );
  870. if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
  871. {
  872. if ( playerDist < 512 )
  873. {
  874. CGCam_Shake( 1.0f*playerDist/256, 1000 );
  875. }
  876. }
  877. else
  878. {
  879. if ( playerDist < 256 )
  880. {
  881. CGCam_Shake( 1.0f*playerDist/128.0f, 1000 );
  882. }
  883. }
  884. break;
  885. case BOTH_MELEE2:
  886. Rancor_Bite();
  887. TIMER_Set( NPC, "attack_dmg2", 450 );
  888. break;
  889. case BOTH_ATTACK1:
  890. if ( NPC->count == 1 && NPC->activator )
  891. {
  892. if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
  893. && NPC->activator->s.number >= MAX_CLIENTS )
  894. {
  895. G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->activator->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE );
  896. }
  897. else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better
  898. {//more damage
  899. G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 55, 70 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  900. }
  901. else
  902. {
  903. G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  904. }
  905. if ( NPC->activator->health <= 0 )
  906. {//killed him
  907. if ( g_dismemberment->integer >= 3 )
  908. {//make it look like we bit his head off
  909. NPC->activator->client->dismembered = false;
  910. G_DoDismemberment( NPC->activator, NPC->activator->currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue );
  911. }
  912. NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  913. }
  914. G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
  915. }
  916. break;
  917. case BOTH_ATTACK2:
  918. case BOTH_ATTACK10:
  919. case BOTH_ATTACK11:
  920. //try to grab
  921. Rancor_Swing( NPC->handRBolt, qtrue );
  922. break;
  923. case BOTH_ATTACK3:
  924. if ( NPC->count == 1 && NPC->activator )
  925. {
  926. //cut in half
  927. if ( NPC->activator->client )
  928. {
  929. NPC->activator->client->dismembered = false;
  930. G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
  931. }
  932. //KILL
  933. 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 );
  934. if ( NPC->activator->client )
  935. {
  936. NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  937. }
  938. TIMER_Set( NPC, "attack_dmg2", 1350 );
  939. G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) );
  940. G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
  941. }
  942. break;
  943. }
  944. }
  945. else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) )
  946. {
  947. switch ( NPC->client->ps.legsAnim )
  948. {
  949. case BOTH_MELEE1:
  950. break;
  951. case BOTH_MELEE2:
  952. Rancor_Bite();
  953. break;
  954. case BOTH_ATTACK1:
  955. break;
  956. case BOTH_ATTACK2:
  957. break;
  958. case BOTH_ATTACK3:
  959. if ( NPC->count == 1 && NPC->activator )
  960. {//swallow victim
  961. G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) );
  962. //FIXME: sometimes end up with a live one in our mouths?
  963. //just make sure they're dead
  964. if ( NPC->activator->health > 0 )
  965. {
  966. //cut in half
  967. NPC->activator->client->dismembered = false;
  968. G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue );
  969. //KILL
  970. 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 );
  971. NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  972. G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health );
  973. }
  974. NPC->count = 2;
  975. TIMER_Set( NPC, "clearGrabbed", 2600 );
  976. }
  977. break;
  978. }
  979. }
  980. // Just using this to remove the attacking flag at the right time
  981. TIMER_Done2( NPC, "attacking", qtrue );
  982. }
  983. //----------------------------------
  984. void Rancor_Combat( void )
  985. {
  986. if ( NPC->count )
  987. {//holding my enemy
  988. NPCInfo->enemyLastSeenTime = level.time;
  989. if ( TIMER_Done2( NPC, "takingPain", qtrue ))
  990. {
  991. NPCInfo->localState = LSTATE_CLEAR;
  992. }
  993. else if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
  994. && NPC->activator
  995. && NPC->activator->s.number >= MAX_CLIENTS )
  996. {
  997. Rancor_Attack( 0, qfalse, qfalse );
  998. }
  999. else if ( NPC->useDebounceTime >= level.time
  1000. && NPC->activator )
  1001. {//just sniffing the guy
  1002. if ( NPC->useDebounceTime <= level.time + 100
  1003. && NPC->client->ps.legsAnim != BOTH_HOLD_DROP)
  1004. {//just about done, drop him
  1005. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  1006. TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer+(Q_irand(500,1000)*(3-g_spskill->integer)) );
  1007. }
  1008. }
  1009. else
  1010. {
  1011. if ( !NPC->useDebounceTime
  1012. && NPC->activator
  1013. && NPC->activator->s.number < MAX_CLIENTS )
  1014. {//first time I pick the player, just sniff them
  1015. if ( TIMER_Done(NPC,"attacking") )
  1016. {//ready to attack
  1017. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_SNIFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  1018. NPC->useDebounceTime = level.time + NPC->client->ps.legsAnimTimer + Q_irand( 500, 2000 );
  1019. }
  1020. }
  1021. else
  1022. {
  1023. Rancor_Attack( 0, qfalse, qfalse );
  1024. }
  1025. }
  1026. NPC_UpdateAngles( qtrue, qtrue );
  1027. return;
  1028. }
  1029. NPCInfo->goalRadius = NPC->maxs[0]+(MAX_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range
  1030. // If we cannot see our target or we have somewhere to go, then do that
  1031. if ( !NPC_ClearLOS( NPC->enemy ) || UpdateGoal( ))
  1032. {
  1033. NPCInfo->combatMove = qtrue;
  1034. NPCInfo->goalEntity = NPC->enemy;
  1035. Rancor_Move( qfalse );
  1036. return;
  1037. }
  1038. NPCInfo->enemyLastSeenTime = level.time;
  1039. // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb
  1040. NPC_FaceEnemy( qtrue );
  1041. float distance = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin );
  1042. qboolean advance = (qboolean)( distance > (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) ? qtrue : qfalse );
  1043. qboolean doCharge = qfalse;
  1044. if ( advance )
  1045. {//have to get closer
  1046. if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
  1047. && (!NPC->enemy||!NPC->enemy->client) )
  1048. {//don't do breath attack vs. bbrushes
  1049. }
  1050. else
  1051. {
  1052. vec3_t yawOnlyAngles = {0, NPC->currentAngles[YAW], 0};
  1053. if ( NPC->enemy->health > 0
  1054. && fabs(distance-(250.0f*NPC->s.modelScale[0])) <= (80.0f*NPC->s.modelScale[0])
  1055. && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, yawOnlyAngles, 30, 30 ) )
  1056. {
  1057. int chance = 9;
  1058. if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )
  1059. {//higher chance of doing breath attack
  1060. chance = 5-g_spskill->integer;
  1061. }
  1062. if ( !Q_irand( 0, chance ) )
  1063. {//go for the charge
  1064. doCharge = qtrue;
  1065. advance = qfalse;
  1066. }
  1067. }
  1068. }
  1069. }
  1070. if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack
  1071. {
  1072. if ( TIMER_Done2( NPC, "takingPain", qtrue ))
  1073. {
  1074. NPCInfo->localState = LSTATE_CLEAR;
  1075. }
  1076. else
  1077. {
  1078. Rancor_Move( 1 );
  1079. }
  1080. }
  1081. else
  1082. {
  1083. Rancor_Attack( distance, doCharge, qfalse );
  1084. }
  1085. }
  1086. /*
  1087. -------------------------
  1088. NPC_Rancor_Pain
  1089. -------------------------
  1090. */
  1091. void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  1092. {
  1093. qboolean hitByRancor = qfalse;
  1094. if ( self->NPC && self->NPC->ignorePain )
  1095. {
  1096. return;
  1097. }
  1098. if ( !TIMER_Done( self, "breathAttack" ) )
  1099. {//nothing interrupts breath attack
  1100. return;
  1101. }
  1102. TIMER_Remove( self, "confusionTime" );
  1103. if ( other&&other->client&&other->client->NPC_class==CLASS_RANCOR )
  1104. {
  1105. hitByRancor = qtrue;
  1106. }
  1107. if ( other
  1108. && other->inuse
  1109. && other != self->enemy
  1110. && !(other->flags&FL_NOTARGET) )
  1111. {
  1112. if ( !self->count )
  1113. {
  1114. if ( (!other->s.number&&!Q_irand(0,3))
  1115. || !self->enemy
  1116. || self->enemy->health == 0
  1117. || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR)
  1118. || (!Q_irand(0, 4 ) && DistanceSquared( other->currentOrigin, self->currentOrigin ) < DistanceSquared( self->enemy->currentOrigin, self->currentOrigin )) )
  1119. {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker
  1120. //FIXME: if can't nav to my enemy, take this guy if I can nav to him
  1121. self->lastEnemy = self->enemy;
  1122. G_SetEnemy( self, other );
  1123. if ( self->enemy != self->lastEnemy )
  1124. {//clear this so that we only sniff the player the first time we pick them up
  1125. self->useDebounceTime = 0;
  1126. }
  1127. TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
  1128. if ( hitByRancor )
  1129. {//stay mad at this Rancor for 2-5 secs before looking for other enemies
  1130. TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) );
  1131. }
  1132. }
  1133. }
  1134. }
  1135. 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
  1136. && self->client->ps.legsAnim != BOTH_STAND1TO2
  1137. && TIMER_Done( self, "takingPain" ) )
  1138. {
  1139. if ( !Rancor_CheckRoar( self ) )
  1140. {
  1141. if ( self->client->ps.legsAnim != BOTH_MELEE1
  1142. && self->client->ps.legsAnim != BOTH_MELEE2
  1143. && self->client->ps.legsAnim != BOTH_ATTACK2
  1144. && self->client->ps.legsAnim != BOTH_ATTACK10
  1145. && self->client->ps.legsAnim != BOTH_ATTACK11 )
  1146. {//cant interrupt one of the big attack anims
  1147. /*
  1148. if ( self->count != 1
  1149. || other == self->activator
  1150. || (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) )
  1151. */
  1152. {//if going to bite our victim, only victim can interrupt that anim
  1153. if ( self->health > 100 || hitByRancor )
  1154. {
  1155. TIMER_Remove( self, "attacking" );
  1156. VectorCopy( self->NPC->lastPathAngles, self->s.angles );
  1157. if ( self->count == 1 )
  1158. {
  1159. NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  1160. }
  1161. else
  1162. {
  1163. NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD );
  1164. }
  1165. TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer+Q_irand(0, 500*(2-g_spskill->integer)) );
  1166. if ( self->NPC )
  1167. {
  1168. self->NPC->localState = LSTATE_WAITING;
  1169. }
  1170. }
  1171. }
  1172. }
  1173. }
  1174. //let go
  1175. /*
  1176. if ( !Q_irand( 0, 3 ) && self->count == 1 )
  1177. {
  1178. Rancor_DropVictim( self );
  1179. }
  1180. */
  1181. }
  1182. }
  1183. void Rancor_CheckDropVictim( void )
  1184. {
  1185. if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL)
  1186. && NPC->activator->s.number >= MAX_CLIENTS )
  1187. {
  1188. return;
  1189. }
  1190. vec3_t mins={NPC->activator->mins[0]-1,NPC->activator->mins[1]-1,0};
  1191. vec3_t maxs={NPC->activator->maxs[0]+1,NPC->activator->maxs[1]+1,1};
  1192. vec3_t start={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmin[2]};
  1193. vec3_t end={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmax[2]-1};
  1194. trace_t trace;
  1195. gi.trace( &trace, start, mins, maxs, end, NPC->activator->s.number, NPC->activator->clipmask );
  1196. if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f )
  1197. {
  1198. Rancor_DropVictim( NPC );
  1199. }
  1200. }
  1201. qboolean Rancor_AttackBBrush( void )
  1202. {
  1203. trace_t trace;
  1204. vec3_t center;
  1205. vec3_t dir2Brush, end;
  1206. float checkDist = 64.0f;//32.0f;
  1207. if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) )
  1208. {//no origin brush, calc center
  1209. VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center );
  1210. VectorScale( center, 0.5f, center );
  1211. }
  1212. else
  1213. {
  1214. VectorCopy( NPCInfo->blockedEntity->s.origin, center );
  1215. }
  1216. if ( NAVDEBUG_showCollision )
  1217. {
  1218. CG_DrawEdge( NPC->currentOrigin, center, EDGE_IMPACT_POSSIBLE );
  1219. }
  1220. center[2] = NPC->currentOrigin[2];//we can't fly, so let's ignore z diff
  1221. NPC_FacePosition( center, qfalse );
  1222. //see if we're close to it
  1223. VectorSubtract( center, NPC->currentOrigin, dir2Brush );
  1224. float brushSize = ((NPCInfo->blockedEntity->maxs[0] - NPCInfo->blockedEntity->mins[0])*0.5f+(NPCInfo->blockedEntity->maxs[1] - NPCInfo->blockedEntity->mins[1])*0.5f) * 0.5f;
  1225. float dist2Brush = VectorNormalize( dir2Brush )-(NPC->maxs[0])-brushSize;
  1226. if ( dist2Brush < (MIN_DISTANCE*NPC->s.modelScale[0]) )
  1227. {//close enough to just hit it
  1228. trace.fraction = 0.0f;
  1229. trace.entityNum = NPCInfo->blockedEntity->s.number;
  1230. }
  1231. else
  1232. {
  1233. VectorMA( NPC->currentOrigin, checkDist, dir2Brush, end );
  1234. gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask );
  1235. if ( trace.allsolid || trace.startsolid )
  1236. {//wtf?
  1237. NPCInfo->blockedEntity = NULL;
  1238. return qfalse;
  1239. }
  1240. }
  1241. if ( trace.fraction >= 1.0f //too far away
  1242. || trace.entityNum != NPCInfo->blockedEntity->s.number )//OR blocked by something else
  1243. {//keep moving towards it
  1244. ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal()
  1245. STEER::Activate(NPC);
  1246. STEER::Seek(NPC, center);
  1247. STEER::AvoidCollisions(NPC);
  1248. STEER::DeActivate(NPC, &ucmd);
  1249. /*
  1250. VectorCopy( dir2Brush, NPC->client->ps.moveDir );
  1251. if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed )
  1252. {
  1253. NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
  1254. ucmd.buttons |= BUTTON_WALKING;
  1255. }
  1256. */
  1257. //NPCInfo->enemyLastSeenTime = level.time;
  1258. //let the function that called us know that we called NAV ourselves
  1259. }
  1260. else if ( trace.entityNum == NPCInfo->blockedEntity->s.number )
  1261. {//close enough, smash it!
  1262. Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through?
  1263. TIMER_Remove( NPC, "attackDebounce" );//don't wait on these
  1264. NPCInfo->enemyLastSeenTime = level.time;
  1265. }
  1266. else
  1267. {
  1268. //Com_Printf( S_COLOR_RED"RANCOR cannot reach intended breakable %s, blocked by %s\n", NPC->blockedEntity->targetname, g_entities[trace.entityNum].classname );
  1269. if ( G_EntIsBreakable( trace.entityNum, NPC ) )
  1270. {//oh, well, smash that, then
  1271. //G_SetEnemy( NPC, &g_entities[trace.entityNum] );
  1272. gentity_t* prevblockedEnt = NPCInfo->blockedEntity;
  1273. NPCInfo->blockedEntity = &g_entities[trace.entityNum];
  1274. Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through?
  1275. TIMER_Remove( NPC, "attackDebounce" );//don't wait on these
  1276. NPCInfo->enemyLastSeenTime = level.time;
  1277. NPCInfo->blockedEntity = prevblockedEnt;
  1278. }
  1279. else
  1280. {
  1281. NPCInfo->blockedEntity = NULL;
  1282. return qfalse;
  1283. }
  1284. }
  1285. return qtrue;
  1286. }
  1287. void Rancor_FireBreathAttack( void )
  1288. {
  1289. int damage = Q_irand( 10, 15 );
  1290. trace_t tr;
  1291. gentity_t *traceEnt = NULL;
  1292. mdxaBone_t boltMatrix;
  1293. vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4};
  1294. vec3_t rancAngles = {0,NPC->client->ps.viewangles[YAW],0};
  1295. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->gutBolt,
  1296. &boltMatrix, rancAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
  1297. NULL, NPC->s.modelScale );
  1298. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, start );
  1299. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, dir );
  1300. VectorMA( start, 512, dir, end );
  1301. gi.trace( &tr, start, traceMins, traceMaxs, end, NPC->s.number, MASK_SHOT );
  1302. traceEnt = &g_entities[tr.entityNum];
  1303. if ( tr.entityNum < ENTITYNUM_WORLD
  1304. && traceEnt->takedamage
  1305. && traceEnt->client )
  1306. {//breath attack only does damage to living things
  1307. 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 );
  1308. }
  1309. if ( tr.fraction < 1.0f )
  1310. {//hit something, do radius damage
  1311. G_RadiusDamage( tr.endpos, NPC, damage, 250, NPC, MOD_LAVA );
  1312. }
  1313. }
  1314. void Rancor_CheckAnimDamage( void )
  1315. {
  1316. if ( NPC->client->ps.legsAnim == BOTH_ATTACK2
  1317. || NPC->client->ps.legsAnim == BOTH_ATTACK10
  1318. || NPC->client->ps.legsAnim == BOTH_ATTACK11 )
  1319. {
  1320. if ( NPC->client->ps.legsAnimTimer >= 1200 && NPC->client->ps.legsAnimTimer <= 1350 )
  1321. {
  1322. if ( Q_irand( 0, 2 ) )
  1323. {
  1324. Rancor_Swing( NPC->handRBolt, qfalse );
  1325. }
  1326. else
  1327. {
  1328. Rancor_Swing( NPC->handRBolt, qtrue );
  1329. }
  1330. }
  1331. else if ( NPC->client->ps.legsAnimTimer >= 1100 && NPC->client->ps.legsAnimTimer <= 1550 )
  1332. {
  1333. Rancor_Swing( NPC->handRBolt, qtrue );
  1334. }
  1335. }
  1336. else if ( NPC->client->ps.legsAnim == BOTH_ATTACK5 )
  1337. {
  1338. if ( NPC->client->ps.legsAnimTimer >= 750 && NPC->client->ps.legsAnimTimer <= 1300 )
  1339. {
  1340. Rancor_Swing( NPC->handLBolt, qfalse );
  1341. }
  1342. else if ( NPC->client->ps.legsAnimTimer >= 1700 && NPC->client->ps.legsAnimTimer <= 2300 )
  1343. {
  1344. Rancor_Swing( NPC->handRBolt, qfalse );
  1345. }
  1346. }
  1347. }
  1348. /*
  1349. -------------------------
  1350. NPC_BSRancor_Default
  1351. -------------------------
  1352. */
  1353. void NPC_BSRancor_Default( void )
  1354. {
  1355. AddSightEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, 50 );
  1356. if (NPCInfo->blockedEntity && TIMER_Done(NPC, "blockedEntityIgnore"))
  1357. {
  1358. if (!TIMER_Exists(NPC, "blockedEntityTimeOut"))
  1359. {
  1360. TIMER_Set(NPC, "blockedEntityTimeOut", 5000);
  1361. }
  1362. else if (TIMER_Done(NPC, "blockedEntityTimeOut"))
  1363. {
  1364. TIMER_Remove(NPC, "blockedEntityTimeOut");
  1365. TIMER_Set(NPC, "blockedEntityIgnore", 25000);
  1366. NPCInfo->blockedEntity = NULL;
  1367. }
  1368. }
  1369. else
  1370. {
  1371. TIMER_Remove(NPC, "blockedEntityTimeOut");
  1372. TIMER_Remove(NPC, "blockedEntityIgnore");
  1373. }
  1374. Rancor_CheckAnimDamage();
  1375. if ( !TIMER_Done( NPC, "breathAttack" ) )
  1376. {//doing breath attack, just do damage
  1377. Rancor_FireBreathAttack();
  1378. NPC_UpdateAngles( qtrue, qtrue );
  1379. return;
  1380. }
  1381. else if ( NPC->client->ps.legsAnim == BOTH_ATTACK4
  1382. || NPC->client->ps.legsAnim == BOTH_ATTACK6
  1383. || NPC->client->ps.legsAnim == BOTH_ATTACK7 )
  1384. {
  1385. G_StopEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number );
  1386. NPC->s.loopSound = 0;
  1387. }
  1388. if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) )
  1389. {
  1390. Rancor_DropVictim( NPC );
  1391. }
  1392. else if ( (NPC->client->ps.legsAnim == BOTH_PAIN2 || NPC->client->ps.legsAnim == BOTH_HOLD_DROP )
  1393. && NPC->count == 1
  1394. && NPC->activator )
  1395. {
  1396. Rancor_CheckDropVictim();
  1397. }
  1398. if ( !TIMER_Done( NPC, "rageTime" ) )
  1399. {//do nothing but roar first time we see an enemy
  1400. AddSoundEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, qfalse, qfalse );
  1401. NPC_FaceEnemy( qtrue );
  1402. return;
  1403. }
  1404. if ( NPCInfo->localState == LSTATE_WAITING
  1405. && TIMER_Done2( NPC, "takingPain", qtrue ) )
  1406. {//was not doing anything because we were taking pain, but pain is done now, so clear it...
  1407. NPCInfo->localState = LSTATE_CLEAR;
  1408. }
  1409. if ( !TIMER_Done( NPC, "confusionTime" ) )
  1410. {
  1411. NPC_UpdateAngles( qtrue, qtrue );
  1412. return;
  1413. }
  1414. if ( NPC->enemy )
  1415. {
  1416. if ( NPC->enemy->client //enemy is a client
  1417. && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught
  1418. && NPC->enemy->enemy != NPC//enemy's enemy is not me
  1419. && (!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)
  1420. {//they should be scared of ME and no-one else
  1421. G_SetEnemy( NPC->enemy, NPC );
  1422. }
  1423. if ( TIMER_Done(NPC,"angrynoise") )
  1424. {
  1425. G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/anger%d.wav", Q_irand(1, 3)) );
  1426. TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) );
  1427. }
  1428. else
  1429. {
  1430. AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER_GREAT, qfalse, qfalse );
  1431. }
  1432. if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 )
  1433. {//we're still chewing our enemy up
  1434. NPC_UpdateAngles( qtrue, qtrue );
  1435. return;
  1436. }
  1437. //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
  1438. if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR )
  1439. {//got mad at another Rancor, look for a valid enemy
  1440. if ( TIMER_Done( NPC, "rancorInfight" ) )
  1441. {
  1442. NPC_CheckEnemyExt( qtrue );
  1443. }
  1444. }
  1445. else if ( !NPC->count )
  1446. {
  1447. if ( NPCInfo->blockedEntity )
  1448. {//something in our way
  1449. if ( !NPCInfo->blockedEntity->inuse )
  1450. {//was destroyed
  1451. NPCInfo->blockedEntity = NULL;
  1452. }
  1453. else
  1454. {
  1455. //a breakable?
  1456. if ( G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) )
  1457. {//breakable brush
  1458. if ( !Rancor_AttackBBrush() )
  1459. {//didn't move inside that func, so call move here...?
  1460. Rancor_Move( 1 );
  1461. }
  1462. NPC_UpdateAngles( qtrue, qtrue );
  1463. return;
  1464. }
  1465. else
  1466. {//if it's a client and in our way, get mad at it!
  1467. if ( NPCInfo->blockedEntity != NPC->enemy
  1468. && NPCInfo->blockedEntity->client
  1469. && NPC_ValidEnemy( NPCInfo->blockedEntity )
  1470. && !Q_irand( 0, 9 ) )
  1471. {
  1472. G_SetEnemy( NPC, NPCInfo->blockedEntity );
  1473. //look again in 2-5 secs
  1474. TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
  1475. NPCInfo->blockedEntity = NULL;
  1476. }
  1477. }
  1478. }
  1479. }
  1480. if ( NPC_ValidEnemy( NPC->enemy ) == qfalse )
  1481. {
  1482. TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now
  1483. if ( !NPC->enemy->inuse
  1484. || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 )
  1485. || (NPC->spawnflags&SPF_RANCOR_FASTKILL) )//don't linger on dead bodies
  1486. {//it's been a while since the enemy died, or enemy is completely gone, get bored with him
  1487. if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
  1488. && player && player->health >= 0 )
  1489. {//all else failing, always go after the player
  1490. NPC->lastEnemy = NPC->enemy;
  1491. G_SetEnemy( NPC, player );
  1492. if ( NPC->enemy != NPC->lastEnemy )
  1493. {//clear this so that we only sniff the player the first time we pick them up
  1494. NPC->useDebounceTime = 0;
  1495. }
  1496. }
  1497. else
  1498. {
  1499. NPC->enemy = NULL;
  1500. Rancor_Patrol();
  1501. NPC_UpdateAngles( qtrue, qtrue );
  1502. return;
  1503. }
  1504. }
  1505. }
  1506. if ( TIMER_Done( NPC, "lookForNewEnemy" ) )
  1507. {
  1508. gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy?
  1509. NPC->enemy = NULL;
  1510. gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse );
  1511. NPC->enemy = sav_enemy;
  1512. if ( newEnemy && newEnemy != sav_enemy )
  1513. {//picked up a new enemy!
  1514. NPC->lastEnemy = NPC->enemy;
  1515. G_SetEnemy( NPC, newEnemy );
  1516. if ( NPC->enemy != NPC->lastEnemy )
  1517. {//clear this so that we only sniff the player the first time we pick them up
  1518. NPC->useDebounceTime = 0;
  1519. }
  1520. //hold this one for at least 5-15 seconds
  1521. TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) );
  1522. }
  1523. else
  1524. {//look again in 2-5 secs
  1525. TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) );
  1526. }
  1527. }
  1528. }
  1529. Rancor_Combat();
  1530. if ( TIMER_Done( NPC, "attacking" )
  1531. && TIMER_Done( NPC, "takingpain" )
  1532. && TIMER_Done( NPC, "confusionDebounce" )
  1533. && NPCInfo->localState == LSTATE_CLEAR
  1534. && !NPC->count )
  1535. {//not busy
  1536. if ( !ucmd.forwardmove
  1537. && !ucmd.rightmove
  1538. && VectorCompare( NPC->client->ps.moveDir, vec3_origin ) )
  1539. {//not moving
  1540. if ( level.time - NPCInfo->enemyLastSeenTime > 5000 )
  1541. {//haven't seen an enemy in a while
  1542. if ( !Q_irand( 0, 20 ) )
  1543. {
  1544. if ( Q_irand( 0, 1 ) )
  1545. {
  1546. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  1547. }
  1548. else
  1549. {
  1550. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  1551. }
  1552. TIMER_Set( NPC, "confusionTime", NPC->client->ps.legsAnimTimer );
  1553. TIMER_Set( NPC, "confusionDebounce", NPC->client->ps.legsAnimTimer+Q_irand( 4000, 8000 ) );
  1554. }
  1555. }
  1556. }
  1557. }
  1558. }
  1559. else
  1560. {
  1561. if ( TIMER_Done(NPC,"idlenoise") )
  1562. {
  1563. G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 4)) );
  1564. TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) );
  1565. AddSoundEvent( NPC, NPC->currentOrigin, 384, AEL_DANGER, qfalse, qfalse );
  1566. }
  1567. if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
  1568. {
  1569. Rancor_Patrol();
  1570. if ( !NPC->enemy && NPC->wait )
  1571. {//we've been mad before and can't find an enemy
  1572. if ( (NPC->spawnflags&SPF_RANCOR_MUTANT)
  1573. && player && player->health >= 0 )
  1574. {//all else failing, always go after the player
  1575. NPC->lastEnemy = NPC->enemy;
  1576. G_SetEnemy( NPC, player );
  1577. if ( NPC->enemy != NPC->lastEnemy )
  1578. {//clear this so that we only sniff the player the first time we pick them up
  1579. NPC->useDebounceTime = 0;
  1580. }
  1581. }
  1582. }
  1583. }
  1584. else
  1585. {
  1586. Rancor_Idle();
  1587. }
  1588. }
  1589. NPC_UpdateAngles( qtrue, qtrue );
  1590. }