AI_Stormtrooper.cpp 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723
  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. #include "g_nav.h"
  5. #include "anims.h"
  6. #include "g_navigator.h"
  7. extern void CG_DrawAlert( vec3_t origin, float rating );
  8. extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
  9. extern void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState );
  10. extern qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum );
  11. extern void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot );
  12. extern void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group );
  13. extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
  14. extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
  15. extern void ChangeWeapon( gentity_t *ent, int newWeapon );
  16. extern void NPC_CheckGetNewWeapon( void );
  17. extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
  18. extern int GetTime ( int lastTime );
  19. extern void NPC_AimAdjust( int change );
  20. extern qboolean FlyingCreature( gentity_t *ent );
  21. extern void NPC_EvasionSaber( void );
  22. extern qboolean RT_Flying( gentity_t *self );
  23. //extern CNavigator navigator;
  24. extern cvar_t *d_asynchronousGroupAI;
  25. #define MAX_VIEW_DIST 1024
  26. #define MAX_VIEW_SPEED 250
  27. #define MAX_LIGHT_INTENSITY 255
  28. #define MIN_LIGHT_THRESHOLD 0.1
  29. #define ST_MIN_LIGHT_THRESHOLD 30
  30. #define ST_MAX_LIGHT_THRESHOLD 180
  31. #define DISTANCE_THRESHOLD 0.075f
  32. #define MIN_TURN_AROUND_DIST_SQ (10000) //(100 squared) don't stop running backwards if your goal is less than 100 away
  33. #define SABER_AVOID_DIST 128.0f//256.0f
  34. #define SABER_AVOID_DIST_SQ (SABER_AVOID_DIST*SABER_AVOID_DIST)
  35. #define DISTANCE_SCALE 0.35f //These first three get your base detection rating, ideally add up to 1
  36. #define FOV_SCALE 0.40f //
  37. #define LIGHT_SCALE 0.25f //
  38. #define SPEED_SCALE 0.25f //These next two are bonuses
  39. #define TURNING_SCALE 0.25f //
  40. #define REALIZE_THRESHOLD 0.6f
  41. #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 )
  42. qboolean NPC_CheckPlayerTeamStealth( void );
  43. static qboolean enemyLOS;
  44. static qboolean enemyCS;
  45. static qboolean enemyInFOV;
  46. static qboolean hitAlly;
  47. static qboolean faceEnemy;
  48. static qboolean move;
  49. static qboolean shoot;
  50. static float enemyDist;
  51. static vec3_t impactPos;
  52. int groupSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several group AI from speaking all at once
  53. void NPC_Saboteur_Precache( void )
  54. {
  55. G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" );
  56. G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" );
  57. }
  58. void Saboteur_Decloak( gentity_t *self, int uncloakTime )
  59. {
  60. if ( self && self->client )
  61. {
  62. if ( self->client->ps.powerups[PW_CLOAKED] && TIMER_Done(self, "decloakwait"))
  63. {//Uncloak
  64. self->client->ps.powerups[PW_CLOAKED] = 0;
  65. self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000;
  66. //FIXME: temp sound
  67. G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/decloak.wav" );
  68. TIMER_Set( self, "nocloak", uncloakTime );
  69. // Can't Recloak
  70. //self->NPC->aiFlags &= ~NPCAI_SHIELDS;
  71. }
  72. }
  73. }
  74. void Saboteur_Cloak( gentity_t *self )
  75. {
  76. if ( self && self->client && self->NPC )
  77. {//FIXME: need to have this timer set once first?
  78. if ( TIMER_Done( self, "nocloak" ) )
  79. {//not sitting around waiting to cloak again
  80. if ( !(self->NPC->aiFlags&NPCAI_SHIELDS) )
  81. {//not allowed to cloak, actually
  82. Saboteur_Decloak( self );
  83. }
  84. else if ( !self->client->ps.powerups[PW_CLOAKED] )
  85. {//cloak
  86. self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE;
  87. self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000;
  88. //FIXME: debounce attacks?
  89. //FIXME: temp sound
  90. G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/cloak.wav" );
  91. }
  92. }
  93. }
  94. }
  95. //Local state enums
  96. enum
  97. {
  98. LSTATE_NONE = 0,
  99. LSTATE_UNDERFIRE,
  100. LSTATE_INVESTIGATE,
  101. };
  102. void ST_AggressionAdjust( gentity_t *self, int change )
  103. {
  104. int upper_threshold, lower_threshold;
  105. self->NPC->stats.aggression += change;
  106. //FIXME: base this on initial NPC stats
  107. if ( self->client->playerTeam == TEAM_PLAYER )
  108. {//good guys are less aggressive
  109. upper_threshold = 7;
  110. lower_threshold = 1;
  111. }
  112. else
  113. {//bad guys are more aggressive
  114. upper_threshold = 10;
  115. lower_threshold = 3;
  116. }
  117. if ( self->NPC->stats.aggression > upper_threshold )
  118. {
  119. self->NPC->stats.aggression = upper_threshold;
  120. }
  121. else if ( self->NPC->stats.aggression < lower_threshold )
  122. {
  123. self->NPC->stats.aggression = lower_threshold;
  124. }
  125. }
  126. void ST_ClearTimers( gentity_t *ent )
  127. {
  128. TIMER_Set( ent, "chatter", 0 );
  129. TIMER_Set( ent, "duck", 0 );
  130. TIMER_Set( ent, "stand", 0 );
  131. TIMER_Set( ent, "shuffleTime", 0 );
  132. TIMER_Set( ent, "sleepTime", 0 );
  133. TIMER_Set( ent, "enemyLastVisible", 0 );
  134. TIMER_Set( ent, "roamTime", 0 );
  135. TIMER_Set( ent, "hideTime", 0 );
  136. TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels
  137. TIMER_Set( ent, "stick", 0 );
  138. TIMER_Set( ent, "scoutTime", 0 );
  139. TIMER_Set( ent, "flee", 0 );
  140. TIMER_Set( ent, "interrogating", 0 );
  141. TIMER_Set( ent, "verifyCP", 0 );
  142. TIMER_Set( ent, "strafeRight", 0 );
  143. TIMER_Set( ent, "strafeLeft", 0 );
  144. }
  145. enum
  146. {
  147. SPEECH_CHASE,
  148. SPEECH_CONFUSED,
  149. SPEECH_COVER,
  150. SPEECH_DETECTED,
  151. SPEECH_GIVEUP,
  152. SPEECH_LOOK,
  153. SPEECH_LOST,
  154. SPEECH_OUTFLANK,
  155. SPEECH_ESCAPING,
  156. SPEECH_SIGHT,
  157. SPEECH_SOUND,
  158. SPEECH_SUSPICIOUS,
  159. SPEECH_YELL,
  160. SPEECH_PUSHED
  161. };
  162. static void ST_Speech( gentity_t *self, int speechType, float failChance )
  163. {
  164. if ( random() < failChance )
  165. {
  166. return;
  167. }
  168. if ( failChance >= 0 )
  169. {//a negative failChance makes it always talk
  170. if ( self->NPC->group )
  171. {//group AI speech debounce timer
  172. if ( self->NPC->group->speechDebounceTime > level.time )
  173. {
  174. return;
  175. }
  176. /*
  177. else if ( !self->NPC->group->enemy )
  178. {
  179. if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time )
  180. {
  181. return;
  182. }
  183. }
  184. */
  185. }
  186. else if ( !TIMER_Done( self, "chatter" ) )
  187. {//personal timer
  188. return;
  189. }
  190. else if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time )
  191. {//for those not in group AI
  192. //FIXME: let certain speech types interrupt others? Let closer NPCs interrupt farther away ones?
  193. return;
  194. }
  195. }
  196. if ( self->NPC->group )
  197. {//So they don't all speak at once...
  198. //FIXME: if they're not yet mad, they have no group, so distracting a group of them makes them all speak!
  199. self->NPC->group->speechDebounceTime = level.time + Q_irand( 2000, 4000 );
  200. }
  201. else
  202. {
  203. TIMER_Set( self, "chatter", Q_irand( 2000, 4000 ) );
  204. }
  205. groupSpeechDebounceTime[self->client->playerTeam] = level.time + Q_irand( 2000, 4000 );
  206. if ( self->NPC->blockedSpeechDebounceTime > level.time )
  207. {
  208. return;
  209. }
  210. switch( speechType )
  211. {
  212. case SPEECH_CHASE:
  213. G_AddVoiceEvent( self, Q_irand(EV_CHASE1, EV_CHASE3), 2000 );
  214. break;
  215. case SPEECH_CONFUSED:
  216. G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
  217. break;
  218. case SPEECH_COVER:
  219. G_AddVoiceEvent( self, Q_irand(EV_COVER1, EV_COVER5), 2000 );
  220. break;
  221. case SPEECH_DETECTED:
  222. G_AddVoiceEvent( self, Q_irand(EV_DETECTED1, EV_DETECTED5), 2000 );
  223. break;
  224. case SPEECH_GIVEUP:
  225. G_AddVoiceEvent( self, Q_irand(EV_GIVEUP1, EV_GIVEUP4), 2000 );
  226. break;
  227. case SPEECH_LOOK:
  228. G_AddVoiceEvent( self, Q_irand(EV_LOOK1, EV_LOOK2), 2000 );
  229. break;
  230. case SPEECH_LOST:
  231. G_AddVoiceEvent( self, EV_LOST1, 2000 );
  232. break;
  233. case SPEECH_OUTFLANK:
  234. G_AddVoiceEvent( self, Q_irand(EV_OUTFLANK1, EV_OUTFLANK2), 2000 );
  235. break;
  236. case SPEECH_ESCAPING:
  237. G_AddVoiceEvent( self, Q_irand(EV_ESCAPING1, EV_ESCAPING3), 2000 );
  238. break;
  239. case SPEECH_SIGHT:
  240. G_AddVoiceEvent( self, Q_irand(EV_SIGHT1, EV_SIGHT3), 2000 );
  241. break;
  242. case SPEECH_SOUND:
  243. G_AddVoiceEvent( self, Q_irand(EV_SOUND1, EV_SOUND3), 2000 );
  244. break;
  245. case SPEECH_SUSPICIOUS:
  246. G_AddVoiceEvent( self, Q_irand(EV_SUSPICIOUS1, EV_SUSPICIOUS5), 2000 );
  247. break;
  248. case SPEECH_YELL:
  249. G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 2000 );
  250. break;
  251. case SPEECH_PUSHED:
  252. G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 );
  253. break;
  254. default:
  255. break;
  256. }
  257. self->NPC->blockedSpeechDebounceTime = level.time + 2000;
  258. }
  259. void ST_MarkToCover( gentity_t *self )
  260. {
  261. if ( !self || !self->NPC )
  262. {
  263. return;
  264. }
  265. self->NPC->localState = LSTATE_UNDERFIRE;
  266. TIMER_Set( self, "attackDelay", Q_irand( 500, 2500 ) );
  267. ST_AggressionAdjust( self, -3 );
  268. if ( self->NPC->group && self->NPC->group->numGroup > 1 )
  269. {
  270. ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound?
  271. }
  272. }
  273. void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime )
  274. {
  275. if ( !self || !self->NPC )
  276. {
  277. return;
  278. }
  279. G_StartFlee( self, enemy, dangerPoint, dangerLevel, minTime, maxTime );
  280. if ( self->NPC->group && self->NPC->group->numGroup > 1 )
  281. {
  282. ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound?
  283. }
  284. }
  285. /*
  286. -------------------------
  287. NPC_ST_Pain
  288. -------------------------
  289. */
  290. void NPC_ST_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  291. {
  292. self->NPC->localState = LSTATE_UNDERFIRE;
  293. TIMER_Set( self, "duck", -1 );
  294. TIMER_Set( self, "hideTime", -1 );
  295. TIMER_Set( self, "stand", 2000 );
  296. NPC_Pain( self, inflictor, other, point, damage, mod, hitLoc );
  297. if ( !damage && self->health > 0 )
  298. {//FIXME: better way to know I was pushed
  299. G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
  300. }
  301. }
  302. /*
  303. -------------------------
  304. ST_HoldPosition
  305. -------------------------
  306. */
  307. static void ST_HoldPosition( void )
  308. {
  309. if ( NPCInfo->squadState == SQUAD_RETREAT )
  310. {
  311. TIMER_Set( NPC, "flee", -level.time );
  312. }
  313. TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't look for another one for a few seconds
  314. NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
  315. //NPCInfo->combatPoint = -1;//???
  316. if ( !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) )
  317. {//don't have a script waiting for me to get to my point, okay to stop trying and stand
  318. AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
  319. NPCInfo->goalEntity = NULL;
  320. }
  321. }
  322. void NPC_ST_SayMovementSpeech( void )
  323. {
  324. if ( !NPCInfo->movementSpeech )
  325. {
  326. return;
  327. }
  328. if ( NPCInfo->group &&
  329. NPCInfo->group->commander &&
  330. NPCInfo->group->commander->client &&
  331. NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL &&
  332. !Q_irand( 0, 3 ) )
  333. {//imperial (commander) gives the order
  334. ST_Speech( NPCInfo->group->commander, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance );
  335. }
  336. else
  337. {//really don't want to say this unless we can actually get there...
  338. ST_Speech( NPC, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance );
  339. }
  340. NPCInfo->movementSpeech = 0;
  341. NPCInfo->movementSpeechChance = 0.0f;
  342. }
  343. void NPC_ST_StoreMovementSpeech( int speech, float chance )
  344. {
  345. NPCInfo->movementSpeech = speech;
  346. NPCInfo->movementSpeechChance = chance;
  347. }
  348. /*
  349. -------------------------
  350. ST_Move
  351. -------------------------
  352. */
  353. void ST_TransferMoveGoal( gentity_t *self, gentity_t *other );
  354. static qboolean ST_Move( void )
  355. {
  356. NPCInfo->combatMove = qtrue;//always move straight toward our goal
  357. qboolean moved = NPC_MoveToGoal( qtrue );
  358. if (moved==qfalse)
  359. {
  360. ST_HoldPosition();
  361. }
  362. NPC_ST_SayMovementSpeech();
  363. return moved;
  364. }
  365. /*
  366. -------------------------
  367. NPC_ST_SleepShuffle
  368. -------------------------
  369. */
  370. static void NPC_ST_SleepShuffle( void )
  371. {
  372. //Play an awake script if we have one
  373. if ( G_ActivateBehavior( NPC, BSET_AWAKE) )
  374. {
  375. return;
  376. }
  377. //Automate some movement and noise
  378. if ( TIMER_Done( NPC, "shuffleTime" ) )
  379. {
  380. //TODO: Play sleeping shuffle animation
  381. //int soundIndex = Q_irand( 0, 1 );
  382. /*
  383. switch ( soundIndex )
  384. {
  385. case 0:
  386. G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper1/scav4/hunh.mp3") );
  387. break;
  388. case 1:
  389. G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper3/scav4/tryingtosleep.wav") );
  390. break;
  391. }
  392. */
  393. TIMER_Set( NPC, "shuffleTime", 4000 );
  394. TIMER_Set( NPC, "sleepTime", 2000 );
  395. return;
  396. }
  397. //They made another noise while we were stirring, see if we can see them
  398. if ( TIMER_Done( NPC, "sleepTime" ) )
  399. {
  400. NPC_CheckPlayerTeamStealth();
  401. TIMER_Set( NPC, "sleepTime", 2000 );
  402. }
  403. }
  404. /*
  405. -------------------------
  406. NPC_ST_Sleep
  407. -------------------------
  408. */
  409. void NPC_BSST_Sleep( void )
  410. {
  411. int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue );//only check sounds since we're alseep!
  412. //There is an event we heard
  413. if ( alertEvent >= 0 )
  414. {
  415. //See if it was enough to wake us up
  416. if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
  417. {
  418. if ( &g_entities[0] && g_entities[0].health > 0 )
  419. {
  420. G_SetEnemy( NPC, &g_entities[0] );
  421. return;
  422. }
  423. }
  424. //Otherwise just stir a bit
  425. NPC_ST_SleepShuffle();
  426. return;
  427. }
  428. }
  429. /*
  430. -------------------------
  431. NPC_CheckEnemyStealth
  432. -------------------------
  433. */
  434. qboolean NPC_CheckEnemyStealth( gentity_t *target )
  435. {
  436. float target_dist, minDist = 40;//any closer than 40 and we definitely notice
  437. //In case we aquired one some other way
  438. if ( NPC->enemy != NULL )
  439. return qtrue;
  440. //Ignore notarget
  441. if ( target->flags & FL_NOTARGET )
  442. return qfalse;
  443. if ( target->health <= 0 )
  444. {
  445. return qfalse;
  446. }
  447. if ( target->client->ps.weapon == WP_SABER && target->client->ps.SaberActive() && !target->client->ps.saberInFlight )
  448. {//if target has saber in hand and activated, we wake up even sooner even if not facing him
  449. minDist = 100;
  450. }
  451. target_dist = DistanceSquared( target->currentOrigin, NPC->currentOrigin );
  452. //If the target is this close, then wake up regardless
  453. if ( !(target->client->ps.pm_flags&PMF_DUCKED)//not ducking
  454. && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES)//looking for enemies
  455. && target_dist < (minDist*minDist) )//closer than minDist
  456. {
  457. G_SetEnemy( NPC, target );
  458. NPCInfo->enemyLastSeenTime = level.time;
  459. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  460. return qtrue;
  461. }
  462. float maxViewDist = MAX_VIEW_DIST;
  463. // if ( NPCInfo->stats.visrange > maxViewDist )
  464. {//FIXME: should we always just set maxViewDist to this?
  465. maxViewDist = NPCInfo->stats.visrange;
  466. }
  467. if ( target_dist > (maxViewDist*maxViewDist) )
  468. {//out of possible visRange
  469. return qfalse;
  470. }
  471. //Check FOV first
  472. if ( InFOV( target, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
  473. return qfalse;
  474. qboolean clearLOS = ( target->client->ps.leanofs ) ? NPC_ClearLOS( target->client->renderInfo.eyePoint ) : NPC_ClearLOS( target );
  475. //Now check for clear line of vision
  476. if ( clearLOS )
  477. {
  478. if ( target->client->NPC_class == CLASS_ATST )
  479. {//can't miss 'em!
  480. G_SetEnemy( NPC, target );
  481. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  482. return qtrue;
  483. }
  484. vec3_t targ_org = {target->currentOrigin[0],target->currentOrigin[1],target->currentOrigin[2]+target->maxs[2]-4};
  485. float hAngle_perc = NPC_GetHFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov );
  486. float vAngle_perc = NPC_GetVFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.vfov );
  487. //Scale them vertically some, and horizontally pretty harshly
  488. vAngle_perc *= vAngle_perc;//( vAngle_perc * vAngle_perc );
  489. hAngle_perc *= ( hAngle_perc * hAngle_perc );
  490. //Cap our vertical vision severely
  491. //if ( vAngle_perc <= 0.3f ) // was 0.5f
  492. // return qfalse;
  493. //Assess the player's current status
  494. target_dist = Distance( target->currentOrigin, NPC->currentOrigin );
  495. float target_speed = VectorLength( target->client->ps.velocity );
  496. int target_crouching = ( target->client->usercmd.upmove < 0 );
  497. float dist_rating = ( target_dist / maxViewDist );
  498. float speed_rating = ( target_speed / MAX_VIEW_SPEED );
  499. float turning_rating = AngleDelta( target->client->ps.viewangles[PITCH], target->lastAngles[PITCH] )/180.0f + AngleDelta( target->client->ps.viewangles[YAW], target->lastAngles[YAW] )/180.0f;
  500. float light_level = ( target->lightLevel / MAX_LIGHT_INTENSITY );
  501. float FOV_perc = 1.0f - ( hAngle_perc + vAngle_perc ) * 0.5f; //FIXME: Dunno about the average...
  502. float vis_rating = 0.0f;
  503. //Too dark
  504. if ( light_level < MIN_LIGHT_THRESHOLD )
  505. return qfalse;
  506. //Too close?
  507. if ( dist_rating < DISTANCE_THRESHOLD )
  508. {
  509. G_SetEnemy( NPC, target );
  510. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  511. return qtrue;
  512. }
  513. //Out of range
  514. if ( dist_rating > 1.0f )
  515. return qfalse;
  516. //Cap our speed checks
  517. if ( speed_rating > 1.0f )
  518. speed_rating = 1.0f;
  519. //Calculate the distance, fov and light influences
  520. //...Visibilty linearly wanes over distance
  521. float dist_influence = DISTANCE_SCALE * ( ( 1.0f - dist_rating ) );
  522. //...As the percentage out of the FOV increases, straight perception suffers on an exponential scale
  523. float fov_influence = FOV_SCALE * ( 1.0f - FOV_perc );
  524. //...Lack of light hides, abundance of light exposes
  525. float light_influence = ( light_level - 0.5f ) * LIGHT_SCALE;
  526. //Calculate our base rating
  527. float target_rating = dist_influence + fov_influence + light_influence;
  528. //Now award any final bonuses to this number
  529. int contents = gi.pointcontents( targ_org, target->s.number );
  530. if ( contents&CONTENTS_WATER )
  531. {
  532. int myContents = gi.pointcontents( NPC->client->renderInfo.eyePoint, NPC->s.number );
  533. if ( !(myContents&CONTENTS_WATER) )
  534. {//I'm not in water
  535. if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
  536. {//these guys can see in in/through water pretty well
  537. vis_rating = 0.10f;//10% bonus
  538. }
  539. else
  540. {
  541. vis_rating = 0.35f;//35% bonus
  542. }
  543. }
  544. else
  545. {//else, if we're both in water
  546. if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
  547. {//I can see him just fine
  548. }
  549. else
  550. {
  551. vis_rating = 0.15f;//15% bonus
  552. }
  553. }
  554. }
  555. else
  556. {//not in water
  557. if ( contents&CONTENTS_FOG )
  558. {
  559. vis_rating = 0.15f;//15% bonus
  560. }
  561. }
  562. target_rating *= (1.0f - vis_rating);
  563. //...Motion draws the eye quickly
  564. target_rating += speed_rating * SPEED_SCALE;
  565. target_rating += turning_rating * TURNING_SCALE;
  566. //FIXME: check to see if they're animating, too? But can we do something as simple as frame != oldframe?
  567. //...Smaller targets are harder to indentify
  568. if ( target_crouching )
  569. {
  570. target_rating *= 0.9f; //10% bonus
  571. }
  572. //If he's violated the threshold, then realize him
  573. //float difficulty_scale = 1.0f + (2.0f-g_spskill->value);//if playing on easy, 20% harder to be seen...?
  574. float realize, cautious;
  575. if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
  576. {//swamptroopers can see much better
  577. realize = (float)CAUTIOUS_THRESHOLD/**difficulty_scale*/;
  578. cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/;
  579. }
  580. else
  581. {
  582. realize = (float)REALIZE_THRESHOLD/**difficulty_scale*/;
  583. cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/;
  584. }
  585. if ( target_rating > realize && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
  586. {
  587. G_SetEnemy( NPC, target );
  588. NPCInfo->enemyLastSeenTime = level.time;
  589. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  590. return qtrue;
  591. }
  592. //If he's above the caution threshold, then realize him in a few seconds unless he moves to cover
  593. if ( target_rating > cautious && !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
  594. {//FIXME: ambushing guys should never talk
  595. if ( TIMER_Done( NPC, "enemyLastVisible" ) )
  596. {//If we haven't already, start the counter
  597. int lookTime = Q_irand( 4500, 8500 );
  598. //NPCInfo->timeEnemyLastVisible = level.time + 2000;
  599. TIMER_Set( NPC, "enemyLastVisible", lookTime );
  600. //TODO: Play a sound along the lines of, "Huh? What was that?"
  601. ST_Speech( NPC, SPEECH_SIGHT, 0 );
  602. NPC_TempLookTarget( NPC, target->s.number, lookTime, lookTime );
  603. //FIXME: set desired yaw and pitch towards this guy?
  604. }
  605. else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) //FIXME: Is this reliable?
  606. {
  607. if ( NPCInfo->rank < RANK_LT && !Q_irand( 0, 2 ) )
  608. {
  609. int interrogateTime = Q_irand( 2000, 4000 );
  610. ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 );
  611. TIMER_Set( NPC, "interrogating", interrogateTime );
  612. G_SetEnemy( NPC, target );
  613. NPCInfo->enemyLastSeenTime = level.time;
  614. TIMER_Set( NPC, "attackDelay", interrogateTime );
  615. TIMER_Set( NPC, "stand", interrogateTime );
  616. }
  617. else
  618. {
  619. G_SetEnemy( NPC, target );
  620. NPCInfo->enemyLastSeenTime = level.time;
  621. //FIXME: ambush guys (like those popping out of water) shouldn't delay...
  622. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  623. TIMER_Set( NPC, "stand", Q_irand( 500, 2500 ) );
  624. }
  625. return qtrue;
  626. }
  627. return qfalse;
  628. }
  629. }
  630. return qfalse;
  631. }
  632. qboolean NPC_CheckPlayerTeamStealth( void )
  633. {
  634. /*
  635. //NOTENOTE: For now, all stealh checks go against the player, since
  636. // he is the main focus. Squad members and rivals do not
  637. // fall into this category and will be ignored.
  638. NPC_CheckEnemyStealth( &g_entities[0] ); //Change this pointer to assess other entities
  639. */
  640. gentity_t *enemy;
  641. for ( int i = 0; i < ENTITYNUM_WORLD; i++ )
  642. {
  643. if(!PInUse(i))
  644. continue;
  645. enemy = &g_entities[i];
  646. if ( enemy
  647. && enemy->client
  648. && NPC_ValidEnemy( enemy ) )
  649. {
  650. if ( NPC_CheckEnemyStealth( enemy ) ) //Change this pointer to assess other entities
  651. {
  652. return qtrue;
  653. }
  654. }
  655. }
  656. return qfalse;
  657. }
  658. qboolean NPC_CheckEnemiesInSpotlight( void )
  659. {
  660. gentity_t *entityList[MAX_GENTITIES];
  661. gentity_t *enemy, *suspect = NULL;
  662. int i, numListedEntities;
  663. vec3_t mins, maxs;
  664. for ( i = 0 ; i < 3 ; i++ )
  665. {
  666. mins[i] = NPC->client->renderInfo.eyePoint[i] - NPC->speed;
  667. maxs[i] = NPC->client->renderInfo.eyePoint[i] + NPC->speed;
  668. }
  669. numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
  670. for ( i = 0; i < numListedEntities; i++ )
  671. {
  672. if(!PInUse(i))
  673. continue;
  674. enemy = entityList[i];
  675. if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam )
  676. {//valid ent & client, valid enemy, on the target team
  677. //check to see if they're in my FOV
  678. if ( InFOV( enemy->currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) )
  679. {//in my cone
  680. //check to see that they're close enough
  681. if ( DistanceSquared( NPC->client->renderInfo.eyePoint, enemy->currentOrigin )-256/*fudge factor: 16 squared*/ <= NPC->speed*NPC->speed )
  682. {//within range
  683. //check to see if we have a clear trace to them
  684. if ( G_ClearLOS( NPC, enemy ) )
  685. {//clear LOS
  686. //make sure their light level is at least my beam's brightness
  687. //FIXME: HOW?
  688. //enemy->lightLevel / MAX_LIGHT_INTENSITY
  689. //good enough, take him!
  690. //FIXME: pick closest one?
  691. //FIXME: have the graduated noticing like other NPCs? (based on distance, FOV dot, etc...)
  692. G_SetEnemy( NPC, enemy );
  693. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  694. return qtrue;
  695. }
  696. }
  697. }
  698. if ( InFOV( enemy->currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, 90, NPCInfo->stats.vfov*3 ) )
  699. {//one to look at if we don't get an enemy
  700. if ( G_ClearLOS( NPC, enemy ) )
  701. {//clear LOS
  702. if ( suspect == NULL || DistanceSquared( NPC->client->renderInfo.eyePoint, enemy->currentOrigin ) < DistanceSquared( NPC->client->renderInfo.eyePoint, suspect->currentOrigin ) )
  703. {//remember him
  704. suspect = enemy;
  705. }
  706. }
  707. }
  708. }
  709. }
  710. if ( suspect && Q_flrand( 0, NPCInfo->stats.visrange*NPCInfo->stats.visrange ) > DistanceSquared( NPC->client->renderInfo.eyePoint, suspect->currentOrigin ) )
  711. {//hey! who's that?
  712. if ( TIMER_Done( NPC, "enemyLastVisible" ) )
  713. {//If we haven't already, start the counter
  714. int lookTime = Q_irand( 4500, 8500 );
  715. //NPCInfo->timeEnemyLastVisible = level.time + 2000;
  716. TIMER_Set( NPC, "enemyLastVisible", lookTime );
  717. //TODO: Play a sound along the lines of, "Huh? What was that?"
  718. ST_Speech( NPC, SPEECH_SIGHT, 0 );
  719. //set desired yaw and pitch towards this guy?
  720. //FIXME: this is permanent, they will never look away... *sigh*
  721. NPC_FacePosition( suspect->currentOrigin, qtrue );
  722. //FIXME: they still need some sort of eye/head tag/bone that can turn?
  723. //NPC_TempLookTarget( NPC, suspect->s.number, lookTime, lookTime );
  724. }
  725. else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500
  726. && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) //FIXME: Is this reliable?
  727. {
  728. if ( !Q_irand( 0, 2 ) )
  729. {
  730. int interrogateTime = Q_irand( 2000, 4000 );
  731. ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 );
  732. TIMER_Set( NPC, "interrogating", interrogateTime );
  733. //G_SetEnemy( NPC, target );
  734. //NPCInfo->enemyLastSeenTime = level.time;
  735. //TIMER_Set( NPC, "attackDelay", interrogateTime );
  736. //TIMER_Set( NPC, "stand", interrogateTime );
  737. //set desired yaw and pitch towards this guy?
  738. //FIXME: this is permanent, they will never look away... *sigh*
  739. NPC_FacePosition( suspect->currentOrigin, qtrue );
  740. //FIXME: they still need some sort of eye/head tag/bone that can turn?
  741. //NPC_TempLookTarget( NPC, suspect->s.number, interrogateTime, interrogateTime );
  742. }
  743. }
  744. }
  745. return qfalse;
  746. }
  747. /*
  748. -------------------------
  749. NPC_ST_InvestigateEvent
  750. -------------------------
  751. */
  752. #define MAX_CHECK_THRESHOLD 1
  753. static qboolean NPC_ST_InvestigateEvent( int eventID, bool extraSuspicious )
  754. {
  755. //If they've given themselves away, just take them as an enemy
  756. if ( NPCInfo->confusionTime < level.time )
  757. {
  758. if ( level.alertEvents[eventID].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
  759. {
  760. //NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
  761. if ( !level.alertEvents[eventID].owner ||
  762. !level.alertEvents[eventID].owner->client ||
  763. level.alertEvents[eventID].owner->health <= 0 ||
  764. level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam )
  765. {//not an enemy
  766. return qfalse;
  767. }
  768. //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him)
  769. //ST_Speech( NPC, SPEECH_CHARGE, 0 );
  770. G_SetEnemy( NPC, level.alertEvents[eventID].owner );
  771. NPCInfo->enemyLastSeenTime = level.time;
  772. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  773. if ( level.alertEvents[eventID].type == AET_SOUND )
  774. {//heard him, didn't see him, stick for a bit
  775. TIMER_Set( NPC, "roamTime", Q_irand( 500, 2500 ) );
  776. }
  777. return qtrue;
  778. }
  779. }
  780. //don't look at the same alert twice
  781. /*
  782. if ( level.alertEvents[eventID].ID == NPCInfo->lastAlertID )
  783. {
  784. return qfalse;
  785. }
  786. NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
  787. */
  788. //Must be ready to take another sound event
  789. /*
  790. if ( NPCInfo->investigateSoundDebounceTime > level.time )
  791. {
  792. return qfalse;
  793. }
  794. */
  795. if ( level.alertEvents[eventID].type == AET_SIGHT )
  796. {//sight alert, check the light level
  797. if ( level.alertEvents[eventID].light < Q_irand( ST_MIN_LIGHT_THRESHOLD, ST_MAX_LIGHT_THRESHOLD ) )
  798. {//below my threshhold of potentially seeing
  799. return qfalse;
  800. }
  801. }
  802. //Save the position for movement (if necessary)
  803. VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal );
  804. //First awareness of it
  805. NPCInfo->investigateCount += ( extraSuspicious ) ? 2 : 1;
  806. //Clamp the value
  807. if ( NPCInfo->investigateCount > 4 )
  808. NPCInfo->investigateCount = 4;
  809. //See if we should walk over and investigate
  810. if ( level.alertEvents[eventID].level > AEL_MINOR && NPCInfo->investigateCount > 1 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
  811. {
  812. //make it so they can walk right to this point and look at it rather than having to use combatPoints
  813. if ( G_ExpandPointToBBox( NPCInfo->investigateGoal, NPC->mins, NPC->maxs, NPC->s.number, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ) )
  814. {//we were able to move the investigateGoal to a point in which our bbox would fit
  815. //drop the goal to the ground so we can get at it
  816. vec3_t end;
  817. trace_t trace;
  818. VectorCopy( NPCInfo->investigateGoal, end );
  819. end[2] -= 512;//FIXME: not always right? What if it's even higher, somehow?
  820. gi.trace( &trace, NPCInfo->investigateGoal, NPC->mins, NPC->maxs, end, ENTITYNUM_NONE, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) );
  821. if ( trace.fraction >= 1.0f )
  822. {//too high to even bother
  823. //FIXME: look at them???
  824. }
  825. else
  826. {
  827. VectorCopy( trace.endpos, NPCInfo->investigateGoal );
  828. NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue );
  829. NPCInfo->localState = LSTATE_INVESTIGATE;
  830. }
  831. }
  832. else
  833. {
  834. int id = NPC_FindCombatPoint( NPCInfo->investigateGoal, NPCInfo->investigateGoal, NPCInfo->investigateGoal, CP_INVESTIGATE|CP_HAS_ROUTE, 0 );
  835. if ( id != -1 )
  836. {
  837. NPC_SetMoveGoal( NPC, level.combatPoints[id].origin, 16, qtrue, id );
  838. NPCInfo->localState = LSTATE_INVESTIGATE;
  839. }
  840. }
  841. //Say something
  842. //FIXME: only if have others in group... these should be responses?
  843. if ( NPCInfo->investigateDebounceTime+NPCInfo->pauseTime > level.time )
  844. {//was already investigating
  845. if ( NPCInfo->group &&
  846. NPCInfo->group->commander &&
  847. NPCInfo->group->commander->client &&
  848. NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL &&
  849. !Q_irand( 0, 3 ) )
  850. {
  851. ST_Speech( NPCInfo->group->commander, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds
  852. }
  853. else
  854. {
  855. ST_Speech( NPC, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds
  856. }
  857. }
  858. else
  859. {
  860. if ( level.alertEvents[eventID].type == AET_SIGHT )
  861. {
  862. ST_Speech( NPC, SPEECH_SIGHT, 0 );
  863. }
  864. else if ( level.alertEvents[eventID].type == AET_SOUND )
  865. {
  866. ST_Speech( NPC, SPEECH_SOUND, 0 );
  867. }
  868. }
  869. //Setup the debounce info
  870. NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000;
  871. NPCInfo->investigateSoundDebounceTime = level.time + 2000;
  872. NPCInfo->pauseTime = level.time;
  873. }
  874. else
  875. {//just look?
  876. //Say something
  877. if ( level.alertEvents[eventID].type == AET_SIGHT )
  878. {
  879. ST_Speech( NPC, SPEECH_SIGHT, 0 );
  880. }
  881. else if ( level.alertEvents[eventID].type == AET_SOUND )
  882. {
  883. ST_Speech( NPC, SPEECH_SOUND, 0 );
  884. }
  885. //Setup the debounce info
  886. NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 1000;
  887. NPCInfo->investigateSoundDebounceTime = level.time + 1000;
  888. NPCInfo->pauseTime = level.time;
  889. VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal );
  890. if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER
  891. && !RT_Flying( NPC ) )
  892. {
  893. //if ( !Q_irand( 0, 2 ) )
  894. {//look around
  895. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  896. }
  897. }
  898. }
  899. if ( level.alertEvents[eventID].level >= AEL_DANGER )
  900. {
  901. NPCInfo->investigateDebounceTime = Q_irand( 500, 2500 );
  902. }
  903. //Start investigating
  904. NPCInfo->tempBehavior = BS_INVESTIGATE;
  905. return qtrue;
  906. }
  907. /*
  908. -------------------------
  909. ST_OffsetLook
  910. -------------------------
  911. */
  912. static void ST_OffsetLook( float offset, vec3_t out )
  913. {
  914. vec3_t angles, forward, temp;
  915. GetAnglesForDirection( NPC->currentOrigin, NPCInfo->investigateGoal, angles );
  916. angles[YAW] += offset;
  917. AngleVectors( angles, forward, NULL, NULL );
  918. VectorMA( NPC->currentOrigin, 64, forward, out );
  919. CalcEntitySpot( NPC, SPOT_HEAD, temp );
  920. out[2] = temp[2];
  921. }
  922. /*
  923. -------------------------
  924. ST_LookAround
  925. -------------------------
  926. */
  927. static void ST_LookAround( void )
  928. {
  929. vec3_t lookPos;
  930. float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime;
  931. //Keep looking at the spot
  932. if ( perc < 0.25 )
  933. {
  934. VectorCopy( NPCInfo->investigateGoal, lookPos );
  935. }
  936. else if ( perc < 0.5f ) //Look up but straight ahead
  937. {
  938. ST_OffsetLook( 0.0f, lookPos );
  939. }
  940. else if ( perc < 0.75f ) //Look right
  941. {
  942. ST_OffsetLook( 45.0f, lookPos );
  943. }
  944. else //Look left
  945. {
  946. ST_OffsetLook( -45.0f, lookPos );
  947. }
  948. NPC_FacePosition( lookPos );
  949. }
  950. /*
  951. -------------------------
  952. NPC_BSST_Investigate
  953. -------------------------
  954. */
  955. void NPC_BSST_Investigate( void )
  956. {
  957. //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too
  958. AI_GetGroup( NPC );
  959. if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
  960. {
  961. WeaponThink( qtrue );
  962. }
  963. if ( NPCInfo->confusionTime < level.time )
  964. {
  965. if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
  966. {
  967. //Look for an enemy
  968. if ( NPC_CheckPlayerTeamStealth() )
  969. {
  970. //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
  971. ST_Speech( NPC, SPEECH_DETECTED, 0 );
  972. NPCInfo->tempBehavior = BS_DEFAULT;
  973. NPC_UpdateAngles( qtrue, qtrue );
  974. return;
  975. }
  976. }
  977. }
  978. if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
  979. {
  980. int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, NPCInfo->lastAlertID );
  981. //There is an event to look at
  982. if ( alertEvent >= 0 )
  983. {
  984. if ( NPCInfo->confusionTime < level.time )
  985. {
  986. if ( NPC_CheckForDanger( alertEvent ) )
  987. {//running like hell
  988. ST_Speech( NPC, SPEECH_COVER, 0 );//FIXME: flee sound?
  989. return;
  990. }
  991. }
  992. //if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
  993. {
  994. NPC_ST_InvestigateEvent( alertEvent, qtrue );
  995. }
  996. }
  997. }
  998. //If we're done looking, then just return to what we were doing
  999. if ( ( NPCInfo->investigateDebounceTime + NPCInfo->pauseTime ) < level.time )
  1000. {
  1001. NPCInfo->tempBehavior = BS_DEFAULT;
  1002. NPCInfo->goalEntity = UpdateGoal();
  1003. NPC_UpdateAngles( qtrue, qtrue );
  1004. //Say something
  1005. ST_Speech( NPC, SPEECH_GIVEUP, 0 );
  1006. return;
  1007. }
  1008. //FIXME: else, look for new alerts
  1009. //See if we're searching for the noise's origin
  1010. if ( NPCInfo->localState == LSTATE_INVESTIGATE && (NPCInfo->goalEntity!=NULL) )
  1011. {
  1012. //See if we're there
  1013. if ( !STEER::Reached(NPC, NPCInfo->goalEntity, 32, !!FlyingCreature(NPC)) )
  1014. {
  1015. ucmd.buttons |= BUTTON_WALKING;
  1016. //Try and move there
  1017. if ( NPC_MoveToGoal( qtrue ) )
  1018. {
  1019. //Bump our times
  1020. NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000;
  1021. NPCInfo->pauseTime = level.time;
  1022. NPC_UpdateAngles( qtrue, qtrue );
  1023. return;
  1024. }
  1025. }
  1026. //Otherwise we're done or have given up
  1027. //Say something
  1028. //ST_Speech( NPC, SPEECH_LOOK, 0.33f );
  1029. NPCInfo->localState = LSTATE_NONE;
  1030. }
  1031. //Look around
  1032. ST_LookAround();
  1033. }
  1034. /*
  1035. -------------------------
  1036. NPC_BSST_Patrol
  1037. -------------------------
  1038. */
  1039. void NPC_BSST_Patrol( void )
  1040. {//FIXME: pick up on bodies of dead buddies?
  1041. //Not a scriptflag, but...
  1042. if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER && (NPC->client->ps.eFlags&EF_SPOTLIGHT) )
  1043. {//using spotlight search mode
  1044. vec3_t eyeFwd, end, mins={-2,-2,-2}, maxs={2,2,2};
  1045. trace_t trace;
  1046. AngleVectors( NPC->client->renderInfo.eyeAngles, eyeFwd, NULL, NULL );
  1047. VectorMA( NPC->client->renderInfo.eyePoint, NPCInfo->stats.visrange, eyeFwd, end );
  1048. //get server-side trace impact point
  1049. gi.trace( &trace, NPC->client->renderInfo.eyePoint, mins, maxs, end, NPC->s.number, MASK_OPAQUE|CONTENTS_BODY|CONTENTS_CORPSE );
  1050. NPC->speed = (trace.fraction*NPCInfo->stats.visrange);
  1051. if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
  1052. {
  1053. //FIXME: do a FOV cone check, then a trace
  1054. if ( trace.entityNum < ENTITYNUM_WORLD )
  1055. {//hit something
  1056. //try cheap check first
  1057. gentity_t *enemy = &g_entities[trace.entityNum];
  1058. if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam )
  1059. {
  1060. G_SetEnemy( NPC, enemy );
  1061. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  1062. //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
  1063. //NPC_AngerSound();
  1064. NPC_UpdateAngles( qtrue, qtrue );
  1065. return;
  1066. }
  1067. }
  1068. //FIXME: maybe do a quick check of ents within the spotlight's radius?
  1069. //hmmm, look around
  1070. if ( NPC_CheckEnemiesInSpotlight() )
  1071. {
  1072. //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
  1073. //NPC_AngerSound();
  1074. NPC_UpdateAngles( qtrue, qtrue );
  1075. return;
  1076. }
  1077. }
  1078. }
  1079. else
  1080. {
  1081. //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too
  1082. AI_GetGroup( NPC );
  1083. if ( NPCInfo->confusionTime < level.time )
  1084. {
  1085. //Look for any enemies
  1086. if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
  1087. {
  1088. if ( NPC_CheckPlayerTeamStealth() )
  1089. {
  1090. //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
  1091. //NPC_AngerSound();
  1092. NPC_UpdateAngles( qtrue, qtrue );
  1093. return;
  1094. }
  1095. }
  1096. }
  1097. }
  1098. if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
  1099. {
  1100. int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue );
  1101. //There is an event to look at
  1102. if ( alertEvent >= 0 )
  1103. {
  1104. if ( NPC_CheckForDanger( alertEvent ) )
  1105. {//going to run?
  1106. ST_Speech( NPC, SPEECH_COVER, 0 );
  1107. return;
  1108. }
  1109. else if (NPC->client->NPC_class==CLASS_BOBAFETT)
  1110. {
  1111. //NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
  1112. if ( !level.alertEvents[alertEvent].owner ||
  1113. !level.alertEvents[alertEvent].owner->client ||
  1114. level.alertEvents[alertEvent].owner->health <= 0 ||
  1115. level.alertEvents[alertEvent].owner->client->playerTeam != NPC->client->enemyTeam )
  1116. {//not an enemy
  1117. return;
  1118. }
  1119. //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him)
  1120. //ST_Speech( NPC, SPEECH_CHARGE, 0 );
  1121. G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
  1122. NPCInfo->enemyLastSeenTime = level.time;
  1123. TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
  1124. return;
  1125. }
  1126. else if ( NPC_ST_InvestigateEvent( alertEvent, qfalse ) )
  1127. {//actually going to investigate it
  1128. NPC_UpdateAngles( qtrue, qtrue );
  1129. return;
  1130. }
  1131. }
  1132. }
  1133. //If we have somewhere to go, then do that
  1134. if ( UpdateGoal() )
  1135. {
  1136. ucmd.buttons |= BUTTON_WALKING;
  1137. //ST_Move( NPCInfo->goalEntity );
  1138. NPC_MoveToGoal( qtrue );
  1139. }
  1140. else// if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
  1141. {
  1142. if ( NPC->client->NPC_class != CLASS_IMPERIAL && NPC->client->NPC_class != CLASS_IMPWORKER )
  1143. {//imperials do not look around
  1144. if ( TIMER_Done( NPC, "enemyLastVisible" ) )
  1145. {//nothing suspicious, look around
  1146. if ( !Q_irand( 0, 30 ) )
  1147. {
  1148. NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 );
  1149. }
  1150. if ( !Q_irand( 0, 30 ) )
  1151. {
  1152. NPCInfo->desiredPitch = Q_irand( -20, 20 );
  1153. }
  1154. }
  1155. }
  1156. }
  1157. NPC_UpdateAngles( qtrue, qtrue );
  1158. //TEMP hack for Imperial stand anim
  1159. if ( NPC->client->NPC_class == CLASS_IMPERIAL
  1160. || NPC->client->NPC_class == CLASS_IMPWORKER )
  1161. {//hack
  1162. if ( NPC->client->ps.weapon != WP_CONCUSSION )
  1163. {//not Rax
  1164. if ( ucmd.forwardmove || ucmd.rightmove || ucmd.upmove )
  1165. {//moving
  1166. if( (!NPC->client->ps.torsoAnimTimer) || (NPC->client->ps.torsoAnim == BOTH_STAND4) )
  1167. {
  1168. if ( (ucmd.buttons&BUTTON_WALKING) && !(NPCInfo->scriptFlags&SCF_RUNNING) )
  1169. {//not running, only set upper anim
  1170. // No longer overrides scripted anims
  1171. NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  1172. NPC->client->ps.torsoAnimTimer = 200;
  1173. }
  1174. }
  1175. }
  1176. else
  1177. {//standing still, set both torso and legs anim
  1178. // No longer overrides scripted anims
  1179. if( ( !NPC->client->ps.torsoAnimTimer || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) &&
  1180. ( !NPC->client->ps.legsAnimTimer || (NPC->client->ps.legsAnim == BOTH_STAND4) ) )
  1181. {
  1182. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  1183. NPC->client->ps.torsoAnimTimer = NPC->client->ps.legsAnimTimer = 200;
  1184. }
  1185. }
  1186. //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way
  1187. if ( NPC->client->ps.weapon != WP_NONE )
  1188. {
  1189. ChangeWeapon( NPC, WP_NONE );
  1190. NPC->client->ps.weapon = WP_NONE;
  1191. NPC->client->ps.weaponstate = WEAPON_READY;
  1192. G_RemoveWeaponModels( NPC );
  1193. }
  1194. }
  1195. }
  1196. }
  1197. /*
  1198. -------------------------
  1199. NPC_BSST_Idle
  1200. -------------------------
  1201. */
  1202. /*
  1203. void NPC_BSST_Idle( void )
  1204. {
  1205. int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue );
  1206. //There is an event to look at
  1207. if ( alertEvent >= 0 )
  1208. {
  1209. NPC_ST_InvestigateEvent( alertEvent, qfalse );
  1210. NPC_UpdateAngles( qtrue, qtrue );
  1211. return;
  1212. }
  1213. TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
  1214. NPC_UpdateAngles( qtrue, qtrue );
  1215. }
  1216. */
  1217. /*
  1218. -------------------------
  1219. ST_CheckMoveState
  1220. -------------------------
  1221. */
  1222. static void ST_CheckMoveState( void )
  1223. {
  1224. if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) )
  1225. {//moving toward a goal that a script is waiting on, so don't stop for anything!
  1226. move = qtrue;
  1227. }
  1228. else if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER
  1229. && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE )
  1230. {//no squad stuff
  1231. return;
  1232. }
  1233. // else if ( NPC->NPC->scriptFlags&SCF_NO_GROUPS )
  1234. {
  1235. move = qtrue;
  1236. }
  1237. //See if we're a scout
  1238. //See if we're moving towards a goal, not the enemy
  1239. if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
  1240. {
  1241. //Did we make it?
  1242. if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) ||
  1243. (enemyLOS && (NPCInfo->aiFlags&NPCAI_STOP_AT_LOS) && !Q3_TaskIDPending(NPC, TID_MOVE_NAV))
  1244. )
  1245. {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy
  1246. int newSquadState = SQUAD_STAND_AND_SHOOT;
  1247. //we got where we wanted to go, set timers based on why we were running
  1248. switch ( NPCInfo->squadState )
  1249. {
  1250. case SQUAD_RETREAT://was running away
  1251. //done fleeing, obviously
  1252. TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 );
  1253. TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
  1254. TIMER_Set( NPC, "flee", -level.time );
  1255. newSquadState = SQUAD_COVER;
  1256. break;
  1257. case SQUAD_TRANSITION://was heading for a combat point
  1258. TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
  1259. break;
  1260. case SQUAD_SCOUT://was running after player
  1261. break;
  1262. default:
  1263. break;
  1264. }
  1265. AI_GroupUpdateSquadstates( NPCInfo->group, NPC, newSquadState );
  1266. NPC_ReachedGoal();
  1267. //don't attack right away
  1268. TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels
  1269. //don't do something else just yet
  1270. // THIS IS THE ONE TRUE PLACE WHERE ROAM TIME IS SET
  1271. TIMER_Set( NPC, "roamTime", Q_irand( 8000, 15000 ) );//Q_irand( 1000, 4000 ) );
  1272. if (Q_irand(0, 3)==0)
  1273. {
  1274. TIMER_Set( NPC, "duck", Q_irand(5000, 10000) ); // just reached our goal, chance of ducking now
  1275. }
  1276. return;
  1277. }
  1278. //keep going, hold of roamTimer until we get there
  1279. TIMER_Set( NPC, "roamTime", Q_irand( 8000, 9000 ) );
  1280. }
  1281. }
  1282. void ST_ResolveBlockedShot( int hit )
  1283. {
  1284. int stuckTime;
  1285. //figure out how long we intend to stand here, max
  1286. if ( TIMER_Get( NPC, "roamTime" ) > TIMER_Get( NPC, "stick" ) )
  1287. {
  1288. stuckTime = TIMER_Get( NPC, "roamTime" )-level.time;
  1289. }
  1290. else
  1291. {
  1292. stuckTime = TIMER_Get( NPC, "stick" )-level.time;
  1293. }
  1294. if ( TIMER_Done( NPC, "duck" ) )
  1295. {//we're not ducking
  1296. if ( AI_GroupContainsEntNum( NPCInfo->group, hit ) )
  1297. {
  1298. gentity_t *member = &g_entities[hit];
  1299. if ( TIMER_Done( member, "duck" ) )
  1300. {//they aren't ducking
  1301. if ( TIMER_Done( member, "stand" ) )
  1302. {//they're not being forced to stand
  1303. //tell them to duck at least as long as I'm not moving
  1304. TIMER_Set( member, "duck", stuckTime ); // tell my friend to duck so I can shoot over his head
  1305. return;
  1306. }
  1307. }
  1308. }
  1309. }
  1310. else
  1311. {//maybe we should stand
  1312. if ( TIMER_Done( NPC, "stand" ) )
  1313. {//stand for as long as we'll be here
  1314. TIMER_Set( NPC, "stand", stuckTime );
  1315. return;
  1316. }
  1317. }
  1318. //Hmm, can't resolve this by telling them to duck or telling me to stand
  1319. //We need to move!
  1320. TIMER_Set( NPC, "roamTime", -1 );
  1321. TIMER_Set( NPC, "stick", -1 );
  1322. TIMER_Set( NPC, "duck", -1 );
  1323. TIMER_Set( NPC, "attakDelay", Q_irand( 1000, 3000 ) );
  1324. }
  1325. /*
  1326. -------------------------
  1327. ST_CheckFireState
  1328. -------------------------
  1329. */
  1330. static void ST_CheckFireState( void )
  1331. {
  1332. if ( enemyCS )
  1333. {//if have a clear shot, always try
  1334. return;
  1335. }
  1336. if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
  1337. {//runners never try to fire at the last pos
  1338. return;
  1339. }
  1340. if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
  1341. {//if moving at all, don't do this
  1342. return;
  1343. }
  1344. //See if we should continue to fire on their last position
  1345. //!TIMER_Done( NPC, "stick" ) ||
  1346. if ( !hitAlly //we're not going to hit an ally
  1347. && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS?
  1348. && NPCInfo->enemyLastSeenTime > 0 //we've seen the enemy
  1349. && NPCInfo->group //have a group
  1350. && (NPCInfo->group->numState[SQUAD_RETREAT]>0||NPCInfo->group->numState[SQUAD_TRANSITION]>0||NPCInfo->group->numState[SQUAD_SCOUT]>0) )//laying down covering fire
  1351. {
  1352. if ( level.time - NPCInfo->enemyLastSeenTime < 10000 &&//we have seem the enemy in the last 10 seconds
  1353. (!NPCInfo->group || level.time - NPCInfo->group->lastSeenEnemyTime < 10000 ))//we are not in a group or the group has seen the enemy in the last 10 seconds
  1354. {
  1355. if ( !Q_irand( 0, 10 ) )
  1356. {
  1357. //Fire on the last known position
  1358. vec3_t muzzle, dir, angles;
  1359. qboolean tooClose = qfalse;
  1360. qboolean tooFar = qfalse;
  1361. CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
  1362. if ( VectorCompare( impactPos, vec3_origin ) )
  1363. {//never checked ShotEntity this frame, so must do a trace...
  1364. trace_t tr;
  1365. //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2};
  1366. vec3_t forward, end;
  1367. AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
  1368. VectorMA( muzzle, 8192, forward, end );
  1369. gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
  1370. VectorCopy( tr.endpos, impactPos );
  1371. }
  1372. //see if impact would be too close to me
  1373. float distThreshold = 16384/*128*128*/;//default
  1374. switch ( NPC->s.weapon )
  1375. {
  1376. case WP_ROCKET_LAUNCHER:
  1377. case WP_FLECHETTE:
  1378. case WP_THERMAL:
  1379. case WP_TRIP_MINE:
  1380. case WP_DET_PACK:
  1381. distThreshold = 65536/*256*256*/;
  1382. break;
  1383. case WP_REPEATER:
  1384. if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
  1385. {
  1386. distThreshold = 65536/*256*256*/;
  1387. }
  1388. break;
  1389. case WP_CONCUSSION:
  1390. if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  1391. {
  1392. distThreshold = 65536/*256*256*/;
  1393. }
  1394. break;
  1395. default:
  1396. break;
  1397. }
  1398. float dist = DistanceSquared( impactPos, muzzle );
  1399. if ( dist < distThreshold )
  1400. {//impact would be too close to me
  1401. tooClose = qtrue;
  1402. }
  1403. else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
  1404. (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
  1405. {//we've haven't seen them in the last 5 seconds
  1406. //see if it's too far from where he is
  1407. distThreshold = 65536/*256*256*/;//default
  1408. switch ( NPC->s.weapon )
  1409. {
  1410. case WP_ROCKET_LAUNCHER:
  1411. case WP_FLECHETTE:
  1412. case WP_THERMAL:
  1413. case WP_TRIP_MINE:
  1414. case WP_DET_PACK:
  1415. distThreshold = 262144/*512*512*/;
  1416. break;
  1417. case WP_REPEATER:
  1418. if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
  1419. {
  1420. distThreshold = 262144/*512*512*/;
  1421. }
  1422. break;
  1423. case WP_CONCUSSION:
  1424. if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  1425. {
  1426. distThreshold = 262144/*512*512*/;
  1427. }
  1428. break;
  1429. default:
  1430. break;
  1431. }
  1432. dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
  1433. if ( dist > distThreshold )
  1434. {//impact would be too far from enemy
  1435. tooFar = qtrue;
  1436. }
  1437. }
  1438. if ( !tooClose && !tooFar )
  1439. {//okay too shoot at last pos
  1440. VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
  1441. VectorNormalize( dir );
  1442. vectoangles( dir, angles );
  1443. NPCInfo->desiredYaw = angles[YAW];
  1444. NPCInfo->desiredPitch = angles[PITCH];
  1445. shoot = qtrue;
  1446. faceEnemy = qfalse;
  1447. //AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
  1448. return;
  1449. }
  1450. }
  1451. }
  1452. }
  1453. }
  1454. void ST_TrackEnemy( gentity_t *self, vec3_t enemyPos )
  1455. {
  1456. //clear timers
  1457. TIMER_Set( self, "attackDelay", Q_irand( 1000, 2000 ) );
  1458. //TIMER_Set( self, "duck", -1 );
  1459. TIMER_Set( self, "stick", Q_irand( 500, 1500 ) );
  1460. TIMER_Set( self, "stand", -1 );
  1461. TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) );
  1462. //leave my combat point
  1463. NPC_FreeCombatPoint( self->NPC->combatPoint );
  1464. //go after his last seen pos
  1465. NPC_SetMoveGoal( self, enemyPos, 100.0f, qfalse );
  1466. if (Q_irand(0,3)==0)
  1467. {
  1468. NPCInfo->aiFlags |= NPCAI_STOP_AT_LOS;
  1469. }
  1470. }
  1471. int ST_ApproachEnemy( gentity_t *self )
  1472. {
  1473. TIMER_Set( self, "attackDelay", Q_irand( 250, 500 ) );
  1474. //TIMER_Set( self, "duck", -1 );
  1475. TIMER_Set( self, "stick", Q_irand( 1000, 2000 ) );
  1476. TIMER_Set( self, "stand", -1 );
  1477. TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) );
  1478. //leave my combat point
  1479. NPC_FreeCombatPoint( self->NPC->combatPoint );
  1480. //return the relevant combat point flags
  1481. return (CP_CLEAR|CP_CLOSEST);
  1482. }
  1483. void ST_HuntEnemy( gentity_t *self )
  1484. {
  1485. //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );//Disabled this for now, guys who couldn't hunt would never attack
  1486. //TIMER_Set( NPC, "duck", -1 );
  1487. TIMER_Set( NPC, "stick", Q_irand( 250, 1000 ) );
  1488. TIMER_Set( NPC, "stand", -1 );
  1489. TIMER_Set( NPC, "scoutTime", TIMER_Get( NPC, "stick" )-level.time+Q_irand(5000, 10000) );
  1490. //leave my combat point
  1491. NPC_FreeCombatPoint( NPCInfo->combatPoint );
  1492. //go directly after the enemy
  1493. if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
  1494. {
  1495. self->NPC->goalEntity = NPC->enemy;
  1496. }
  1497. }
  1498. void ST_TransferTimers( gentity_t *self, gentity_t *other )
  1499. {
  1500. TIMER_Set( other, "attackDelay", TIMER_Get( self, "attackDelay" )-level.time );
  1501. TIMER_Set( other, "duck", TIMER_Get( self, "duck" )-level.time );
  1502. TIMER_Set( other, "stick", TIMER_Get( self, "stick" )-level.time );
  1503. TIMER_Set( other, "scoutTime", TIMER_Get( self, "scoutTime" )-level.time );
  1504. TIMER_Set( other, "roamTime", TIMER_Get( self, "roamTime" )-level.time );
  1505. TIMER_Set( other, "stand", TIMER_Get( self, "stand" )-level.time );
  1506. TIMER_Set( self, "attackDelay", -1 );
  1507. TIMER_Set( self, "duck", -1 );
  1508. TIMER_Set( self, "stick", -1 );
  1509. TIMER_Set( self, "scoutTime", -1 );
  1510. TIMER_Set( self, "roamTime", -1 );
  1511. TIMER_Set( self, "stand", -1 );
  1512. }
  1513. void ST_TransferMoveGoal( gentity_t *self, gentity_t *other )
  1514. {
  1515. if ( Q3_TaskIDPending( self, TID_MOVE_NAV ) )
  1516. {//can't transfer movegoal when a script we're running is waiting to complete
  1517. return;
  1518. }
  1519. if ( self->NPC->combatPoint != -1 )
  1520. {//I've got a combatPoint I'm going to, give it to him
  1521. self->NPC->lastFailedCombatPoint = other->NPC->combatPoint = self->NPC->combatPoint;
  1522. self->NPC->combatPoint = -1;
  1523. }
  1524. else
  1525. {//I must be going for a goal, give that to him instead
  1526. if ( self->NPC->goalEntity == self->NPC->tempGoal )
  1527. {
  1528. NPC_SetMoveGoal( other, self->NPC->tempGoal->currentOrigin, self->NPC->goalRadius, ((self->NPC->tempGoal->svFlags&SVF_NAVGOAL)?true:false) );
  1529. }
  1530. else
  1531. {
  1532. other->NPC->goalEntity = self->NPC->goalEntity;
  1533. }
  1534. }
  1535. //give him my squadstate
  1536. AI_GroupUpdateSquadstates( self->NPC->group, other, NPCInfo->squadState );
  1537. //give him my timers and clear mine
  1538. ST_TransferTimers( self, other );
  1539. //now make me stand around for a second or two at least
  1540. AI_GroupUpdateSquadstates( self->NPC->group, self, SQUAD_STAND_AND_SHOOT );
  1541. TIMER_Set( self, "stand", Q_irand( 1000, 3000 ) );
  1542. }
  1543. int ST_GetCPFlags( void )
  1544. {
  1545. int cpFlags = 0;
  1546. if ( NPC && NPCInfo->group )
  1547. {
  1548. if ( NPC == NPCInfo->group->commander && NPC->client->NPC_class == CLASS_IMPERIAL )
  1549. {//imperials hang back and give orders
  1550. if ( NPCInfo->group->numGroup > 1 && Q_irand( -3, NPCInfo->group->numGroup ) > 1 )
  1551. {//FIXME: make sure he;s giving orders with these lines
  1552. if ( Q_irand( 0, 1 ) )
  1553. {
  1554. ST_Speech( NPC, SPEECH_CHASE, 0.5 );
  1555. }
  1556. else
  1557. {
  1558. ST_Speech( NPC, SPEECH_YELL, 0.5 );
  1559. }
  1560. }
  1561. cpFlags = (CP_CLEAR|CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT);
  1562. }
  1563. else if ( NPCInfo->group->morale < 0 )
  1564. {//hide
  1565. cpFlags = (CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT);
  1566. /*
  1567. if ( NPC->client->NPC_class == CLASS_SABOTEUR && !Q_irand( 0, 3 ) )
  1568. {
  1569. Saboteur_Cloak( NPC );
  1570. }
  1571. */
  1572. }
  1573. /* else if ( NPCInfo->group->morale < NPCInfo->group->numGroup )
  1574. {//morale is low for our size
  1575. int moraleDrop = NPCInfo->group->numGroup - NPCInfo->group->morale;
  1576. if ( moraleDrop < -6 )
  1577. {//flee (no clear shot needed)
  1578. cpFlags = (CP_FLEE|CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE);
  1579. }
  1580. else if ( moraleDrop < -3 )
  1581. {//retreat (no clear shot needed)
  1582. cpFlags = (CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE);
  1583. }
  1584. else if ( moraleDrop < 0 )
  1585. {//cover (no clear shot needed)
  1586. cpFlags = (CP_COVER|CP_AVOID|CP_SAFE);
  1587. }
  1588. }*/
  1589. else
  1590. {
  1591. int moraleBoost = NPCInfo->group->morale - NPCInfo->group->numGroup;
  1592. if ( moraleBoost > 20 )
  1593. {//charge to any one and outflank (no cover needed)
  1594. cpFlags = (CP_CLEAR|CP_FLANK|CP_APPROACH_ENEMY);
  1595. //Saboteur_Decloak( NPC );
  1596. }
  1597. else if ( moraleBoost > 15 )
  1598. {//charge to closest one (no cover needed)
  1599. cpFlags = (CP_CLEAR|CP_CLOSEST|CP_APPROACH_ENEMY);
  1600. /*
  1601. if ( NPC->client->NPC_class == CLASS_SABOTEUR && !Q_irand( 0, 3 ) )
  1602. {
  1603. Saboteur_Decloak( NPC );
  1604. }
  1605. */
  1606. }
  1607. else if ( moraleBoost > 10 )
  1608. {//charge closer (no cover needed)
  1609. cpFlags = (CP_CLEAR|CP_APPROACH_ENEMY);
  1610. /*
  1611. if ( NPC->client->NPC_class == CLASS_SABOTEUR && !Q_irand( 0, 6 ) )
  1612. {
  1613. Saboteur_Decloak( NPC );
  1614. }
  1615. */
  1616. }
  1617. }
  1618. }
  1619. if ( !cpFlags )
  1620. {
  1621. //at some medium level of morale
  1622. switch( Q_irand( 0, 3 ) )
  1623. {
  1624. case 0://just take the nearest one
  1625. cpFlags = (CP_CLEAR|CP_COVER|CP_NEAREST);
  1626. break;
  1627. case 1://take one closer to the enemy
  1628. cpFlags = (CP_CLEAR|CP_COVER|CP_APPROACH_ENEMY);
  1629. break;
  1630. case 2://take the one closest to the enemy
  1631. cpFlags = (CP_CLEAR|CP_COVER|CP_CLOSEST|CP_APPROACH_ENEMY);
  1632. break;
  1633. case 3://take the one on the other side of the enemy
  1634. cpFlags = (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY);
  1635. break;
  1636. }
  1637. }
  1638. if ( NPC && (NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
  1639. {
  1640. cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
  1641. cpFlags |= CP_NEAREST;
  1642. }
  1643. return cpFlags;
  1644. }
  1645. /*
  1646. -------------------------
  1647. ST_Commander
  1648. Make decisions about who should go where, etc.
  1649. FIXME: leader (group-decision-making) AI?
  1650. FIXME: need alternate routes!
  1651. FIXME: more group voice interaction
  1652. FIXME: work in pairs?
  1653. -------------------------
  1654. */
  1655. void ST_Commander( void )
  1656. {
  1657. int i;//, j;
  1658. int cp, cpFlags_org, cpFlags;
  1659. AIGroupInfo_t *group = NPCInfo->group;
  1660. gentity_t *member;//, *buddy;
  1661. qboolean runner = qfalse;
  1662. qboolean enemyLost = qfalse;
  1663. qboolean scouting = qfalse;
  1664. int squadState;
  1665. float avoidDist;
  1666. group->processed = qtrue;
  1667. if ( group->enemy == NULL || group->enemy->client == NULL )
  1668. {//hmm, no enemy...?!
  1669. return;
  1670. }
  1671. //FIXME: have this group commander check the enemy group (if any) and see if they have
  1672. // superior numbers. If they do, fall back rather than advance. If you have
  1673. // superior numbers, advance on them.
  1674. //FIXME: find the group commander and have him occasionally give orders when there is speech
  1675. //FIXME: start fleeing when only a couple of you vs. a lightsaber, possibly give up if the only one left
  1676. SaveNPCGlobals();
  1677. if ( group->lastSeenEnemyTime < level.time - 180000 )
  1678. {//dissolve the group
  1679. ST_Speech( NPC, SPEECH_LOST, 0.0f );
  1680. group->enemy->waypoint = NAV::GetNearestNode(group->enemy);
  1681. for ( i = 0; i < group->numGroup; i++ )
  1682. {
  1683. member = &g_entities[group->member[i].number];
  1684. SetNPCGlobals( member );
  1685. if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) )
  1686. {//running somewhere that a script requires us to go, don't break from that
  1687. continue;
  1688. }
  1689. if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
  1690. {//not allowed to move on my own
  1691. continue;
  1692. }
  1693. //Lost enemy for three minutes? go into search mode?
  1694. G_ClearEnemy( NPC );
  1695. NPC->waypoint = NAV::GetNearestNode(group->enemy);
  1696. if ( NPC->waypoint == WAYPOINT_NONE )
  1697. {
  1698. NPCInfo->behaviorState = BS_DEFAULT;//BS_PATROL;
  1699. }
  1700. else if ( group->enemy->waypoint == WAYPOINT_NONE || (NAV::EstimateCostToGoal( NPC->waypoint, group->enemy->waypoint ) >= Q3_INFINITE) )
  1701. {
  1702. NPC_BSSearchStart( NPC->waypoint, BS_SEARCH );
  1703. }
  1704. else
  1705. {
  1706. NPC_BSSearchStart( group->enemy->waypoint, BS_SEARCH );
  1707. }
  1708. }
  1709. group->enemy = NULL;
  1710. RestoreNPCGlobals();
  1711. return;
  1712. }
  1713. //see if anyone is running
  1714. if ( group->numState[SQUAD_SCOUT] > 0 ||
  1715. group->numState[SQUAD_TRANSITION] > 0 ||
  1716. group->numState[SQUAD_RETREAT] > 0 )
  1717. {//someone is running
  1718. runner = qtrue;
  1719. }
  1720. if ( /*!runner &&*/ group->lastSeenEnemyTime > level.time - 32000 && group->lastSeenEnemyTime < level.time - 30000 )
  1721. {//no-one has seen the enemy for 30 seconds// and no-one is running after him
  1722. if ( group->commander && !Q_irand( 0, 1 ) )
  1723. {
  1724. ST_Speech( group->commander, SPEECH_ESCAPING, 0.0f );
  1725. }
  1726. else
  1727. {
  1728. ST_Speech( NPC, SPEECH_ESCAPING, 0.0f );
  1729. }
  1730. //don't say this again
  1731. NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
  1732. }
  1733. if ( group->lastSeenEnemyTime < level.time - 7000 )
  1734. {//no-one has seen the enemy for at least 10 seconds! Should send a scout
  1735. enemyLost = qtrue;
  1736. }
  1737. //Go through the list:
  1738. //Everyone should try to get to a combat point if possible
  1739. int curMemberNum, lastMemberNum;
  1740. if ( d_asynchronousGroupAI->integer )
  1741. {//do one member a turn
  1742. group->activeMemberNum++;
  1743. if ( group->activeMemberNum >= group->numGroup )
  1744. {
  1745. group->activeMemberNum = 0;
  1746. }
  1747. curMemberNum = group->activeMemberNum;
  1748. lastMemberNum = curMemberNum + 1;
  1749. }
  1750. else
  1751. {
  1752. curMemberNum = 0;
  1753. lastMemberNum = group->numGroup;
  1754. }
  1755. for ( i = curMemberNum; i < lastMemberNum; i++ )
  1756. {
  1757. //reset combat point flags
  1758. cp = -1;
  1759. cpFlags = 0;
  1760. squadState = SQUAD_IDLE;
  1761. avoidDist = 0;
  1762. scouting = qfalse;
  1763. //get the next guy
  1764. member = &g_entities[group->member[i].number];
  1765. if ( !member->enemy )
  1766. {//don't include guys that aren't angry
  1767. continue;
  1768. }
  1769. SetNPCGlobals( member );
  1770. if ( !TIMER_Done( NPC, "flee" ) )
  1771. {//running away
  1772. continue;
  1773. }
  1774. if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) )
  1775. {//running somewhere that a script requires us to go
  1776. continue;
  1777. }
  1778. if ( NPC->s.weapon == WP_NONE
  1779. && NPCInfo->goalEntity
  1780. && NPCInfo->goalEntity == NPCInfo->tempGoal
  1781. && NPCInfo->goalEntity->s.eType == ET_ITEM )
  1782. {//running to pick up a gun, don't do other logic
  1783. continue;
  1784. }
  1785. if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
  1786. {//not allowed to do combat-movement
  1787. continue;
  1788. }
  1789. if ( NPC->client->ps.weapon == WP_NONE )
  1790. {//weaponless, should be hiding
  1791. if ( NPCInfo->goalEntity == NULL || NPCInfo->goalEntity->enemy == NULL || NPCInfo->goalEntity->enemy->s.eType != ET_ITEM )
  1792. {//not running after a pickup
  1793. if ( TIMER_Done( NPC, "hideTime" ) || (DistanceSquared( group->enemy->currentOrigin, NPC->currentOrigin ) < 65536 && NPC_ClearLOS( NPC->enemy )) )
  1794. {//done hiding or enemy near and can see us
  1795. //er, start another flee I guess?
  1796. NPC_StartFlee( NPC->enemy, NPC->enemy->currentOrigin, AEL_DANGER_GREAT, 5000, 10000 );
  1797. }//else, just hang here
  1798. }
  1799. continue;
  1800. }
  1801. if (enemyLost && NAV::InSameRegion(NPC, NPC->enemy->currentOrigin))
  1802. {
  1803. ST_TrackEnemy( NPC, NPC->enemy->currentOrigin );
  1804. continue;
  1805. }
  1806. if (!NPC->enemy)
  1807. {
  1808. continue;
  1809. }
  1810. // Check To See We Have A Clear Shot To The Enemy Every Couple Seconds
  1811. //---------------------------------------------------------------------
  1812. if (TIMER_Done( NPC, "checkGrenadeTooCloseDebouncer" ))
  1813. {
  1814. TIMER_Set (NPC, "checkGrenadeTooCloseDebouncer", Q_irand(300, 600));
  1815. vec3_t mins;
  1816. vec3_t maxs;
  1817. bool fled = false;
  1818. gentity_t* ent;
  1819. gentity_t *entityList[MAX_GENTITIES];
  1820. for (int i = 0 ; i < 3 ; i++ )
  1821. {
  1822. mins[i] = NPC->currentOrigin[i] - 200;
  1823. maxs[i] = NPC->currentOrigin[i] + 200;
  1824. }
  1825. int numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
  1826. for (int e = 0 ; e < numListedEntities ; e++ )
  1827. {
  1828. ent = entityList[ e ];
  1829. if (ent == NPC)
  1830. continue;
  1831. if (ent->owner == NPC)
  1832. continue;
  1833. if ( !(ent->inuse) )
  1834. continue;
  1835. if ( ent->s.eType == ET_MISSILE )
  1836. {
  1837. if ( ent->s.weapon == WP_THERMAL )
  1838. {//a thermal
  1839. if ( ent->has_bounced && (!ent->owner || !OnSameTeam(ent->owner, NPC)))
  1840. {//bounced and an enemy thermal
  1841. ST_Speech( NPC, SPEECH_COVER, 0 );//FIXME: flee sound?
  1842. NPC_StartFlee(NPC->enemy, ent->currentOrigin, AEL_DANGER_GREAT, 1000, 2000);
  1843. fled = true;
  1844. // cpFlags |= (CP_CLEAR|CP_COVER); // NOPE, Can't See The Enemy, So Find A New Combat Point
  1845. TIMER_Set (NPC, "checkGrenadeTooCloseDebouncer", Q_irand(2000, 4000));
  1846. break;
  1847. }
  1848. }
  1849. }
  1850. }
  1851. if (fled)
  1852. {
  1853. continue;
  1854. }
  1855. }
  1856. // Check To See We Have A Clear Shot To The Enemy Every Couple Seconds
  1857. //---------------------------------------------------------------------
  1858. if (TIMER_Done( NPC, "checkEnemyVisDebouncer" ))
  1859. {
  1860. TIMER_Set (NPC, "checkEnemyVisDebouncer", Q_irand(3000, 7000));
  1861. if (!NPC_ClearLOS(NPC->enemy))
  1862. {
  1863. cpFlags |= (CP_CLEAR|CP_COVER); // NOPE, Can't See The Enemy, So Find A New Combat Point
  1864. }
  1865. }
  1866. // Check To See If The Enemy Is Too Close For Comfort
  1867. //----------------------------------------------------
  1868. if (NPC->client->NPC_class!=CLASS_ASSASSIN_DROID)
  1869. {
  1870. if (TIMER_Done(NPC, "checkEnemyTooCloseDebouncer"))
  1871. {
  1872. TIMER_Set (NPC, "checkEnemyTooCloseDebouncer", Q_irand(1000, 6000));
  1873. float distThreshold = 16384/*128*128*/;//default
  1874. switch ( NPC->s.weapon )
  1875. {
  1876. case WP_ROCKET_LAUNCHER:
  1877. case WP_FLECHETTE:
  1878. case WP_THERMAL:
  1879. case WP_TRIP_MINE:
  1880. case WP_DET_PACK:
  1881. distThreshold = 65536/*256*256*/;
  1882. break;
  1883. case WP_REPEATER:
  1884. if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
  1885. {
  1886. distThreshold = 65536/*256*256*/;
  1887. }
  1888. break;
  1889. case WP_CONCUSSION:
  1890. if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  1891. {
  1892. distThreshold = 65536/*256*256*/;
  1893. }
  1894. break;
  1895. default:
  1896. break;
  1897. }
  1898. if ( DistanceSquared( group->enemy->currentOrigin, NPC->currentOrigin ) < distThreshold )
  1899. {
  1900. cpFlags |= (CP_CLEAR|CP_COVER);
  1901. }
  1902. }
  1903. }
  1904. //clear the local state
  1905. NPCInfo->localState = LSTATE_NONE;
  1906. cpFlags &= ~CP_NEAREST;
  1907. //Assign combat points
  1908. if ( cpFlags )
  1909. {//we want to run to a combat point
  1910. //always avoid enemy when picking combat points, and we always want to be able to get there
  1911. cpFlags |= CP_AVOID_ENEMY|CP_HAS_ROUTE|CP_TRYFAR;
  1912. avoidDist = 200;
  1913. cpFlags_org = cpFlags; //remember what we *wanted* to do...
  1914. //now get a combat point
  1915. if ( cp == -1 )
  1916. {//may have had sone set above
  1917. cp = NPC_FindCombatPointRetry( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, &cpFlags, avoidDist, NPCInfo->lastFailedCombatPoint );
  1918. }
  1919. //see if we got a valid one
  1920. if ( cp != -1 )
  1921. {//found a combat point
  1922. //let others know that someone is now running
  1923. runner = qtrue;
  1924. //don't change course again until we get to where we're going
  1925. TIMER_Set( NPC, "roamTime", Q3_INFINITE );
  1926. NPC_SetCombatPoint( cp );
  1927. NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
  1928. // If Successfully
  1929. if ((cpFlags&CP_FLANK) || ((cpFlags&CP_COVER) && (cpFlags&CP_CLEAR)))
  1930. {
  1931. }
  1932. else if (Q_irand(0,3)==0)
  1933. {
  1934. NPCInfo->aiFlags |= NPCAI_STOP_AT_LOS;
  1935. }
  1936. //okay, try a move right now to see if we can even get there
  1937. if ( (cpFlags&CP_FLANK) )
  1938. {
  1939. if ( group->numGroup > 1 )
  1940. {
  1941. NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 );
  1942. }
  1943. }
  1944. else if ( (cpFlags&CP_COVER) && !(cpFlags&CP_CLEAR) )
  1945. {//going into hiding
  1946. NPC_ST_StoreMovementSpeech( SPEECH_COVER, -1 );
  1947. }
  1948. else
  1949. {
  1950. if ( !Q_irand( 0, 20 ) )
  1951. {//hell, we're loading the sounds, use them every now and then!
  1952. if ( Q_irand( 0, 1 ) )
  1953. {
  1954. NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 );
  1955. }
  1956. else
  1957. {
  1958. NPC_ST_StoreMovementSpeech( SPEECH_ESCAPING, -1 );
  1959. }
  1960. }
  1961. }
  1962. }
  1963. }
  1964. }
  1965. RestoreNPCGlobals();
  1966. return;
  1967. }
  1968. extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
  1969. void Noghri_StickTrace( void )
  1970. {
  1971. if ( !NPC->ghoul2.size()
  1972. || NPC->weaponModel[0] <= 0 )
  1973. {
  1974. return;
  1975. }
  1976. int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[0]], "*weapon");
  1977. if ( boltIndex != -1 )
  1978. {
  1979. int curTime = (cg.time?cg.time:level.time);
  1980. qboolean hit = qfalse;
  1981. int lastHit = ENTITYNUM_NONE;
  1982. for ( int time = curTime-25; time <= curTime+25&&!hit; time += 25 )
  1983. {
  1984. mdxaBone_t boltMatrix;
  1985. vec3_t tip, dir, base, angles={0,NPC->currentAngles[YAW],0};
  1986. vec3_t mins={-2,-2,-2},maxs={2,2,2};
  1987. trace_t trace;
  1988. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[0],
  1989. boltIndex,
  1990. &boltMatrix, angles, NPC->currentOrigin, time,
  1991. NULL, NPC->s.modelScale );
  1992. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, base );
  1993. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, dir );
  1994. VectorMA( base, 48, dir, tip );
  1995. #ifndef FINAL_BUILD
  1996. if ( d_saberCombat->integer > 1 )
  1997. {
  1998. G_DebugLine(base, tip, FRAMETIME, 0x000000ff, qtrue);
  1999. }
  2000. #endif
  2001. gi.trace( &trace, base, mins, maxs, tip, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
  2002. if ( trace.fraction < 1.0f && trace.entityNum != lastHit )
  2003. {//hit something
  2004. gentity_t *traceEnt = &g_entities[trace.entityNum];
  2005. if ( traceEnt->takedamage
  2006. && (!traceEnt->client || traceEnt == NPC->enemy || traceEnt->client->NPC_class != NPC->client->NPC_class) )
  2007. {//smack
  2008. int dmg = Q_irand( 12, 20 );//FIXME: base on skill!
  2009. //FIXME: debounce?
  2010. G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", Q_irand( 1, 4 ) ) ) );
  2011. G_Damage( traceEnt, NPC, NPC, vec3_origin, trace.endpos, dmg, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
  2012. if ( traceEnt->health > 0 && dmg > 17 )
  2013. {//do pain on enemy
  2014. G_Knockdown( traceEnt, NPC, dir, 300, qtrue );
  2015. }
  2016. lastHit = trace.entityNum;
  2017. hit = qtrue;
  2018. }
  2019. }
  2020. }
  2021. }
  2022. }
  2023. /*
  2024. -------------------------
  2025. NPC_BSST_Attack
  2026. -------------------------
  2027. */
  2028. void NPC_BSST_Attack( void )
  2029. {
  2030. //Don't do anything if we're hurt
  2031. if ( NPC->painDebounceTime > level.time )
  2032. {
  2033. NPC_UpdateAngles( qtrue, qtrue );
  2034. return;
  2035. }
  2036. //NPC_CheckEnemy( qtrue, qfalse );
  2037. //If we don't have an enemy, just idle
  2038. if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )//
  2039. {
  2040. if( NPC->client->playerTeam == TEAM_PLAYER )
  2041. {
  2042. NPC_BSPatrol();
  2043. }
  2044. else
  2045. {
  2046. NPC_BSST_Patrol();//FIXME: or patrol?
  2047. }
  2048. return;
  2049. }
  2050. //FIXME: put some sort of delay into the guys depending on how they saw you...?
  2051. //Get our group info
  2052. if ( TIMER_Done( NPC, "interrogating" ) )
  2053. {
  2054. AI_GetGroup( NPC );//, 45, 512, NPC->enemy );
  2055. }
  2056. else
  2057. {
  2058. //FIXME: when done interrogating, I should send out a team alert!
  2059. }
  2060. if ( NPCInfo->group )
  2061. {//I belong to a squad of guys - we should *always* have a group
  2062. if ( !NPCInfo->group->processed )
  2063. {//I'm the first ent in my group, I'll make the command decisions
  2064. #if AI_TIMERS
  2065. int startTime = GetTime(0);
  2066. #endif// AI_TIMERS
  2067. ST_Commander();
  2068. #if AI_TIMERS
  2069. int commTime = GetTime ( startTime );
  2070. if ( commTime > 20 )
  2071. {
  2072. gi.Printf( S_COLOR_RED"ERROR: Commander time: %d\n", commTime );
  2073. }
  2074. else if ( commTime > 10 )
  2075. {
  2076. gi.Printf( S_COLOR_YELLOW"WARNING: Commander time: %d\n", commTime );
  2077. }
  2078. else if ( commTime > 2 )
  2079. {
  2080. gi.Printf( S_COLOR_GREEN"Commander time: %d\n", commTime );
  2081. }
  2082. #endif// AI_TIMERS
  2083. }
  2084. }
  2085. else if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
  2086. {//not already fleeing, and going to run
  2087. ST_Speech( NPC, SPEECH_COVER, 0 );
  2088. NPC_UpdateAngles( qtrue, qtrue );
  2089. return;
  2090. }
  2091. if ( !NPC->enemy )
  2092. {//WTF? somehow we lost our enemy?
  2093. NPC_BSST_Patrol();//FIXME: or patrol?
  2094. return;
  2095. }
  2096. if (NPCInfo->goalEntity && NPCInfo->goalEntity!=NPC->enemy)
  2097. {
  2098. NPCInfo->goalEntity = UpdateGoal();
  2099. }
  2100. enemyLOS = enemyCS = enemyInFOV = qfalse;
  2101. move = qtrue;
  2102. faceEnemy = qfalse;
  2103. shoot = qfalse;
  2104. hitAlly = qfalse;
  2105. VectorClear( impactPos );
  2106. enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
  2107. vec3_t enemyDir, shootDir;
  2108. VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir );
  2109. VectorNormalize( enemyDir );
  2110. AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL );
  2111. float dot = DotProduct( enemyDir, shootDir );
  2112. if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
  2113. {//enemy is in front of me or they're very close and not behind me
  2114. enemyInFOV = qtrue;
  2115. }
  2116. if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128
  2117. {//enemy within 128
  2118. if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) &&
  2119. (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
  2120. {//shooting an explosive, but enemy too close, switch to primary fire
  2121. NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
  2122. //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not...
  2123. }
  2124. }
  2125. else if ( enemyDist > 65536 )//256 squared
  2126. {
  2127. if ( NPC->client->ps.weapon == WP_DISRUPTOR )
  2128. {//sniping...
  2129. if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  2130. {//use primary fire
  2131. NPCInfo->scriptFlags |= SCF_ALT_FIRE;
  2132. //reset fire-timing variables
  2133. NPC_ChangeWeapon( NPC->client->ps.weapon );
  2134. NPC_UpdateAngles( qtrue, qtrue );
  2135. return;
  2136. }
  2137. }
  2138. }
  2139. //can we see our target?
  2140. if ( NPC_ClearLOS( NPC->enemy ) )
  2141. {
  2142. AI_GroupUpdateEnemyLastSeen( NPCInfo->group, NPC->enemy->currentOrigin );
  2143. NPCInfo->enemyLastSeenTime = level.time;
  2144. enemyLOS = qtrue;
  2145. if ( NPC->client->ps.weapon == WP_NONE )
  2146. {
  2147. enemyCS = qfalse;//not true, but should stop us from firing
  2148. NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon
  2149. }
  2150. else
  2151. {//can we shoot our target?
  2152. if ((enemyDist < MIN_ROCKET_DIST_SQUARED) &&
  2153. ((level.time - NPC->lastMoveTime)<5000) &&
  2154. (
  2155. (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER
  2156. || (NPC->client->ps.weapon == WP_CONCUSSION && !(NPCInfo->scriptFlags&SCF_ALT_FIRE))
  2157. || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE)))))
  2158. {
  2159. enemyCS = qfalse;//not true, but should stop us from firing
  2160. hitAlly = qtrue;//us!
  2161. //FIXME: if too close, run away!
  2162. }
  2163. else if ( enemyInFOV )
  2164. {//if enemy is FOV, go ahead and check for shooting
  2165. int hit = NPC_ShotEntity( NPC->enemy, impactPos );
  2166. gentity_t *hitEnt = &g_entities[hit];
  2167. if ( hit == NPC->enemy->s.number
  2168. || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
  2169. || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) )
  2170. {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
  2171. AI_GroupUpdateClearShotTime( NPCInfo->group );
  2172. enemyCS = qtrue;
  2173. NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
  2174. VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
  2175. }
  2176. else
  2177. {//Hmm, have to get around this bastard
  2178. NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
  2179. ST_ResolveBlockedShot( hit );
  2180. if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
  2181. {//would hit an ally, don't fire!!!
  2182. hitAlly = qtrue;
  2183. }
  2184. else
  2185. {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
  2186. }
  2187. }
  2188. }
  2189. else
  2190. {
  2191. enemyCS = qfalse;//not true, but should stop us from firing
  2192. }
  2193. }
  2194. }
  2195. else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) )
  2196. {
  2197. NPCInfo->enemyLastSeenTime = level.time;
  2198. faceEnemy = qtrue;
  2199. NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
  2200. }
  2201. if ( NPC->client->ps.weapon == WP_NONE )
  2202. {
  2203. faceEnemy = qfalse;
  2204. shoot = qfalse;
  2205. }
  2206. else
  2207. {
  2208. if ( enemyLOS )
  2209. {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
  2210. faceEnemy = qtrue;
  2211. }
  2212. if ( enemyCS )
  2213. {
  2214. shoot = qtrue;
  2215. }
  2216. }
  2217. //Check for movement to take care of
  2218. ST_CheckMoveState();
  2219. //See if we should override shooting decision with any special considerations
  2220. ST_CheckFireState();
  2221. if ( faceEnemy )
  2222. {//face the enemy
  2223. NPC_FaceEnemy( qtrue );
  2224. }
  2225. if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
  2226. {//not supposed to chase my enemies
  2227. if ( NPCInfo->goalEntity == NPC->enemy )
  2228. {//goal is my entity, so don't move
  2229. move = qfalse;
  2230. }
  2231. }
  2232. else if (NPC->NPC->scriptFlags&SCF_NO_GROUPS)
  2233. {
  2234. // NPCInfo->goalEntity = UpdateGoal();
  2235. NPCInfo->goalEntity = (enemyLOS)?(0):(NPC->enemy);
  2236. }
  2237. if ( NPC->client->fireDelay && NPC->s.weapon == WP_ROCKET_LAUNCHER )
  2238. {
  2239. move = qfalse;
  2240. }
  2241. if ( !ucmd.rightmove )
  2242. {//only if not already strafing for some strange reason...?
  2243. //NOTE: these are never set here, but can be set in AI_Jedi.cpp for those NPCs who are sort of Stormtrooper/Jedi hybrids
  2244. //NOTE: this stomps navigation movement entirely!
  2245. //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too
  2246. if ( !TIMER_Done( NPC, "strafeLeft" ) )
  2247. {
  2248. /*
  2249. if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 )
  2250. {//we want to turn left, don't apply the strafing
  2251. }
  2252. else
  2253. */
  2254. {//go ahead and strafe left
  2255. ucmd.rightmove = -127;
  2256. //re-check the duck as we might want to be rolling
  2257. VectorClear( NPC->client->ps.moveDir );
  2258. move = qfalse;
  2259. }
  2260. }
  2261. else if ( !TIMER_Done( NPC, "strafeRight" ) )
  2262. {
  2263. /*if ( NPCInfo->desiredYaw < NPC->client->ps.viewangles[YAW] - 60 )
  2264. {//we want to turn right, don't apply the strafing
  2265. }
  2266. else
  2267. */
  2268. {//go ahead and strafe left
  2269. ucmd.rightmove = 127;
  2270. VectorClear( NPC->client->ps.moveDir );
  2271. move = qfalse;
  2272. }
  2273. }
  2274. }
  2275. if ( NPC->client->ps.legsAnim == BOTH_GUARD_LOOKAROUND1 )
  2276. {//don't move when doing silly look around thing
  2277. move = qfalse;
  2278. }
  2279. if ( move )
  2280. {//move toward goal
  2281. if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared
  2282. {
  2283. move = ST_Move();
  2284. if ( (NPC->client->NPC_class != CLASS_ROCKETTROOPER||NPC->s.weapon!=WP_ROCKET_LAUNCHER||enemyDist<MIN_ROCKET_DIST_SQUARED)//rockettroopers who use rocket launchers turn around and run if you get too close (closer than 128)
  2285. && ucmd.forwardmove <= -32 )
  2286. {//moving backwards at least 45 degrees
  2287. if ( NPCInfo->goalEntity
  2288. && DistanceSquared( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin ) > MIN_TURN_AROUND_DIST_SQ )
  2289. {//don't stop running backwards if your goal is less than 100 away
  2290. if ( TIMER_Done( NPC, "runBackwardsDebounce" ) )
  2291. {//not already waiting for next run backwards
  2292. if ( !TIMER_Exists( NPC, "runningBackwards" ) )
  2293. {//start running backwards
  2294. TIMER_Set( NPC, "runningBackwards", Q_irand( 500, 1000 ) );//Q_irand( 2000, 3500 ) );
  2295. }
  2296. else if ( TIMER_Done2( NPC, "runningBackwards", qtrue ) )
  2297. {//done running backwards
  2298. TIMER_Set( NPC, "runBackwardsDebounce", Q_irand( 3000, 5000 ) );
  2299. }
  2300. }
  2301. }
  2302. }
  2303. else
  2304. {//not running backwards
  2305. //TIMER_Remove( NPC, "runningBackwards" );
  2306. }
  2307. }
  2308. else
  2309. {
  2310. move = qfalse;
  2311. }
  2312. }
  2313. if ( !move )
  2314. {
  2315. if (NPC->client->NPC_class != CLASS_ASSASSIN_DROID)
  2316. {
  2317. if ( !TIMER_Done( NPC, "duck" ) )
  2318. {
  2319. ucmd.upmove = -127;
  2320. }
  2321. }
  2322. //FIXME: what about leaning?
  2323. }
  2324. else
  2325. {//stop ducking!
  2326. TIMER_Set( NPC, "duck", -1 );
  2327. }
  2328. if ( NPC->client->NPC_class == CLASS_REBORN//cultist using a gun
  2329. && NPCInfo->rank >= RANK_LT_COMM //commando or better
  2330. && NPC->enemy->s.weapon == WP_SABER )//fighting a saber-user
  2331. {//commando saboteur vs. jedi/reborn
  2332. //see if we need to avoid their saber
  2333. NPC_EvasionSaber();
  2334. }
  2335. if ( //!TIMER_Done( NPC, "flee" ) ||
  2336. (move&&!TIMER_Done( NPC, "runBackwardsDebounce" )) )
  2337. {//running away
  2338. faceEnemy = qfalse;
  2339. }
  2340. //FIXME: check scf_face_move_dir here?
  2341. if ( !faceEnemy )
  2342. {//we want to face in the dir we're running
  2343. if ( !move )
  2344. {//if we haven't moved, we should look in the direction we last looked?
  2345. VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles );
  2346. }
  2347. NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
  2348. NPCInfo->desiredPitch = 0;
  2349. NPC_UpdateAngles( qtrue, qtrue );
  2350. if ( move )
  2351. {//don't run away and shoot
  2352. shoot = qfalse;
  2353. }
  2354. }
  2355. if ( NPCInfo->scriptFlags & SCF_DONT_FIRE )
  2356. {
  2357. shoot = qfalse;
  2358. }
  2359. if ( NPC->enemy && NPC->enemy->enemy )
  2360. {
  2361. if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER )
  2362. {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH)
  2363. shoot = qfalse;
  2364. }
  2365. }
  2366. //FIXME: don't shoot right away!
  2367. if ( NPC->client->fireDelay )
  2368. {
  2369. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  2370. {
  2371. Saboteur_Decloak( NPC );
  2372. }
  2373. if ( NPC->s.weapon == WP_ROCKET_LAUNCHER
  2374. || (NPC->s.weapon==WP_CONCUSSION&&!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) )
  2375. {
  2376. if ( !enemyLOS || !enemyCS )
  2377. {//cancel it
  2378. NPC->client->fireDelay = 0;
  2379. }
  2380. else
  2381. {//delay our next attempt
  2382. TIMER_Set( NPC, "attackDelay", Q_irand( 3000, 5000 ) );
  2383. }
  2384. }
  2385. }
  2386. else if ( shoot )
  2387. {//try to shoot if it's time
  2388. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  2389. {
  2390. Saboteur_Decloak( NPC );
  2391. }
  2392. if ( TIMER_Done( NPC, "attackDelay" ) )
  2393. {
  2394. if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
  2395. {
  2396. WeaponThink( qtrue );
  2397. }
  2398. //NASTY
  2399. if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
  2400. {
  2401. if ( (ucmd.buttons&BUTTON_ATTACK)
  2402. && !move
  2403. && g_spskill->integer > 1
  2404. && !Q_irand( 0, 3 ) )
  2405. {//every now and then, shoot a homing rocket
  2406. ucmd.buttons &= ~BUTTON_ATTACK;
  2407. ucmd.buttons |= BUTTON_ALT_ATTACK;
  2408. NPC->client->fireDelay = Q_irand( 1000, 2500 );
  2409. }
  2410. }
  2411. else if ( NPC->s.weapon == WP_NOGHRI_STICK
  2412. && enemyDist < (48*48) )//?
  2413. {
  2414. ucmd.buttons &= ~BUTTON_ATTACK;
  2415. ucmd.buttons |= BUTTON_ALT_ATTACK;
  2416. NPC->client->fireDelay = Q_irand( 1500, 2000 );
  2417. }
  2418. }
  2419. }
  2420. else
  2421. {
  2422. if ( NPC->attackDebounceTime < level.time )
  2423. {
  2424. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  2425. {
  2426. Saboteur_Cloak( NPC );
  2427. }
  2428. }
  2429. }
  2430. }
  2431. extern qboolean G_TuskenAttackAnimDamage( gentity_t *self );
  2432. void NPC_BSST_Default( void )
  2433. {
  2434. if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
  2435. {
  2436. WeaponThink( qtrue );
  2437. }
  2438. if ( NPC->s.weapon == WP_NOGHRI_STICK )
  2439. {
  2440. if ( G_TuskenAttackAnimDamage( NPC ) )
  2441. {
  2442. Noghri_StickTrace();
  2443. }
  2444. }
  2445. if( !NPC->enemy )
  2446. {//don't have an enemy, look for one
  2447. NPC_BSST_Patrol();
  2448. }
  2449. else //if ( NPC->enemy )
  2450. {//have an enemy
  2451. if ( NPC->enemy->client //enemy is a client
  2452. && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught
  2453. && NPC->enemy->enemy != NPC//enemy's enemy is not me
  2454. && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || (NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR&&NPC->enemy->enemy->client->NPC_class!=CLASS_WAMPA)) )//enemy's enemy is not a client or is not a wampa or rancor (which is scarier than me)
  2455. {//they should be scared of ME and no-one else
  2456. G_SetEnemy( NPC->enemy, NPC );
  2457. }
  2458. NPC_CheckGetNewWeapon();
  2459. NPC_BSST_Attack();
  2460. }
  2461. }