1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 #include "cg_headers.h"
25
26 #define CG_PLAYERS_CPP
27 #include "cg_media.h"
28 #include "FxScheduler.h"
29 #include "../game/ghoul2_shared.h"
30 #include "../game/anims.h"
31 #include "../game/wp_saber.h"
32 #include "../game/g_vehicles.h"
33 #include "../Rufl/hstring.h"
34
35 #define LOOK_SWING_SCALE 0.5f
36 #define CG_SWINGSPEED 0.3f
37
38 #include "animtable.h"
39
40 extern qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum );
41 extern void WP_SaberSwingSound( gentity_t *ent, int saberNum, swingType_t swingType );
42
43 extern vmCvar_t cg_debugHealthBars;
44 /*
45
46 player entities generate a great deal of information from implicit ques
47 taken from the entityState_t
48
49 */
50
51 //rww - generic function for applying a shader to the skin.
52 extern vmCvar_t cg_g2Marks;
CG_AddGhoul2Mark(int type,float size,vec3_t hitloc,vec3_t hitdirection,int entnum,vec3_t entposition,float entangle,CGhoul2Info_v & ghoul2,vec3_t modelScale,int lifeTime,int firstModel,vec3_t uaxis)53 void CG_AddGhoul2Mark(int type, float size, vec3_t hitloc, vec3_t hitdirection,
54 int entnum, vec3_t entposition, float entangle, CGhoul2Info_v &ghoul2, vec3_t modelScale, int lifeTime, int firstModel, vec3_t uaxis )
55 {
56 if ( !cg_g2Marks.integer )
57 {//don't want these
58 return;
59 }
60
61 static SSkinGoreData goreSkin;
62
63 memset ( &goreSkin, 0, sizeof(goreSkin) );
64
65 goreSkin.growDuration = -1; // do not grow
66 goreSkin.goreScaleStartFraction = 1.0; // default start scale
67 goreSkin.frontFaces = true; // yes front
68 goreSkin.backFaces = false; // no back
69 goreSkin.lifeTime = lifeTime;
70 goreSkin.firstModel = firstModel;
71 /*
72 //NOTE: sorry, have to disable fade-out of marks, causes sorting issues
73 if (lifeTime > 0)
74 {
75 goreSkin.fadeOutTime = lifeTime*0.1; //use whatever you want here -rww
76 }
77 goreSkin.fadeRGB = true; //fade on RGB and alpha instead of just alpha (not needed for all shaders, but whatever)
78 */
79
80 goreSkin.currentTime = cg.time;
81 goreSkin.entNum = entnum;
82 goreSkin.SSize = size;
83 goreSkin.TSize = size;
84 goreSkin.shader = type;
85 goreSkin.theta = flrand(0.0f,6.28f);
86
87 if (uaxis)
88 {
89 goreSkin.backFaces = true;
90 goreSkin.SSize = 6;
91 goreSkin.TSize = 3;
92 goreSkin.depthStart = -10; //arbitrary depths, just limiting marks to near hit loc
93 goreSkin.depthEnd = 15;
94 goreSkin.useTheta = false;
95 VectorCopy(uaxis, goreSkin.uaxis);
96 if( VectorNormalize(goreSkin.uaxis) < 0.001f )
97 {//too short to make a mark
98 return;
99 }
100 }
101 else
102 {
103 goreSkin.depthStart = -1000;
104 goreSkin.depthEnd = 1000;
105 goreSkin.useTheta = true;
106 }
107 VectorCopy(modelScale, goreSkin.scale);
108
109 if ( VectorCompare( hitdirection, vec3_origin ) )
110 {//wtf, no dir? Make one up
111 VectorSubtract( entposition, hitloc, goreSkin.rayDirection);
112 VectorNormalize( goreSkin.rayDirection );
113 }
114 else
115 {//use passed in value
116 VectorCopy ( hitdirection, goreSkin.rayDirection);
117 }
118
119 VectorCopy ( hitloc, goreSkin.hitLocation );
120 VectorCopy ( entposition, goreSkin.position );
121 goreSkin.angles[YAW] = entangle;
122
123 gi.G2API_AddSkinGore(ghoul2,goreSkin);
124 }
125
126 qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *headModelName, const char *headSkinName,
127 const char *torsoModelName, const char *torsoSkinName,
128 const char *legsModelName, const char *legsSkinName );
129
130 static void CG_PlayerFootsteps( centity_t *const cent, footstepType_t footStepType );
131 static void CG_PlayerAnimEvents( int animFileIndex, qboolean torso, int oldFrame, int frame, int entNum );
132 extern void BG_G2SetBoneAngles( centity_t *cent, gentity_t *gent, int boneIndex, const vec3_t angles, const int flags,
133 const Eorientations up, const Eorientations left, const Eorientations forward, qhandle_t *modelList );
134 extern void FX_BorgDeathSparkParticles( vec3_t origin, vec3_t angles, vec3_t vel, vec3_t user );
135 extern qboolean PM_SaberInSpecialAttack( int anim );
136 extern qboolean PM_SaberInAttack( int move );
137 extern qboolean PM_SaberInTransitionAny( int move );
138 extern int PM_GetTurnAnim( gentity_t *gent, int anim );
139 extern int PM_AnimLength( int index, animNumber_t anim );
140 extern qboolean PM_InRoll( playerState_t *ps );
141 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
142 extern qboolean PM_SuperBreakWinAnim( int anim );
143
144 //Basic set of custom sounds that everyone needs
145 // (keep numbers in ascending order in order for variant-capping to work)
146 const char *cg_customBasicSoundNames[MAX_CUSTOM_BASIC_SOUNDS] =
147 {
148 "*death1.wav",
149 "*death2.wav",
150 "*death3.wav",
151 "*jump1.wav",
152 "*pain25.wav",
153 "*pain50.wav",
154 "*pain75.wav",
155 "*pain100.wav",
156 "*gurp1.wav",
157 "*gurp2.wav",
158 "*drown.wav",
159 "*gasp.wav",
160 "*land1.wav",
161 "*falling1.wav",
162 };
163
164 //Used as a supplement to the basic set for enemies and hazard team
165 // (keep numbers in ascending order in order for variant-capping to work)
166 const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS] =
167 {
168 "*anger1.wav", //Say when acquire an enemy when didn't have one before
169 "*anger2.wav",
170 "*anger3.wav",
171 "*victory1.wav", //Say when killed an enemy
172 "*victory2.wav",
173 "*victory3.wav",
174 "*confuse1.wav", //Say when confused
175 "*confuse2.wav",
176 "*confuse3.wav",
177 "*pushed1.wav", //Say when force-pushed
178 "*pushed2.wav",
179 "*pushed3.wav",
180 "*choke1.wav",
181 "*choke2.wav",
182 "*choke3.wav",
183 "*ffwarn.wav",
184 "*ffturn.wav",
185 };
186
187 //Used as a supplement to the basic set for stormtroopers
188 // (keep numbers in ascending order in order for variant-capping to work)
189 const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS] =
190 {
191 "*chase1.wav",
192 "*chase2.wav",
193 "*chase3.wav",
194 "*cover1.wav",
195 "*cover2.wav",
196 "*cover3.wav",
197 "*cover4.wav",
198 "*cover5.wav",
199 "*detected1.wav",
200 "*detected2.wav",
201 "*detected3.wav",
202 "*detected4.wav",
203 "*detected5.wav",
204 "*lost1.wav",
205 "*outflank1.wav",
206 "*outflank2.wav",
207 "*escaping1.wav",
208 "*escaping2.wav",
209 "*escaping3.wav",
210 "*giveup1.wav",
211 "*giveup2.wav",
212 "*giveup3.wav",
213 "*giveup4.wav",
214 "*look1.wav",
215 "*look2.wav",
216 "*sight1.wav",
217 "*sight2.wav",
218 "*sight3.wav",
219 "*sound1.wav",
220 "*sound2.wav",
221 "*sound3.wav",
222 "*suspicious1.wav",
223 "*suspicious2.wav",
224 "*suspicious3.wav",
225 "*suspicious4.wav",
226 "*suspicious5.wav",
227 };
228
229 //Used as a supplement to the basic set for jedi
230 // (keep numbers in ascending order in order for variant-capping to work)
231 const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS] =
232 {
233 "*combat1.wav",
234 "*combat2.wav",
235 "*combat3.wav",
236 "*jdetected1.wav",
237 "*jdetected2.wav",
238 "*jdetected3.wav",
239 "*taunt1.wav",
240 "*taunt2.wav",
241 "*taunt3.wav",
242 "*jchase1.wav",
243 "*jchase2.wav",
244 "*jchase3.wav",
245 "*jlost1.wav",
246 "*jlost2.wav",
247 "*jlost3.wav",
248 "*deflect1.wav",
249 "*deflect2.wav",
250 "*deflect3.wav",
251 "*gloat1.wav",
252 "*gloat2.wav",
253 "*gloat3.wav",
254 "*pushfail.wav",
255 };
256
257
258 // done at registration time only...
259 //
260 // cuts down on sound-variant registration for low end machines,
261 // eg *gloat1.wav (plus...2,...3) can be capped to all be just *gloat1.wav
262 //
GetCustomSound_VariantCapped(const char * ppsTable[],int iEntryNum,qboolean bForceVariant1)263 static const char *GetCustomSound_VariantCapped(const char *ppsTable[], int iEntryNum, qboolean bForceVariant1)
264 {
265 extern vmCvar_t cg_VariantSoundCap;
266
267 // const int iVariantCap = 2; // test
268 const int &iVariantCap = cg_VariantSoundCap.integer;
269
270 if (iVariantCap || bForceVariant1)
271 {
272 char *p = (char *)strchr(ppsTable[iEntryNum],'.');
273 if (p && p-2 > ppsTable[iEntryNum] && isdigit(p[-1]) && !isdigit(p[-2]))
274 {
275 int iThisVariant = p[-1]-'0';
276
277 if (iThisVariant > iVariantCap || bForceVariant1)
278 {
279 // ok, let's not load this variant, so pick a random one below the cap value...
280 //
281 for (int i=0; i<2; i++) // 1st pass, choose random, 2nd pass (if random not in list), choose xxx1, else fall through...
282 {
283 char sName[MAX_QPATH];
284
285 Q_strncpyz(sName, ppsTable[iEntryNum], sizeof(sName));
286 p = strchr(sName,'.');
287 if (p)
288 {
289 *p = '\0';
290 sName[strlen(sName)-1] = '\0'; // strip the digit
291
292 int iRandom = bForceVariant1 ? 1 : (!i ? Q_irand(1,iVariantCap) : 1);
293
294 strcat(sName,va("%d",iRandom));
295
296 // does this exist in the entries before the original one?...
297 //
298 for (int iScanNum=0; iScanNum<iEntryNum; iScanNum++)
299 {
300 if (!Q_stricmp(ppsTable[iScanNum], sName))
301 {
302 // yeah, this entry is also present in the table, so ok to return it
303 //
304 return ppsTable[iScanNum];
305 }
306 }
307 }
308 }
309
310 // didn't find an entry corresponding to either the random name, or the xxxx1 version,
311 // so give up and drop through to return the original...
312 //
313 }
314 }
315 }
316
317 return ppsTable[iEntryNum];
318 }
319
320 extern cvar_t *g_sex;
321 extern cvar_t *com_buildScript;
CG_RegisterCustomSounds(clientInfo_t * ci,int iSoundEntryBase,int iTableEntries,const char * ppsTable[],const char * psDir)322 static void CG_RegisterCustomSounds(clientInfo_t *ci, int iSoundEntryBase,
323 int iTableEntries, const char *ppsTable[], const char *psDir
324 )
325 {
326 for ( int i=0 ; i<iTableEntries; i++ )
327 {
328 char s[MAX_QPATH]={0};
329 const char *pS = GetCustomSound_VariantCapped(ppsTable,i, qfalse);
330 COM_StripExtension( pS, s, sizeof(s) );
331
332 sfxHandle_t hSFX = 0;
333 if ( g_sex->string[0] == 'f' )
334 {
335 hSFX = cgi_S_RegisterSound( va("sound/chars/%s/misc/%s_f.wav", psDir, s + 1) );
336 }
337 if (hSFX == 0 || com_buildScript->integer)
338 {
339 hSFX = cgi_S_RegisterSound( va("sound/chars/%s/misc/%s.wav", psDir, s + 1) );
340 }
341 if (hSFX == 0)
342 {
343 // hmmm... variant in table was missing, so forcibly-retry with %1 version (which we may have just tried, but wtf?)...
344 //
345 pS = GetCustomSound_VariantCapped(ppsTable,i, qtrue);
346 COM_StripExtension( pS, s, sizeof(s) );
347 if ( g_sex->string[0] == 'f' )
348 {
349 hSFX = cgi_S_RegisterSound( va("sound/chars/%s/misc/%s_f.wav", psDir, s + 1) );
350 }
351 if (hSFX == 0 || com_buildScript->integer)
352 {
353 hSFX = cgi_S_RegisterSound( va("sound/chars/%s/misc/%s.wav", psDir, s + 1) );
354 }
355 //
356 // and fall through regardless...
357 //
358 }
359
360 ci->sounds[i + iSoundEntryBase] = hSFX;
361 }
362 }
363
364
365
366 /*
367 ================
368 CG_CustomSound
369
370 NOTE: when you call this, check the value. If zero, do not try to play the sound.
371 Either that or when a sound that doesn't exist is played, don't play the null
372 sound honk and don't display the error message
373
374 ================
375 */
CG_CustomSound(int entityNum,const char * soundName,int customSoundSet)376 static sfxHandle_t CG_CustomSound( int entityNum, const char *soundName, int customSoundSet )
377 {
378 clientInfo_t *ci;
379 int i;
380
381 if ( soundName[0] != '*' )
382 {
383 return cgi_S_RegisterSound( soundName );
384 }
385
386 if ( !g_entities[entityNum].client )
387 {
388 // No client, this should never happen, so just don't
389 #ifndef FINAL_BUILD
390 // CG_Printf( "custom sound not on client: %s", soundName );
391 #endif
392 return 0;
393 }
394 else
395 {
396 ci = &g_entities[entityNum].client->clientInfo;
397 }
398
399 //FIXME: if the sound you want to play could not be found, pick another from the same
400 //general grouping? ie: if you want ff_2c and there is none, try ff_2b or ff_2a...
401 switch ( customSoundSet )
402 {
403 case CS_BASIC:
404 // There should always be a clientInfo structure if there is a client, but just make sure...
405 if ( ci )
406 {
407 for ( i = 0 ; i < MAX_CUSTOM_BASIC_SOUNDS && cg_customBasicSoundNames[i] ; i++ )
408 {
409 if ( !Q_stricmp( soundName, cg_customBasicSoundNames[i] ) )
410 {
411 return ci->sounds[i];
412 }
413 }
414 }
415 break;
416 case CS_COMBAT:
417 // There should always be a clientInfo structure if there is a client, but just make sure...
418 if ( ci )
419 {
420 for ( i = 0 ; i < MAX_CUSTOM_COMBAT_SOUNDS && cg_customCombatSoundNames[i] ; i++ )
421 {
422 if ( !Q_stricmp( soundName, cg_customCombatSoundNames[i] ) )
423 {
424 return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS];
425 }
426 }
427 }
428 break;
429 case CS_EXTRA:
430 // There should always be a clientInfo structure if there is a client, but just make sure...
431 if ( ci )
432 {
433 for ( i = 0 ; i < MAX_CUSTOM_EXTRA_SOUNDS && cg_customExtraSoundNames[i] ; i++ )
434 {
435 if ( !Q_stricmp( soundName, cg_customExtraSoundNames[i] ) )
436 {
437 return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS];
438 }
439 }
440 }
441 break;
442 case CS_JEDI:
443 // There should always be a clientInfo structure if there is a client, but just make sure...
444 if ( ci )
445 {
446 for ( i = 0 ; i < MAX_CUSTOM_JEDI_SOUNDS && cg_customJediSoundNames[i] ; i++ )
447 {
448 if ( !Q_stricmp( soundName, cg_customJediSoundNames[i] ) )
449 {
450 return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS+MAX_CUSTOM_EXTRA_SOUNDS];
451 }
452 }
453 }
454 break;
455 case CS_TRY_ALL:
456 default:
457 //no set specified, search all
458 if ( ci )
459 {
460 for ( i = 0 ; i < MAX_CUSTOM_BASIC_SOUNDS && cg_customBasicSoundNames[i] ; i++ )
461 {
462 if ( !Q_stricmp( soundName, cg_customBasicSoundNames[i] ) )
463 {
464 return ci->sounds[i];
465 }
466 }
467 for ( i = 0 ; i < MAX_CUSTOM_COMBAT_SOUNDS && cg_customCombatSoundNames[i] ; i++ )
468 {
469 if ( !Q_stricmp( soundName, cg_customCombatSoundNames[i] ) )
470 {
471 return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS];
472 }
473 }
474 for ( i = 0 ; i < MAX_CUSTOM_EXTRA_SOUNDS && cg_customExtraSoundNames[i] ; i++ )
475 {
476 if ( !Q_stricmp( soundName, cg_customExtraSoundNames[i] ) )
477 {
478 return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS];
479 }
480 }
481 for ( i = 0 ; i < MAX_CUSTOM_JEDI_SOUNDS && cg_customJediSoundNames[i] ; i++ )
482 {
483 if ( !Q_stricmp( soundName, cg_customJediSoundNames[i] ) )
484 {
485 return ci->sounds[i+MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS+MAX_CUSTOM_EXTRA_SOUNDS];
486 }
487 }
488 }
489 break;
490 }
491
492 #ifdef FINAL_BUILD
493 CG_Printf( "Unknown custom sound: %s", soundName );
494 #else
495 CG_Error( "Unknown custom sound: %s", soundName );
496 #endif
497 return 0;
498 }
499
CG_TryPlayCustomSound(vec3_t origin,int entityNum,soundChannel_t channel,const char * soundName,int customSoundSet)500 qboolean CG_TryPlayCustomSound( vec3_t origin, int entityNum, soundChannel_t channel, const char *soundName, int customSoundSet )
501 {
502 sfxHandle_t soundIndex = CG_CustomSound( entityNum, soundName, customSoundSet );
503 if ( !soundIndex )
504 {
505 return qfalse;
506 }
507
508 cgi_S_StartSound( origin, entityNum, channel, soundIndex );
509 return qtrue;
510 }
511 /*
512 ======================
513 CG_NewClientinfo
514
515 For player only, NPCs get them through NPC_stats and G_ModelIndex
516 ======================
517 */
CG_NewClientinfo(int clientNum)518 void CG_NewClientinfo( int clientNum )
519 {
520 clientInfo_t *ci;
521 const char *configstring;
522 const char *v;
523 // const char *s;
524 // int i;
525
526 configstring = CG_ConfigString( clientNum + CS_PLAYERS );
527
528 if ( !configstring[0] )
529 {
530 return; // player just left
531 }
532 //ci = &cgs.clientinfo[clientNum];
533 if ( !(g_entities[clientNum].client) )
534 {
535 return;
536 }
537 ci = &g_entities[clientNum].client->clientInfo;
538
539 // isolate the player's name
540 v = Info_ValueForKey(configstring, "n");
541 Q_strncpyz( ci->name, v, sizeof( ci->name ) );
542
543 // handicap
544 v = Info_ValueForKey( configstring, "hc" );
545 ci->handicap = atoi( v );
546
547 // team
548 v = Info_ValueForKey( configstring, "t" );
549 ci->team = (team_t) atoi( v );
550
551 // legsModel
552 v = Info_ValueForKey( configstring, "legsModel" );
553
554 Q_strncpyz( g_entities[clientNum].client->renderInfo.legsModelName, v,
555 sizeof( g_entities[clientNum].client->renderInfo.legsModelName));
556
557 // torsoModel
558 v = Info_ValueForKey( configstring, "torsoModel" );
559
560 Q_strncpyz( g_entities[clientNum].client->renderInfo.torsoModelName, v,
561 sizeof( g_entities[clientNum].client->renderInfo.torsoModelName));
562
563 // headModel
564 v = Info_ValueForKey( configstring, "headModel" );
565
566 Q_strncpyz( g_entities[clientNum].client->renderInfo.headModelName, v,
567 sizeof( g_entities[clientNum].client->renderInfo.headModelName));
568
569 // sounds
570 v = Info_ValueForKey( configstring, "snd" );
571
572 ci->customBasicSoundDir = G_NewString( v );
573
574 //player uses only the basic custom and combat sound sets, not the extra or jedi
575 CG_RegisterCustomSounds(ci,
576 0, // int iSoundEntryBase,
577 MAX_CUSTOM_BASIC_SOUNDS, // int iTableEntries,
578 cg_customBasicSoundNames, // const char *ppsTable[],
579 ci->customBasicSoundDir // const char *psDir
580 );
581
582 CG_RegisterCustomSounds(ci,
583 MAX_CUSTOM_BASIC_SOUNDS, // int iSoundEntryBase,
584 MAX_CUSTOM_COMBAT_SOUNDS, // int iTableEntries,
585 cg_customCombatSoundNames, // const char *ppsTable[],
586 ci->customBasicSoundDir // const char *psDir
587 );
588 ci->infoValid = qfalse;
589 }
590
591 /*
592 CG_RegisterNPCCustomSounds
593 */
CG_RegisterNPCCustomSounds(clientInfo_t * ci)594 void CG_RegisterNPCCustomSounds( clientInfo_t *ci )
595 {
596 // const char *s;
597 // int i;
598
599 // sounds
600
601 if ( ci->customBasicSoundDir && ci->customBasicSoundDir[0] )
602 {
603 CG_RegisterCustomSounds(ci,
604 0, // int iSoundEntryBase,
605 MAX_CUSTOM_BASIC_SOUNDS, // int iTableEntries,
606 cg_customBasicSoundNames, // const char *ppsTable[],
607 ci->customBasicSoundDir // const char *psDir
608 );
609 }
610
611 if ( ci->customCombatSoundDir && ci->customCombatSoundDir[0] )
612 {
613 CG_RegisterCustomSounds(ci,
614 MAX_CUSTOM_BASIC_SOUNDS, // int iSoundEntryBase,
615 MAX_CUSTOM_COMBAT_SOUNDS, // int iTableEntries,
616 cg_customCombatSoundNames, // const char *ppsTable[],
617 ci->customCombatSoundDir // const char *psDir
618 );
619 }
620
621 if ( ci->customExtraSoundDir && ci->customExtraSoundDir[0] )
622 {
623 CG_RegisterCustomSounds(ci,
624 MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS, // int iSoundEntryBase,
625 MAX_CUSTOM_EXTRA_SOUNDS, // int iTableEntries,
626 cg_customExtraSoundNames, // const char *ppsTable[],
627 ci->customExtraSoundDir // const char *psDir
628 );
629 }
630
631 if ( ci->customJediSoundDir && ci->customJediSoundDir[0] )
632 {
633 CG_RegisterCustomSounds(ci,
634 MAX_CUSTOM_BASIC_SOUNDS+MAX_CUSTOM_COMBAT_SOUNDS+MAX_CUSTOM_EXTRA_SOUNDS, // int iSoundEntryBase,
635 MAX_CUSTOM_JEDI_SOUNDS, // int iTableEntries,
636 cg_customJediSoundNames, // const char *ppsTable[],
637 ci->customJediSoundDir // const char *psDir
638 );
639 }
640 }
641
642 //=============================================================================
643
644 /*
645 void CG_RegisterNPCEffects( team_t team )
646
647 This should register all the shaders, models and sounds used by a specific type
648 of NPC's spawn, death and other miscellaneous effects. NOT WEAPON EFFECTS, as those
649 are taken care of in CG_RegisterWeapon
650 */
651 /*
652 void CG_RegisterNPCEffects( team_t team )
653 {
654 switch( team )
655 {
656
657 case TEAM_ENEMY:
658 break;
659
660 case TEAM_NEUTRAL:
661 break;
662
663 case TEAM_PLAYER:
664 break;
665
666 default:
667 break;
668 }
669 }
670 */
671 /*
672 =============================================================================
673
674 PLAYER ANIMATION
675
676 =============================================================================
677 */
678
ValidAnimFileIndex(int index)679 qboolean ValidAnimFileIndex ( int index )
680 {
681 if ( index < 0 || index >= level.numKnownAnimFileSets )
682 {
683 Com_Printf( S_COLOR_RED "Bad animFileIndex: %d\n", index );
684 return qfalse;
685 }
686
687 return qtrue;
688 }
689
690
691 /*
692 ======================
693 CG_ClearAnimEvtCache
694
695 resets all the eventcache so that a vid restart will recache them
696 ======================
697 */
CG_ClearAnimEvtCache(void)698 void CG_ClearAnimEvtCache( void )
699 {
700 int i;
701 for (i=0; i < level.numKnownAnimFileSets; i++)
702 {
703 // TODO: Make this work again?
704 // level.knownAnimFileSets[i].eventsParsed = qfalse;
705 }
706 }
707
708 /*
709 ===============
710 CG_SetLerpFrameAnimation
711 ===============
712 */
CG_SetLerpFrameAnimation(clientInfo_t * ci,lerpFrame_t * lf,int newAnimation)713 static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation )
714 {
715 animation_t *anim;
716
717 if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS )
718 {
719 #ifdef FINAL_BUILD
720 newAnimation = 0;
721 #else
722 CG_Error( "Bad animation number: %i for ", newAnimation, ci->name );
723 #endif
724 }
725
726 lf->animationNumber = newAnimation;
727
728 if ( !ValidAnimFileIndex( ci->animFileIndex ) )
729 {
730 #ifdef FINAL_BUILD
731 ci->animFileIndex = 0;
732 #else
733 CG_Error( "Bad animFileIndex: %i for %s", ci->animFileIndex, ci->name);
734 #endif
735 }
736
737 anim = &level.knownAnimFileSets[ci->animFileIndex].animations[ newAnimation ];
738
739 lf->animation = anim;
740 lf->animationTime = lf->frameTime + abs(anim->frameLerp);
741 }
742
743 /*
744 ===============
745 CG_RunLerpFrame
746
747 Sets cg.snap, cg.oldFrame, and cg.backlerp
748 cg.time should be between oldFrameTime and frameTime after exit
749 ===============
750 */
CG_RunLerpFrame(clientInfo_t * ci,lerpFrame_t * lf,int newAnimation,float fpsMod,int entNum)751 static qboolean CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float fpsMod, int entNum ) {
752 int f, animFrameTime;
753 animation_t *anim;
754 qboolean newFrame = qfalse;
755
756 if(fpsMod > 2 || fpsMod < 0.5)
757 {//should have been set right
758 fpsMod = 1.0f;
759 }
760
761 // see if the animation sequence is switching
762 //FIXME: allow multiple-frame overlapped lerping between sequences? - Possibly last 3 of last seq and first 3 of next seq?
763 if ( newAnimation != lf->animationNumber || !lf->animation )
764 {
765 CG_SetLerpFrameAnimation( ci, lf, newAnimation );
766 }
767
768 // if we have passed the current frame, move it to
769 // oldFrame and calculate a new frame
770 if ( cg.time >= lf->frameTime )
771 {
772 lf->oldFrame = lf->frame;
773 lf->oldFrameTime = lf->frameTime;
774
775 // get the next frame based on the animation
776 anim = lf->animation;
777 //Do we need to speed up or slow down the anim?
778 /*if(fpsMod != 1.0)
779 {//Note! despite it's name, a higher fpsMod slows down the anim, a lower one speeds it up
780 animFrameTime = ceil(lf->frameTime * fpsMod);
781 }
782 else*/
783 {
784 animFrameTime = abs(anim->frameLerp);
785
786 //special hack for player to ensure quick weapon change
787 if ( entNum == 0 )
788 {
789 if ( lf->animationNumber == TORSO_DROPWEAP1 || lf->animationNumber == TORSO_RAISEWEAP1 )
790 {
791 animFrameTime = 50;
792 }
793 }
794 }
795
796 if ( cg.time < lf->animationTime )
797 {
798 lf->frameTime = lf->animationTime; // initial lerp
799 }
800 else
801 {
802 lf->frameTime = lf->oldFrameTime + animFrameTime;
803 }
804
805 f = ( lf->frameTime - lf->animationTime ) / animFrameTime;
806 if ( f >= anim->numFrames )
807 {//Reached the end of the anim
808 //FIXME: Need to set a flag here to TASK_COMPLETE
809 f -= anim->numFrames;
810 if ( anim->loopFrames != -1 ) //Before 0 meant no loop
811 {
812 if(anim->numFrames - anim->loopFrames == 0)
813 {
814 f %= anim->numFrames;
815 }
816 else
817 {
818 f %= (anim->numFrames - anim->loopFrames);
819 }
820 f += anim->loopFrames;
821 }
822 else
823 {
824 f = anim->numFrames - 1;
825 if (f<0)
826 {
827 f = 0;
828 }
829 // the animation is stuck at the end, so it
830 // can immediately transition to another sequence
831 lf->frameTime = cg.time;
832 }
833 }
834
835 if ( anim->frameLerp < 0 )
836 {
837 lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
838 }
839 else
840 {
841 lf->frame = anim->firstFrame + f;
842 }
843
844 if ( cg.time > lf->frameTime )
845 {
846 lf->frameTime = cg.time;
847 }
848
849 newFrame = qtrue;
850 }
851
852 if ( lf->frameTime > cg.time + 200 )
853 {
854 lf->frameTime = cg.time;
855 }
856
857 if ( lf->oldFrameTime > cg.time )
858 {
859 lf->oldFrameTime = cg.time;
860 }
861 // calculate current lerp value
862 if ( lf->frameTime == lf->oldFrameTime )
863 {
864 lf->backlerp = 0;
865 }
866 else
867 {
868 lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
869 }
870
871 return newFrame;
872 }
873
874
875 /*
876 ===============
877 CG_ClearLerpFrame
878 ===============
879 */
CG_ClearLerpFrame(clientInfo_t * ci,lerpFrame_t * lf,int animationNumber)880 static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber )
881 {
882 lf->frameTime = lf->oldFrameTime = cg.time;
883 CG_SetLerpFrameAnimation( ci, lf, animationNumber );
884 if ( lf->animation->frameLerp < 0 )
885 {//Plays backwards
886 lf->oldFrame = lf->frame = (lf->animation->firstFrame + lf->animation->numFrames);
887 }
888 else
889 {
890 lf->oldFrame = lf->frame = lf->animation->firstFrame;
891 }
892 }
893
894 /*
895 ===============
896 CG_PlayerAnimation
897 ===============
898 */
CG_PlayerAnimation(centity_t * cent,int * legsOld,int * legs,float * legsBackLerp,int * torsoOld,int * torso,float * torsoBackLerp)899 static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp,
900 int *torsoOld, int *torso, float *torsoBackLerp ) {
901 clientInfo_t *ci;
902 int legsAnim;
903 int legsTurnAnim = -1;
904 qboolean newLegsFrame = qfalse;
905 qboolean newTorsoFrame = qfalse;
906
907 ci = ¢->gent->client->clientInfo;
908 //Changed this from cent->currentState.legsAnim to cent->gent->client->ps.legsAnim because it was screwing up our timers when we've just changed anims while turning
909 legsAnim = cent->gent->client->ps.legsAnim;
910
911 // do the shuffle turn frames locally (MAN this is an Fugly-ass hack!)
912
913 if ( cent->pe.legs.yawing )
914 {
915 legsTurnAnim = PM_GetTurnAnim( cent->gent, legsAnim );
916 }
917
918 if ( legsTurnAnim != -1 )
919 {
920 newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, legsTurnAnim, cent->gent->client->renderInfo.legsFpsMod, cent->gent->s.number );
921 //This line doesn't seem to serve any useful purpose, rather it
922 //breaks things since any task waiting for a lower anim to complete
923 //never will finish if this happens!!!
924 //cent->gent->client->ps.legsAnimTimer = 0;
925 }
926 else
927 {
928 newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, legsAnim, cent->gent->client->renderInfo.legsFpsMod, cent->gent->s.number);
929 }
930
931 *legsOld = cent->pe.legs.oldFrame;
932 *legs = cent->pe.legs.frame;
933 *legsBackLerp = cent->pe.legs.backlerp;
934
935 if( newLegsFrame )
936 {
937 if ( ValidAnimFileIndex( ci->animFileIndex ) )
938 {
939 CG_PlayerAnimEvents( ci->animFileIndex, qfalse, cent->pe.legs.frame, cent->pe.legs.frame, cent->currentState.number );
940 }
941 }
942
943 newTorsoFrame = CG_RunLerpFrame( ci, ¢->pe.torso, cent->gent->client->ps.torsoAnim, cent->gent->client->renderInfo.torsoFpsMod, cent->gent->s.number );
944
945 *torsoOld = cent->pe.torso.oldFrame;
946 *torso = cent->pe.torso.frame;
947 *torsoBackLerp = cent->pe.torso.backlerp;
948
949 if( newTorsoFrame )
950 {
951 if ( ValidAnimFileIndex( ci->animFileIndex ) )
952 {
953 CG_PlayerAnimEvents(ci->animFileIndex, qtrue, cent->pe.torso.frame, cent->pe.torso.frame, cent->currentState.number );
954 }
955 }
956 }
957
958
959 extern int PM_LegsAnimForFrame( gentity_t *ent, int legsFrame );
960 extern int PM_TorsoAnimForFrame( gentity_t *ent, int torsoFrame );
CG_PlayerAnimEventDo(centity_t * cent,animevent_t * animEvent)961 static void CG_PlayerAnimEventDo( centity_t *cent, animevent_t *animEvent )
962 {
963 //FIXME: pass in event, switch off the type
964 if ( cent == NULL || animEvent == NULL )
965 {
966 return;
967 }
968
969 soundChannel_t channel = CHAN_AUTO;
970 switch ( animEvent->eventType )
971 {
972 case AEV_SOUNDCHAN:
973 channel = (soundChannel_t)animEvent->eventData[AED_SOUNDCHANNEL];
974 case AEV_SOUND:
975 // are there variations on the sound?
976 {
977 const int holdSnd = animEvent->eventData[ AED_SOUNDINDEX_START+Q_irand( 0, animEvent->eventData[AED_SOUND_NUMRANDOMSNDS] ) ];
978 if ( holdSnd > 0 )
979 {
980 if ( cgs.sound_precache[ holdSnd ] )
981 {
982 cgi_S_StartSound( NULL, cent->currentState.clientNum, channel, cgs.sound_precache[holdSnd ] );
983 }
984 else
985 {//try a custom sound
986 const char *s = CG_ConfigString( CS_SOUNDS + holdSnd );
987 CG_TryPlayCustomSound(NULL, cent->currentState.clientNum, channel, va("%s.wav",s), CS_TRY_ALL );
988 }
989 }
990 }
991 break;
992 case AEV_SABER_SWING:
993 if ( cent->gent )
994 {//cheat over to game side and play sound from there...
995 WP_SaberSwingSound( cent->gent, animEvent->eventData[AED_SABER_SWING_SABERNUM], (swingType_t)animEvent->eventData[AED_SABER_SWING_TYPE] );
996 }
997 break;
998 case AEV_SABER_SPIN:
999 if ( cent->gent
1000 && cent->gent->client )
1001 {
1002 saberInfo_t *saber = ¢->gent->client->ps.saber[animEvent->eventData[AED_SABER_SPIN_SABERNUM]];
1003 if ( saber )
1004 {
1005 int spinSound = 0;
1006 if ( saber->spinSound
1007 && cgs.sound_precache[saber->spinSound] )
1008 {//use override
1009 spinSound = cgs.sound_precache[saber->spinSound];
1010 }
1011 else
1012 {
1013 switch ( animEvent->eventData[AED_SABER_SPIN_TYPE] )
1014 {
1015 case 0://saberspinoff
1016 spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspinoff.wav" );
1017 break;
1018 case 1://saberspin
1019 spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin.wav" );
1020 break;
1021 case 2://saberspin1
1022 spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" );
1023 break;
1024 case 3://saberspin2
1025 spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" );
1026 break;
1027 case 4://saberspin3
1028 spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" );
1029 break;
1030 default://random saberspin1-3
1031 spinSound = cgi_S_RegisterSound( va( "sound/weapons/saber/saberspin%d.wav", Q_irand(1,3)) );
1032 break;
1033 }
1034 }
1035 if ( spinSound )
1036 {
1037 cgi_S_StartSound( NULL, cent->currentState.clientNum, CHAN_AUTO, spinSound );
1038 }
1039 }
1040 }
1041 break;
1042 case AEV_FOOTSTEP:
1043 CG_PlayerFootsteps( cent, (footstepType_t)animEvent->eventData[AED_FOOTSTEP_TYPE] );
1044 break;
1045 case AEV_EFFECT:
1046 if ( animEvent->eventData[AED_EFFECTINDEX] == -1 )
1047 {//invalid effect
1048 if ( animEvent->stringData != NULL
1049 && animEvent->stringData[0] )
1050 {//some sort of hard-coded effect
1051 if ( Q_stricmp( "push_l", animEvent->stringData ) == 0 )
1052 {
1053 cgi_S_StartSound ( cent->lerpOrigin, cent->currentState.clientNum, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/force/push.wav" ) );
1054 cent->gent->client->ps.powerups[PW_FORCE_PUSH] = cg.time + animEvent->eventData[AED_EFFECT_PROBABILITY];//AED_EFFECT_PROBABILITY in this case is the number of ms for the effect to last
1055 cent->gent->client->pushEffectFadeTime = 0;
1056 }
1057 else if ( Q_stricmp( "push_r", animEvent->stringData ) == 0 )
1058 {
1059 cgi_S_StartSound ( cent->lerpOrigin, cent->currentState.clientNum, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/force/push.wav" ) );
1060 cent->gent->client->ps.powerups[PW_FORCE_PUSH_RHAND] = cg.time + animEvent->eventData[AED_EFFECT_PROBABILITY];//AED_EFFECT_PROBABILITY in this case is the number of ms for the effect to last
1061 cent->gent->client->pushEffectFadeTime = 0;
1062 }
1063 else if ( Q_stricmp( "scepter_beam", animEvent->stringData ) == 0 )
1064 {
1065 int modelIndex = cent->gent->weaponModel[1];
1066 if ( modelIndex <= 0 )
1067 {
1068 modelIndex = cent->gent->cinematicModel;
1069 }
1070 if ( modelIndex > 0 )
1071 {//we have a cinematic model
1072 int boltIndex = gi.G2API_AddBolt( ¢->gent->ghoul2[modelIndex], "*flash" );
1073 if ( boltIndex > -1 )
1074 {//cinematic model has a flash bolt
1075 CG_PlayEffectBolted( "scepter/beam.efx", modelIndex, boltIndex, cent->currentState.clientNum, cent->lerpOrigin, animEvent->eventData[AED_EFFECT_PROBABILITY], qtrue );//AED_EFFECT_PROBABILITY in this case is the number of ms for the effect to last
1076 }
1077 }
1078 }
1079 //FIXME: add more
1080 }
1081 }
1082 else
1083 {
1084 //add bolt, play effect
1085 if ( animEvent->stringData != NULL && cent && cent->gent && cent->gent->ghoul2.size() )
1086 {//have a bolt name we want to use
1087 animEvent->eventData[AED_MODELINDEX] = cent->gent->playerModel;
1088 if ( ( Q_stricmpn( "*blade", animEvent->stringData, 6 ) == 0
1089 || Q_stricmp( "*flash", animEvent->stringData ) == 0 )
1090 && cent->gent->weaponModel[0] > 0 )
1091 {//must be a weapon, try weapon 0?
1092 animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->weaponModel[0]], animEvent->stringData );
1093 if ( animEvent->eventData[AED_BOLTINDEX] != -1 )
1094 {//found it!
1095 animEvent->eventData[AED_MODELINDEX] = cent->gent->weaponModel[0];
1096 }
1097 else
1098 {//hmm, just try on the player model, then?
1099 animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData );
1100 }
1101 }
1102 else
1103 {
1104 animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData );
1105 }
1106 animEvent->stringData = NULL;//so we don't try to do this again
1107 }
1108 if ( animEvent->eventData[AED_BOLTINDEX] != -1 )
1109 {//have a bolt we want to play the effect on
1110 CG_PlayEffectIDBolted( animEvent->eventData[AED_EFFECTINDEX],
1111 animEvent->eventData[AED_MODELINDEX],
1112 animEvent->eventData[AED_BOLTINDEX],
1113 cent->currentState.clientNum,
1114 cent->lerpOrigin );
1115 }
1116 else
1117 {//play at origin? FIXME: maybe allow a fwd/rt/up offset?
1118 const vec3_t up = {0,0,1};
1119 CG_PlayEffectID( animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, up );
1120 //G_PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, up );
1121 //theFxScheduler.PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, qfalse );
1122 }
1123 }
1124 break;
1125 case AEV_FIRE:
1126 //add fire event
1127 if ( animEvent->eventData[AED_FIRE_ALT] )
1128 {
1129 G_AddEvent( cent->gent, EV_ALT_FIRE, 0 );
1130 }
1131 else
1132 {
1133 G_AddEvent( cent->gent, EV_FIRE_WEAPON, 0 );
1134 }
1135 break;
1136 case AEV_MOVE:
1137 //make him jump
1138 if ( cent && cent->gent && cent->gent->client )
1139 {
1140 if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE )
1141 {//on something
1142 vec3_t fwd, rt, up, angles = {0, cent->gent->client->ps.viewangles[YAW], 0};
1143 AngleVectors( angles, fwd, rt, up );
1144 //FIXME: set or add to velocity?
1145 VectorScale( fwd, animEvent->eventData[AED_MOVE_FWD], cent->gent->client->ps.velocity );
1146 VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_RT], rt, cent->gent->client->ps.velocity );
1147 VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_UP], up, cent->gent->client->ps.velocity );
1148
1149 if ( animEvent->eventData[AED_MOVE_UP] > 0 )
1150 {//a jump
1151 cent->gent->client->ps.pm_flags |= PMF_JUMPING;
1152
1153 G_AddEvent( cent->gent, EV_JUMP, 0 );
1154 //FIXME: if have force jump, do this? or specify sound in the event data?
1155 //cent->gent->client->ps.forceJumpZStart = cent->gent->client->ps.origin[2];//so we don't take damage if we land at same height
1156 //G_SoundOnEnt( cent->gent, CHAN_BODY, "sound/weapons/force/jump.wav" );
1157 }
1158 }
1159 }
1160 break;
1161 default:
1162 return;
1163 break;
1164 }
1165 }
1166
CG_PlayerAnimEvents(int animFileIndex,qboolean torso,int oldFrame,int frame,int entNum)1167 static void CG_PlayerAnimEvents( int animFileIndex, qboolean torso, int oldFrame, int frame, int entNum )
1168 {
1169 int i;
1170 int firstFrame = 0, lastFrame = 0;
1171 qboolean doEvent = qfalse, inSameAnim = qfalse, loopAnim = qfalse, match = qfalse, animBackward = qfalse;
1172 animevent_t *animEvents = NULL;
1173 int glaIndex = -1;
1174
1175 if ( g_entities[entNum].ghoul2.size() )
1176 {
1177 glaIndex = gi.G2API_GetAnimIndex(&(g_entities[entNum].ghoul2[0]));
1178 }
1179
1180 if ( torso )
1181 {
1182 animEvents = level.knownAnimFileSets[animFileIndex].torsoAnimEvents;
1183 }
1184 else
1185 {
1186 animEvents = level.knownAnimFileSets[animFileIndex].legsAnimEvents;
1187 }
1188 if ( abs(oldFrame-frame) > 1 )
1189 {//given a range, see if keyFrame falls in that range
1190 int oldAnim, anim;
1191 if ( torso )
1192 {
1193 //more precise, slower
1194 oldAnim = PM_TorsoAnimForFrame( &g_entities[entNum], oldFrame );
1195 anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame );
1196 }
1197 else
1198 {
1199 //more precise, slower
1200 oldAnim = PM_LegsAnimForFrame( &g_entities[entNum], oldFrame );
1201 anim = PM_LegsAnimForFrame( &g_entities[entNum], frame );
1202 }
1203
1204 if ( anim != oldAnim )
1205 {//not in same anim
1206 inSameAnim = qfalse;
1207 //FIXME: we *could* see if the oldFrame was *just about* to play the keyframed sound...
1208 }
1209 else
1210 {//still in same anim, check for looping anim
1211 inSameAnim = qtrue;
1212 animation_t *animation = &level.knownAnimFileSets[animFileIndex].animations[anim];
1213 animBackward = (qboolean)(animation->frameLerp<0);
1214 if ( animation->loopFrames != -1 )
1215 {//a looping anim!
1216 loopAnim = qtrue;
1217 firstFrame = animation->firstFrame;
1218 lastFrame = animation->firstFrame+animation->numFrames;
1219 }
1220 }
1221 }
1222
1223 hstring myModel = g_entities[entNum].NPC_type; //apparently NPC_type is always the same as the model name???
1224
1225 // Check for anim event
1226 for ( i=0; i < MAX_ANIM_EVENTS; ++i )
1227 {
1228 if ( animEvents[i].eventType == AEV_NONE ) // No event, end of list
1229 {
1230 break;
1231 }
1232
1233 if (glaIndex != -1 && animEvents[i].glaIndex!=glaIndex)
1234 {
1235 continue;
1236 }
1237
1238 match = qfalse;
1239 if (animEvents[i].modelOnly==0 || animEvents[i].modelOnly==myModel.handle())
1240 {
1241 if ( animEvents[i].keyFrame == frame )
1242 {//exact match
1243 match = qtrue;
1244 }
1245 else if ( abs(oldFrame-frame) > 1 )//&& cg_reliableAnimEvents.integer )
1246 {//given a range, see if keyFrame falls in that range
1247 if ( inSameAnim )
1248 {//if changed anims altogether, sorry, the sound is lost
1249 if ( abs(oldFrame-animEvents[i].keyFrame) <= 3
1250 || abs(frame-animEvents[i].keyFrame) <= 3 )
1251 {//must be at least close to the keyframe
1252 if ( animBackward )
1253 {//animation plays backwards
1254 if ( oldFrame > animEvents[i].keyFrame && frame < animEvents[i].keyFrame )
1255 {//old to new passed through keyframe
1256 match = qtrue;
1257 }
1258 else if ( loopAnim )
1259 {//hmm, didn't pass through it linearally, see if we looped
1260 if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame )
1261 {//keyframe is in this anim
1262 if ( oldFrame > animEvents[i].keyFrame
1263 && frame > oldFrame )
1264 {//old to new passed through keyframe
1265 match = qtrue;
1266 }
1267 }
1268 }
1269 }
1270 else
1271 {//anim plays forwards
1272 if ( oldFrame < animEvents[i].keyFrame && frame > animEvents[i].keyFrame )
1273 {//old to new passed through keyframe
1274 match = qtrue;
1275 }
1276 else if ( loopAnim )
1277 {//hmm, didn't pass through it linearally, see if we looped
1278 if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame )
1279 {//keyframe is in this anim
1280 if ( oldFrame < animEvents[i].keyFrame
1281 && frame < oldFrame )
1282 {//old to new passed through keyframe
1283 match = qtrue;
1284 }
1285 }
1286 }
1287 }
1288 }
1289 }
1290 }
1291 if ( match )
1292 {
1293 switch ( animEvents[i].eventType )
1294 {
1295 case AEV_SOUNDCHAN:
1296 case AEV_SOUND:
1297 // Determine probability of playing sound
1298 if (!animEvents[i].eventData[AED_SOUND_PROBABILITY]) // 100%
1299 {
1300 doEvent = qtrue;
1301 }
1302 else if (animEvents[i].eventData[AED_SOUND_PROBABILITY] > Q_irand(0, 99) )
1303 {
1304 doEvent = qtrue;
1305 }
1306 break;
1307 case AEV_SABER_SWING:
1308 // Determine probability of playing sound
1309 if (!animEvents[i].eventData[AED_SABER_SWING_PROBABILITY]) // 100%
1310 {
1311 doEvent = qtrue;
1312 }
1313 else if (animEvents[i].eventData[AED_SABER_SWING_PROBABILITY] > Q_irand(0, 99) )
1314 {
1315 doEvent = qtrue;
1316 }
1317 break;
1318 case AEV_SABER_SPIN:
1319 // Determine probability of playing sound
1320 if (!animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY]) // 100%
1321 {
1322 doEvent = qtrue;
1323 }
1324 else if (animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY] > Q_irand(0, 99) )
1325 {
1326 doEvent = qtrue;
1327 }
1328 break;
1329 case AEV_FOOTSTEP:
1330 // Determine probability of playing sound
1331 //Com_Printf( "Footstep event on frame %d, even should be on frame %d, off by %d\n", frame, animEvents[i].keyFrame, frame-animEvents[i].keyFrame );
1332 if (!animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY]) // 100%
1333 {
1334 doEvent = qtrue;
1335 }
1336 else if (animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY] > Q_irand(0, 99) )
1337 {
1338 doEvent = qtrue;
1339 }
1340 break;
1341 case AEV_EFFECT:
1342 // Determine probability of playing sound
1343 if (!animEvents[i].eventData[AED_EFFECT_PROBABILITY]) // 100%
1344 {
1345 doEvent = qtrue;
1346 }
1347 else if (animEvents[i].eventData[AED_EFFECT_PROBABILITY] > Q_irand(0, 99) )
1348 {
1349 doEvent = qtrue;
1350 }
1351 break;
1352 case AEV_FIRE:
1353 // Determine probability of playing sound
1354 if (!animEvents[i].eventData[AED_FIRE_PROBABILITY]) // 100%
1355 {
1356 doEvent = qtrue;
1357 }
1358 else if (animEvents[i].eventData[AED_FIRE_PROBABILITY] > Q_irand(0, 99) )
1359 {
1360 doEvent = qtrue;
1361 }
1362 break;
1363 case AEV_MOVE:
1364 doEvent = qtrue;
1365 break;
1366 default:
1367 //doEvent = qfalse;//implicit
1368 break;
1369 }
1370 // do event
1371 if ( doEvent )
1372 {
1373 CG_PlayerAnimEventDo( &cg_entities[entNum], &animEvents[i] );
1374 }
1375 }// end if event matches
1376 }// end if model matches
1377 }// end for
1378 }
1379
CGG2_AnimEvents(centity_t * cent)1380 static void CGG2_AnimEvents( centity_t *cent )
1381 {
1382 if ( !cent || !cent->gent || !cent->gent->client)
1383 {
1384 return;
1385 }
1386 if ( !cent->gent->ghoul2.size() )
1387 {//sorry, ghoul2 models only
1388 return;
1389 }
1390 assert(cent->gent->playerModel>=0&¢->gent->playerModel<cent->gent->ghoul2.size());
1391 if ( ValidAnimFileIndex( cent->gent->client->clientInfo.animFileIndex ) )
1392 {
1393 int junk, curFrame=0;
1394 float currentFrame=0, animSpeed;
1395
1396 if (cent->gent->rootBone>=0&&gi.G2API_GetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->rootBone, cg.time, ¤tFrame, &junk, &junk, &junk, &animSpeed, cgs.model_draw ))
1397 {
1398 // the above may have failed, not sure what to do about it, current frame will be zero in that case
1399 curFrame = floor( currentFrame );
1400 }
1401 if ( curFrame != cent->gent->client->renderInfo.legsFrame )
1402 {
1403 CG_PlayerAnimEvents( cent->gent->client->clientInfo.animFileIndex, qfalse, cent->gent->client->renderInfo.legsFrame, curFrame, cent->currentState.clientNum );
1404 }
1405 cent->gent->client->renderInfo.legsFrame = curFrame;
1406 cent->pe.legs.frame = curFrame;
1407
1408 if (cent->gent->lowerLumbarBone>=0&& gi.G2API_GetBoneAnimIndex(¢->gent->ghoul2[cent->gent->playerModel], cent->gent->lowerLumbarBone, cg.time, ¤tFrame, &junk, &junk, &junk, &animSpeed, cgs.model_draw ) )
1409 {
1410 curFrame = floor( currentFrame );
1411 }
1412 if ( curFrame != cent->gent->client->renderInfo.torsoFrame )
1413 {
1414 CG_PlayerAnimEvents( cent->gent->client->clientInfo.animFileIndex, qtrue, cent->gent->client->renderInfo.torsoFrame, curFrame, cent->currentState.clientNum );
1415 }
1416 cent->gent->client->renderInfo.torsoFrame = curFrame;
1417 cent->pe.torso.frame = curFrame;
1418 }
1419 }
1420 /*
1421 =============================================================================
1422
1423 PLAYER ANGLES
1424
1425 =============================================================================
1426 */
1427
1428 /*
1429 ==================
1430 CG_UpdateAngleClamp
1431 Turn curAngle toward destAngle at angleSpeed, but stay within clampMin and Max
1432 ==================
1433 */
CG_UpdateAngleClamp(float destAngle,float clampMin,float clampMax,float angleSpeed,float * curAngle,float normalAngle)1434 static void CG_UpdateAngleClamp( float destAngle, float clampMin, float clampMax, float angleSpeed, float *curAngle, float normalAngle)
1435 {
1436 float swing;
1437 float move;
1438 float scale;
1439 float actualSpeed;
1440
1441 swing = AngleSubtract( destAngle, *curAngle );
1442
1443 if(swing == 0)
1444 {//Don't have to turn
1445 return;
1446 }
1447
1448 // modify the angleSpeed depending on the delta
1449 // so it doesn't seem so linear
1450 scale = fabs( swing );
1451 if (swing > 0)
1452 {
1453 if ( swing < clampMax * 0.25 )
1454 {//Pretty small way to go
1455 scale = 0.25;
1456 }
1457 else if ( swing > clampMax * 2.0 )
1458 {//Way out of our range
1459 scale = 2.0;
1460 }
1461 else
1462 {//Scale it smoothly
1463 scale = swing/clampMax;
1464 }
1465 }
1466 else// if (swing < 0)
1467 {
1468 if ( swing > clampMin * 0.25 )
1469 {//Pretty small way to go
1470 scale = 0.5;
1471 }
1472 else if ( swing < clampMin * 2.0 )
1473 {//Way out of our range
1474 scale = 2.0;
1475 }
1476 else
1477 {//Scale it smoothly
1478 scale = swing/clampMin;
1479 }
1480 }
1481
1482 actualSpeed = scale * angleSpeed;
1483 // swing towards the destination angle
1484 if ( swing >= 0 )
1485 {
1486 move = cg.frametime * actualSpeed;
1487 if ( move >= swing )
1488 {//our turnspeed is so fast, no need to swing, just match
1489 *curAngle = destAngle;
1490 }
1491 else
1492 {
1493 *curAngle = AngleNormalize360( *curAngle + move );
1494 }
1495 }
1496 else if ( swing < 0 )
1497 {
1498 move = cg.frametime * -actualSpeed;
1499 if ( move <= swing )
1500 {//our turnspeed is so fast, no need to swing, just match
1501 *curAngle = destAngle;
1502 }
1503 else
1504 {
1505 *curAngle = AngleNormalize180( *curAngle + move );
1506 }
1507 }
1508
1509 swing = AngleSubtract( *curAngle, normalAngle );
1510
1511 // clamp to no more than normalAngle + tolerance
1512 if ( swing > clampMax )
1513 {
1514 *curAngle = AngleNormalize180( normalAngle + clampMax );
1515 }
1516 else if ( swing < clampMin )
1517 {
1518 *curAngle = AngleNormalize180( normalAngle + clampMin );
1519 }
1520 }
1521 /*
1522 ==================
1523 CG_SwingAngles
1524
1525 If the body is not locked OR if the upper part is trying to swing beyond it's
1526 range, turn the lower body part to catch up.
1527
1528 Parms: desired angle, (Our eventual goal angle
1529 min swing tolerance,(Lower angle value threshold at which to start turning)
1530 max swing tolerance,(Upper angle value threshold at which to start turning)
1531 min clamp tolerance,(Lower angle value threshold to clamp output angle to)
1532 max clamp tolerance,(Upper angle value threshold to clamp output angle to)
1533 angle speed, (How fast to turn)
1534 current angle, (Current angle to modify)
1535 locked mode (Don't turn unless you exceed the swing/clamp tolerance)
1536 ==================
1537 */
CG_SwingAngles(float destAngle,float swingTolMin,float swingTolMax,float clampMin,float clampMax,float angleSpeed,float * curAngle,qboolean * turning)1538 static void CG_SwingAngles( float destAngle,
1539 float swingTolMin, float swingTolMax,
1540 float clampMin, float clampMax,
1541 float angleSpeed, float *curAngle,
1542 qboolean *turning )
1543 {
1544 float swing;
1545 float move;
1546 float scale;
1547
1548 swing = AngleSubtract( destAngle, *curAngle );
1549
1550 if(swing == 0)
1551 {//Don't have to turn
1552 *turning = qfalse;
1553 }
1554 else
1555 {
1556 *turning = qtrue;
1557 }
1558
1559 //If we're not turning, then we're done
1560 if ( *turning == qfalse)
1561 return;
1562
1563 // modify the angleSpeed depending on the delta
1564 // so it doesn't seem so linear
1565 scale = fabs( swing );
1566
1567 if (swing > 0)
1568 {
1569 if ( clampMax <= 0 )
1570 {
1571 *curAngle = destAngle;
1572 return;
1573 }
1574
1575 if ( swing < swingTolMax * 0.5 )
1576 {//Pretty small way to go
1577 scale = 0.5;
1578 }
1579 else if ( scale < swingTolMax )
1580 {//More than halfway to go
1581 scale = 1.0;
1582 }
1583 else
1584 {//Way out of our range
1585 scale = 2.0;
1586 }
1587 }
1588 else// if (swing < 0)
1589 {
1590 if ( clampMin >= 0 )
1591 {
1592 *curAngle = destAngle;
1593 return;
1594 }
1595
1596 if ( swing > swingTolMin * 0.5 )
1597 {//Pretty small way to go
1598 scale = 0.5;
1599 }
1600 else if ( scale > swingTolMin )
1601 {//More than halfway to go
1602 scale = 1.0;
1603 }
1604 else
1605 {//Way out of our range
1606 scale = 2.0;
1607 }
1608 }
1609
1610 // swing towards the destination angle
1611 if ( swing >= 0 )
1612 {
1613 move = cg.frametime * scale * angleSpeed;
1614 if ( move >= swing )
1615 {//our turnspeed is so fast, no need to swing, just match
1616 move = swing;
1617 }
1618 *curAngle = AngleNormalize360( *curAngle + move );
1619 }
1620 else if ( swing < 0 )
1621 {
1622 move = cg.frametime * scale * -angleSpeed;
1623 if ( move <= swing )
1624 {//our turnspeed is so fast, no need to swing, just match
1625 move = swing;
1626 }
1627 *curAngle = AngleNormalize360( *curAngle + move );
1628 }
1629
1630
1631 // clamp to no more than tolerance
1632 if ( swing > clampMax )
1633 {
1634 *curAngle = AngleNormalize360( destAngle - (clampMax - 1) );
1635 }
1636 else if ( swing < clampMin )
1637 {
1638 *curAngle = AngleNormalize360( destAngle + (-clampMin - 1) );
1639 }
1640 }
1641
1642 /*
1643 ===============
1644 CG_BreathPuffs
1645 ===============
1646 Description: Makes the player appear to have breath puffs (from the cold).
1647 Added 11/06/02 by Aurelio Reis.
1648 */
1649 extern vmCvar_t cg_drawBreath;
CG_BreathPuffs(centity_t * cent,vec3_t angles,vec3_t origin)1650 static void CG_BreathPuffs( centity_t *cent, vec3_t angles, vec3_t origin )
1651 {
1652 gclient_t *client = cent->gent->client;
1653
1654 /* cg_drawBreath.integer == 0 - Don't draw at all.
1655 == 1 - Draw both (but bubbles only when under water).
1656 == 2 - Draw only cold breath.
1657 == 3 - Draw only under water bubbles (when under water) */
1658
1659 if ( !client
1660 || cg_drawBreath.integer == 0
1661 || !cg.renderingThirdPerson
1662 || client->ps.pm_type == PM_DEAD
1663 || client->breathPuffTime > cg.time )
1664 {
1665 return;
1666 }
1667
1668 // Get the head-front bolt/tag.
1669 int bolt = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], "*head_front" );
1670 if ( bolt == -1 )
1671 {
1672 return;
1673 }
1674
1675 vec3_t vEffectOrigin;
1676 mdxaBone_t boltMatrix;
1677 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt, &boltMatrix, angles, origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
1678 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, vEffectOrigin );
1679
1680 int contents = cgi_CM_PointContents( vEffectOrigin, 0 );
1681 if ( contents & ( CONTENTS_SLIME | CONTENTS_LAVA ) ) // If they're submerged in something bad, leave.
1682 {
1683 return;
1684 }
1685
1686 // Show bubbles effect if we're under water.
1687 if ( (contents & CONTENTS_WATER) && ( cg_drawBreath.integer == 1 || cg_drawBreath.integer == 3 ) )
1688 {
1689 CG_PlayEffectBolted( "misc/waterbreath", cent->gent->playerModel, bolt, cent->currentState.clientNum, vEffectOrigin );
1690 }
1691 // Draw cold breath effect.
1692 else if ( cg_drawBreath.integer == 1 || cg_drawBreath.integer == 2 )
1693 {
1694 CG_PlayEffectBolted( "misc/breath", cent->gent->playerModel, bolt, cent->currentState.clientNum, vEffectOrigin );
1695 }
1696
1697 // TODO: It'd be nice if they breath faster when they're more damaged or when running...
1698 if ( gi.VoiceVolume[cent->currentState.number] > 0 )
1699 {//make breath when talking
1700 client->breathPuffTime = cg.time + 300; // every 200 ms
1701 }
1702 else
1703 {
1704 client->breathPuffTime = cg.time + 3000; // every 3 seconds.
1705 }
1706 }
1707
1708 #define LOOK_DEFAULT_SPEED 0.15f
1709 #define LOOK_TALKING_SPEED 0.15f
1710
CG_CheckLookTarget(centity_t * cent,vec3_t lookAngles,float * lookingSpeed)1711 static qboolean CG_CheckLookTarget( centity_t *cent, vec3_t lookAngles, float *lookingSpeed )
1712 {
1713 if ( !cent->gent->ghoul2.size() )
1714 {
1715 if ( !cent->gent->client->clientInfo.torsoModel || !cent->gent->client->clientInfo.headModel )
1716 {
1717 return qfalse;
1718 }
1719 }
1720
1721 //FIXME: also clamp the lookAngles based on the clamp + the existing difference between
1722 // headAngles and torsoAngles? But often the tag_torso is straight but the torso itself
1723 // is deformed to not face straight... sigh...
1724
1725 //Now calc head angle to lookTarget, if any
1726 if ( cent->gent->client->renderInfo.lookTarget >= 0 && cent->gent->client->renderInfo.lookTarget < ENTITYNUM_WORLD )
1727 {
1728 vec3_t lookDir, lookOrg = { 0.0f }, eyeOrg;
1729 if ( cent->gent->client->renderInfo.lookMode == LM_ENT )
1730 {
1731 centity_t *lookCent = &cg_entities[cent->gent->client->renderInfo.lookTarget];
1732 if ( lookCent && lookCent->gent )
1733 {
1734 if ( lookCent->gent != cent->gent->enemy )
1735 {//We turn heads faster than headbob speed, but not as fast as if watching an enemy
1736 if ( cent->gent->client->NPC_class == CLASS_ROCKETTROOPER )
1737 {//they look around slowly and deliberately
1738 *lookingSpeed = LOOK_DEFAULT_SPEED*0.25f;
1739 }
1740 else
1741 {
1742 *lookingSpeed = LOOK_DEFAULT_SPEED;
1743 }
1744 }
1745
1746 //FIXME: Ignore small deltas from current angles so we don't bob our head in synch with theirs?
1747
1748 if ( cent->gent->client->renderInfo.lookTarget == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
1749 {//Special case- use cg.refdef.vieworg if looking at player and not in third person view
1750 VectorCopy( cg.refdef.vieworg, lookOrg );
1751 }
1752 else if ( lookCent->gent->client )
1753 {
1754 VectorCopy( lookCent->gent->client->renderInfo.eyePoint, lookOrg );
1755 }
1756 else if ( lookCent->gent->s.pos.trType == TR_INTERPOLATE )
1757 {
1758 VectorCopy( lookCent->lerpOrigin, lookOrg );
1759 }
1760 else if ( lookCent->gent->inuse && !VectorCompare( lookCent->gent->currentOrigin, vec3_origin ) )
1761 {
1762 VectorCopy( lookCent->gent->currentOrigin, lookOrg );
1763 }
1764 else
1765 {//at origin of world
1766 return qfalse;
1767 }
1768 //Look in dir of lookTarget
1769 }
1770 }
1771 else if ( cent->gent->client->renderInfo.lookMode == LM_INTEREST && cent->gent->client->renderInfo.lookTarget > -1 && cent->gent->client->renderInfo.lookTarget < MAX_INTEREST_POINTS )
1772 {
1773 VectorCopy( level.interestPoints[cent->gent->client->renderInfo.lookTarget].origin, lookOrg );
1774 }
1775 else
1776 {
1777 return qfalse;
1778 }
1779
1780 VectorCopy( cent->gent->client->renderInfo.eyePoint, eyeOrg );
1781
1782 VectorSubtract( lookOrg, eyeOrg, lookDir );
1783 #if 1
1784 vectoangles( lookDir, lookAngles );
1785 #else
1786 //FIXME: get the angle of the head tag and account for that when finding the lookAngles-
1787 // so if they're lying on their back we get an accurate lookAngle...
1788 vec3_t headDirs[3];
1789 vec3_t finalDir;
1790
1791 AnglesToAxis( cent->gent->client->renderInfo.headAngles, headDirs );
1792 VectorRotate( lookDir, headDirs, finalDir );
1793 vectoangles( finalDir, lookAngles );
1794 #endif
1795 for ( int i = 0; i < 3; i++ )
1796 {
1797 lookAngles[i] = AngleNormalize180( lookAngles[i] );
1798 cent->gent->client->renderInfo.eyeAngles[i] = AngleNormalize180( cent->gent->client->renderInfo.eyeAngles[i] );
1799 }
1800 AnglesSubtract( lookAngles, cent->gent->client->renderInfo.eyeAngles, lookAngles );
1801 return qtrue;
1802 }
1803
1804 return qfalse;
1805 }
1806
1807 /*
1808 =================
1809 CG_AddHeadBob
1810 =================
1811 */
CG_AddHeadBob(centity_t * cent,vec3_t addTo)1812 static qboolean CG_AddHeadBob( centity_t *cent, vec3_t addTo )
1813 {
1814 renderInfo_t *renderInfo = ¢->gent->client->renderInfo;
1815 const int volume = gi.VoiceVolume[cent->gent->s.clientNum];
1816 const int volChange = volume - renderInfo->lastVoiceVolume;//was *3 because voice fromLA was too low
1817 int i;
1818
1819 renderInfo->lastVoiceVolume = volume;
1820
1821 if ( !volume )
1822 {
1823 // Not talking, set our target to be the normal head position
1824 VectorClear( renderInfo->targetHeadBobAngles );
1825
1826 if ( VectorLengthSquared( renderInfo->headBobAngles ) < 1.0f )
1827 {
1828 // We are close enough to being back to our normal head position, so we are done for now
1829 return qfalse;
1830 }
1831 }
1832 else if ( volChange > 2 )
1833 {
1834 // a big positive change in volume
1835 for ( i = 0; i < 3; i++ )
1836 {
1837 // Move our head angle target a bit
1838 renderInfo->targetHeadBobAngles[i] += Q_flrand( -1.0 * volChange, 1.0 * volChange );
1839
1840 // Clamp so we don't get too out of hand
1841 if ( renderInfo->targetHeadBobAngles[i] > 7.0f )
1842 renderInfo->targetHeadBobAngles[i] = 7.0f;
1843
1844 if ( renderInfo->targetHeadBobAngles[i] < -7.0f )
1845 renderInfo->targetHeadBobAngles[i] = -7.0f;
1846 }
1847 }
1848
1849 for ( i = 0; i < 3; i++ )
1850 {
1851 // Always try to move head angles towards our target
1852 renderInfo->headBobAngles[i] += ( renderInfo->targetHeadBobAngles[i] - renderInfo->headBobAngles[i] ) * ( cg.frametime / 150.0f );
1853 if ( addTo )
1854 {
1855 addTo[i] = AngleNormalize180( addTo[i] + AngleNormalize180( renderInfo->headBobAngles[i] ) );
1856 }
1857 }
1858
1859 // We aren't back to our normal position yet, so we still have to apply headBobAngles
1860 return qtrue;
1861 }
1862
1863 extern float vectoyaw( const vec3_t vec );
CG_PlayerLegsYawFromMovement(centity_t * cent,const vec3_t velocity,float * yaw,float fwdAngle,float swingTolMin,float swingTolMax,qboolean alwaysFace)1864 static qboolean CG_PlayerLegsYawFromMovement( centity_t *cent, const vec3_t velocity, float *yaw, float fwdAngle, float swingTolMin, float swingTolMax, qboolean alwaysFace )
1865 {
1866 float newAddAngle, angleDiff, turnRate = 10, addAngle = 0;
1867
1868 //figure out what the offset, if any, should be
1869 if ( velocity[0] || velocity[1] )
1870 {
1871 float moveYaw;
1872 moveYaw = vectoyaw( velocity );
1873 addAngle = AngleDelta( cent->lerpAngles[YAW], moveYaw )*-1;
1874 if ( addAngle > 150 || addAngle < -150 )
1875 {
1876 addAngle = 0;
1877 }
1878 else
1879 {
1880 //FIXME: use actual swing/clamp tolerances
1881 if ( addAngle > swingTolMax )
1882 {
1883 addAngle = swingTolMax;
1884 }
1885 else if ( addAngle < swingTolMin )
1886 {
1887 addAngle = swingTolMin;
1888 }
1889 if ( cent->gent->client->ps.pm_flags&PMF_BACKWARDS_RUN )
1890 {
1891 addAngle *= -1;
1892 }
1893 turnRate = 5;
1894 }
1895 }
1896 else if ( !alwaysFace )
1897 {
1898 return qfalse;
1899 }
1900 if ( cent->gent && cent->gent->client && cent->gent->client->ps.forcePowersActive & (1 << FP_SPEED) )
1901 {//using force speed
1902 //scale up the turning speed
1903 turnRate /= cg_timescale.value;
1904 }
1905 //lerp the legs angle to the new angle
1906 angleDiff = AngleDelta( cent->pe.legs.yawAngle, (*yaw+addAngle) );
1907 newAddAngle = angleDiff*cg.frameInterpolation*-1;
1908 if ( fabs(newAddAngle) > fabs(angleDiff) )
1909 {
1910 newAddAngle = angleDiff*-1;
1911 }
1912 if ( newAddAngle > turnRate )
1913 {
1914 newAddAngle = turnRate;
1915 }
1916 else if ( newAddAngle < -turnRate )
1917 {
1918 newAddAngle = -turnRate;
1919 }
1920 *yaw = cent->pe.legs.yawAngle + newAddAngle;
1921 //Now clamp
1922 angleDiff = AngleDelta( fwdAngle, *yaw );
1923 if ( angleDiff > swingTolMax )
1924 {
1925 *yaw = fwdAngle - swingTolMax;
1926 }
1927 else if ( angleDiff < swingTolMin )
1928 {
1929 *yaw = fwdAngle - swingTolMin;
1930 }
1931 return qtrue;
1932 }
1933
CG_ATSTLegsYaw(centity_t * cent,vec3_t trailingLegsAngles)1934 static void CG_ATSTLegsYaw( centity_t *cent, vec3_t trailingLegsAngles )
1935 {
1936
1937 float ATSTLegsYaw = cent->lerpAngles[YAW];
1938
1939 CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &ATSTLegsYaw, cent->lerpAngles[YAW], -60, 60, qtrue );
1940
1941 float legAngleDiff = AngleNormalize180(ATSTLegsYaw) - AngleNormalize180(cent->pe.legs.yawAngle);
1942 int legsAnim = cent->currentState.legsAnim;
1943 qboolean moving = (qboolean)!VectorCompare(cent->gent->client->ps.velocity, vec3_origin);
1944 if ( moving || legsAnim == BOTH_TURN_LEFT1 || legsAnim == BOTH_TURN_RIGHT1 || fabs(legAngleDiff) > 45 )
1945 {//moving or turning or beyond the turn allowance
1946 if ( legsAnim == BOTH_STAND1 && !moving )
1947 {//standing
1948 if ( legAngleDiff > 0 )
1949 {
1950 NPC_SetAnim( cent->gent, SETANIM_LEGS, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1951 }
1952 else
1953 {
1954 NPC_SetAnim( cent->gent, SETANIM_LEGS, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1955 }
1956 VectorSet( trailingLegsAngles, 0, cent->pe.legs.yawAngle, 0 );
1957 cent->gent->client->renderInfo.legsYaw = trailingLegsAngles[YAW];
1958 }
1959 else if ( legsAnim == BOTH_TURN_LEFT1 || legsAnim == BOTH_TURN_RIGHT1 )
1960 {//turning
1961 legAngleDiff = AngleSubtract( ATSTLegsYaw, cent->gent->client->renderInfo.legsYaw );
1962 float add = 0;
1963 if ( legAngleDiff > 50 )
1964 {
1965 cent->pe.legs.yawAngle += legAngleDiff - 50;
1966 }
1967 else if ( legAngleDiff < -50 )
1968 {
1969 cent->pe.legs.yawAngle += legAngleDiff + 50;
1970 }
1971 float animLength = PM_AnimLength( cent->gent->client->clientInfo.animFileIndex, (animNumber_t)legsAnim );
1972 legAngleDiff *= ( animLength - cent->gent->client->ps.legsAnimTimer)/animLength;
1973 VectorSet( trailingLegsAngles, 0, cent->pe.legs.yawAngle+legAngleDiff+add, 0 );
1974 if ( !cent->gent->client->ps.legsAnimTimer )
1975 {//FIXME: if start turning in the middle of this, our legs pop back to the old cent->pe.legs.yawAngle...
1976 cent->gent->client->renderInfo.legsYaw = trailingLegsAngles[YAW];
1977 }
1978 }
1979 else
1980 {//moving
1981 legAngleDiff = AngleSubtract( ATSTLegsYaw, cent->pe.legs.yawAngle );
1982 //FIXME: framerate dependant!!!
1983 if ( legAngleDiff > 50 )
1984 {
1985 legAngleDiff -= 50;
1986 }
1987 else if ( legAngleDiff > 5 )
1988 {
1989 legAngleDiff = 5;
1990 }
1991 else if ( legAngleDiff < -50 )
1992 {
1993 legAngleDiff += 50;
1994 }
1995 else if ( legAngleDiff < -5 )
1996 {
1997 legAngleDiff = -5;
1998 }
1999 legAngleDiff *= cg.frameInterpolation;
2000 VectorSet( trailingLegsAngles, 0, AngleNormalize180(cent->pe.legs.yawAngle + legAngleDiff), 0 );
2001 cent->gent->client->renderInfo.legsYaw = trailingLegsAngles[YAW];
2002 }
2003 cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = trailingLegsAngles[YAW];
2004 cent->pe.legs.yawing = qtrue;
2005 }
2006 else
2007 {
2008 VectorSet( trailingLegsAngles, 0, cent->pe.legs.yawAngle, 0 );
2009 cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = trailingLegsAngles[YAW];
2010 cent->pe.legs.yawing = qfalse;
2011 }
2012 return;
2013 }
2014
2015 extern qboolean G_ClassHasBadBones( int NPC_class );
2016 extern void G_BoneOrientationsForClass( int NPC_class, const char *boneName, Eorientations *oUp, Eorientations *oRt, Eorientations *oFwd );
2017 extern qboolean PM_FlippingAnim( int anim );
2018 extern qboolean PM_SpinningSaberAnim( int anim );
2019 static CGhoul2Info_v dummyGhoul2;
2020 static int dummyRootBone;
2021 static int dummyHipsBolt;
CG_G2ClientSpineAngles(centity_t * cent,vec3_t viewAngles,const vec3_t angles,vec3_t thoracicAngles,vec3_t ulAngles,vec3_t llAngles)2022 static void CG_G2ClientSpineAngles( centity_t *cent, vec3_t viewAngles, const vec3_t angles, vec3_t thoracicAngles, vec3_t ulAngles, vec3_t llAngles )
2023 {
2024 vec3_t motionBoneCorrectAngles = {0};
2025 cent->pe.torso.pitchAngle = viewAngles[PITCH];
2026 viewAngles[YAW] = AngleDelta( cent->lerpAngles[YAW], angles[YAW] );
2027 cent->pe.torso.yawAngle = viewAngles[YAW];
2028
2029 /*
2030 if ( G_ClassHasBadBones( cent->gent->client->NPC_class ) )
2031 {//don't use lower bones
2032 VectorClear( thoracicAngles );
2033 VectorClear( ulAngles );
2034 VectorClear( llAngles );
2035 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, POSITIVE_Y, POSITIVE_Z, cgs.model_draw );
2036 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, POSITIVE_Y, POSITIVE_Z, cgs.model_draw );
2037 return;
2038 }
2039 */
2040
2041 if ( cent->gent->client->NPC_class == CLASS_SABER_DROID )
2042 {//don't use lower bones
2043 VectorClear( thoracicAngles );
2044 VectorClear( ulAngles );
2045 VectorClear( llAngles );
2046 return;
2047 }
2048
2049 if ( cg_motionBoneComp.integer
2050 && !PM_FlippingAnim( cent->currentState.legsAnim )
2051 && !PM_SpinningSaberAnim( cent->currentState.legsAnim )
2052 && !PM_SpinningSaberAnim( cent->currentState.torsoAnim )
2053 && cent->currentState.legsAnim != cent->currentState.torsoAnim //NOTE: presumes your legs & torso are on the same frame, though they *should* be because PM_SetAnimFinal tries to keep them in synch
2054 && !G_ClassHasBadBones( cent->gent->client->NPC_class ) )//these guys' bones are so fucked up we shouldn't even bother with this motion bone comp...
2055 {//FIXME: no need to do this if legs and torso on are same frame
2056 mdxaBone_t boltMatrix;
2057
2058 if ( cg_motionBoneComp.integer > 2 && cent->gent->rootBone >= 0 && cent->gent->lowerLumbarBone >= 0 )
2059 {//expensive version
2060 //have a local ghoul2 instance to mess with for this stuff... :/
2061 //remember the frame the lower is on
2062 float upperFrame, animSpeed;
2063 int junk;
2064 vec3_t llFwd, llRt, destPAngles, curPAngles, tempAng;
2065
2066 if ( !dummyGhoul2.size() )
2067 {//set it up
2068 int dummyHModel = cgi_R_RegisterModel( "models/players/_humanoid/_humanoid.glm" );
2069 gi.G2API_InitGhoul2Model( dummyGhoul2, "models/players/_humanoid/_humanoid.glm", dummyHModel, NULL_HANDLE, NULL_HANDLE, 0, 0 );
2070 dummyRootBone = gi.G2API_GetBoneIndex( &dummyGhoul2[0], "model_root", qtrue );
2071 dummyHipsBolt = gi.G2API_AddBolt( &dummyGhoul2[0], "pelvis" );
2072 }
2073
2074 gi.G2API_GetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->lowerLumbarBone, cg.time, &upperFrame, &junk, &junk, &junk, &animSpeed, cgs.model_draw );
2075 //set the dummyGhoul2 lower body to same frame as upper
2076 gi.G2API_SetBoneAnimIndex(&dummyGhoul2[0], dummyRootBone, upperFrame, upperFrame, BONE_ANIM_OVERRIDE_FREEZE, 1, cg.time, upperFrame, 0 );
2077 //get the dummyGhoul2 lower_lumbar orientation
2078 gi.G2API_GetBoltMatrix( dummyGhoul2, 0, dummyHipsBolt, &boltMatrix, vec3_origin, vec3_origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
2079 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, llFwd );
2080 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, llRt );
2081 vectoangles( llFwd, destPAngles );
2082 vectoangles( llRt, tempAng );
2083 destPAngles[ROLL] = -tempAng[PITCH];
2084 //get my lower_lumbar
2085 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->crotchBolt, &boltMatrix, vec3_origin, vec3_origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
2086 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, llFwd );
2087 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, llRt );
2088 vectoangles( llFwd, curPAngles );
2089 vectoangles( llRt, tempAng );
2090 curPAngles[ROLL] = -tempAng[PITCH];
2091
2092 //get the difference
2093 for ( int ang = 0; ang < 3; ang++ )
2094 {
2095 motionBoneCorrectAngles[ang] = AngleNormalize180( AngleDelta( AngleNormalize180( destPAngles[ang] ), AngleNormalize180( curPAngles[ang]) ) );
2096 }
2097 #ifdef _DEBUG
2098 Com_Printf( "motion bone correction: %4.2f %4.2f %4.2f\n", motionBoneCorrectAngles[PITCH], motionBoneCorrectAngles[YAW], motionBoneCorrectAngles[ROLL] );
2099 #endif// _DEBUG
2100 /*
2101 for ( int ang = 0; ang < 3; ang++ )
2102 {
2103 viewAngles[ang] = AngleNormalize180( viewAngles[ang] - AngleNormalize180( destLLAngles[ang] ) );
2104 }
2105 */
2106 }
2107 else
2108 {
2109 //adjust for motion offset
2110 vec3_t motionFwd, motionAngles;
2111
2112 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->motionBolt, &boltMatrix, vec3_origin, cent->lerpOrigin, cg.time, cgs.model_draw, cent->currentState.modelScale );
2113 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, motionFwd );
2114 vectoangles( motionFwd, motionAngles );
2115 if ( cg_motionBoneComp.integer > 1 )
2116 {//do roll, too
2117 vec3_t motionRt, tempAng;
2118 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, motionRt );
2119 vectoangles( motionRt, tempAng );
2120 motionAngles[ROLL] = -tempAng[PITCH];
2121 }
2122
2123 for ( int ang = 0; ang < 3; ang++ )
2124 {
2125 viewAngles[ang] = AngleNormalize180( viewAngles[ang] - AngleNormalize180( motionAngles[ang] ) );
2126 }
2127 }
2128 }
2129 //distribute the angles differently up the spine
2130 //NOTE: each of these distributions must add up to 1.0f
2131 if ( cent->gent->client->NPC_class == CLASS_HAZARD_TROOPER )
2132 {//only uses lower_lumbar and upper_lumbar to look around
2133 VectorClear( thoracicAngles );
2134 ulAngles[PITCH] = viewAngles[PITCH]*0.50f;
2135 llAngles[PITCH] = viewAngles[PITCH]*0.50f+motionBoneCorrectAngles[PITCH];
2136
2137 ulAngles[YAW] = viewAngles[YAW]*0.45f;
2138 llAngles[YAW] = viewAngles[YAW]*0.55f+motionBoneCorrectAngles[YAW];
2139
2140 ulAngles[ROLL] = viewAngles[ROLL]*0.45f;
2141 llAngles[ROLL] = viewAngles[ROLL]*0.55f+motionBoneCorrectAngles[ROLL];
2142 }
2143 else if ( cent->gent->client->NPC_class == CLASS_ASSASSIN_DROID )
2144 {//each bone has only 1 axis of rotation!
2145 //upper lumbar does not pitch
2146 thoracicAngles[PITCH] = viewAngles[PITCH]*0.40f;
2147 ulAngles[PITCH] = 0.0f;
2148 llAngles[PITCH] = viewAngles[PITCH]*0.60f+motionBoneCorrectAngles[PITCH];
2149 //only upper lumbar yaws
2150 thoracicAngles[YAW] = 0.0f;
2151 ulAngles[YAW] = viewAngles[YAW];
2152 llAngles[YAW] = motionBoneCorrectAngles[YAW];
2153 //no bone is capable of rolling
2154 thoracicAngles[ROLL] = 0.0f;
2155 ulAngles[ROLL] = 0.0f;
2156 llAngles[ROLL] = motionBoneCorrectAngles[ROLL];
2157 }
2158 else
2159 {//use all 3 bones
2160 thoracicAngles[PITCH] = viewAngles[PITCH]*0.20f;
2161 ulAngles[PITCH] = viewAngles[PITCH]*0.40f;
2162 llAngles[PITCH] = viewAngles[PITCH]*0.40f+motionBoneCorrectAngles[PITCH];
2163
2164 thoracicAngles[YAW] = viewAngles[YAW]*0.20f;
2165 ulAngles[YAW] = viewAngles[YAW]*0.35f;
2166 llAngles[YAW] = viewAngles[YAW]*0.45f+motionBoneCorrectAngles[YAW];
2167
2168 thoracicAngles[ROLL] = viewAngles[ROLL]*0.20f;
2169 ulAngles[ROLL] = viewAngles[ROLL]*0.35f;
2170 llAngles[ROLL] = viewAngles[ROLL]*0.45f+motionBoneCorrectAngles[ROLL];
2171 }
2172
2173 if ( G_IsRidingVehicle( cent->gent ) )// && type == VH_SPEEDER ?
2174 {//aim torso forward too
2175 ulAngles[YAW] = llAngles[YAW] = 0;
2176
2177 // Only if they have weapon can they pitch forward/back.
2178 if ( cent->gent->client->ps.weapon == WP_NONE || cent->gent->client->ps.weapon == WP_SABER )
2179 {
2180 ulAngles[PITCH] = llAngles[PITCH] = 0;
2181 }
2182 }
2183 //thoracic is added modified again by neckAngle calculations, so don't set it until then
2184 if ( G_ClassHasBadBones( cent->gent->client->NPC_class ) )
2185 {
2186 Eorientations oUp, oRt, oFwd;
2187 if ( cent->gent->client->NPC_class == CLASS_RANCOR )
2188 {
2189 llAngles[YAW] = llAngles[ROLL] = 0.0f;
2190 ulAngles[YAW] = ulAngles[ROLL] = 0.0f;
2191 }
2192 G_BoneOrientationsForClass( cent->gent->client->NPC_class, "upper_lumbar", &oUp, &oRt, &oFwd );
2193 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, ulAngles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, cgs.model_draw);
2194 G_BoneOrientationsForClass( cent->gent->client->NPC_class, "lower_lumbar", &oUp, &oRt, &oFwd );
2195 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, llAngles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, cgs.model_draw);
2196 }
2197 else
2198 {
2199 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2200 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2201 }
2202 }
2203
CG_G2ClientNeckAngles(centity_t * cent,const vec3_t lookAngles,vec3_t headAngles,vec3_t neckAngles,vec3_t thoracicAngles,vec3_t headClampMinAngles,vec3_t headClampMaxAngles)2204 static void CG_G2ClientNeckAngles( centity_t *cent, const vec3_t lookAngles, vec3_t headAngles, vec3_t neckAngles, vec3_t thoracicAngles, vec3_t headClampMinAngles, vec3_t headClampMaxAngles )
2205 {
2206 /*
2207 if ( G_ClassHasBadBones( cent->gent->client->NPC_class ) )
2208 {//don't use lower bones
2209 VectorClear( thoracicAngles );
2210 VectorClear( headAngles );
2211 VectorClear( neckAngles );
2212 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, POSITIVE_Y, POSITIVE_Z, cgs.model_draw );
2213 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->cervicalBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, POSITIVE_Y, POSITIVE_Z, cgs.model_draw );
2214 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, POSITIVE_Y, POSITIVE_Z, cgs.model_draw );
2215 return;
2216 }
2217 */
2218 if ( cent->gent->client->NPC_class == CLASS_HAZARD_TROOPER )
2219 {//don't use upper bones
2220 return;
2221 }
2222 vec3_t lA;
2223 VectorCopy( lookAngles, lA );
2224 //clamp the headangles (which should now be relative to the cervical (neck) angles
2225 if ( lA[PITCH] < headClampMinAngles[PITCH] )
2226 {
2227 lA[PITCH] = headClampMinAngles[PITCH];
2228 }
2229 else if ( lA[PITCH] > headClampMaxAngles[PITCH] )
2230 {
2231 lA[PITCH] = headClampMaxAngles[PITCH];
2232 }
2233
2234 if ( lA[YAW] < headClampMinAngles[YAW] )
2235 {
2236 lA[YAW] = headClampMinAngles[YAW];
2237 }
2238 else if ( lA[YAW] > headClampMaxAngles[YAW] )
2239 {
2240 lA[YAW] = headClampMaxAngles[YAW];
2241 }
2242
2243 if ( lA[ROLL] < headClampMinAngles[ROLL] )
2244 {
2245 lA[ROLL] = headClampMinAngles[ROLL];
2246 }
2247 else if ( lA[ROLL] > headClampMaxAngles[ROLL] )
2248 {
2249 lA[ROLL] = headClampMaxAngles[ROLL];
2250 }
2251
2252 //split it up between the neck and cranium
2253 if ( cent->gent->client->NPC_class == CLASS_ASSASSIN_DROID )
2254 {//each bone has only 1 axis of rotation!
2255 //thoracic only pitches, split with cervical
2256 if ( thoracicAngles[PITCH] )
2257 {//already been set above, blend them
2258 thoracicAngles[PITCH] = (thoracicAngles[PITCH] + (lA[PITCH] * 0.5f)) * 0.5f;
2259 }
2260 else
2261 {
2262 thoracicAngles[PITCH] = lA[PITCH] * 0.5f;
2263 }
2264 thoracicAngles[YAW] = thoracicAngles[ROLL] = 0.0f;
2265 //cervical only pitches, split with thoracis
2266 neckAngles[PITCH] = lA[PITCH] * 0.5f;
2267 neckAngles[YAW] = 0.0f;
2268 neckAngles[ROLL] = 0.0f;
2269 //cranium only yaws
2270 headAngles[PITCH] = 0.0f;
2271 headAngles[YAW] = lA[YAW];
2272 headAngles[ROLL] = 0.0f;
2273 //no bones roll
2274 }
2275 else if ( cent->gent->client->NPC_class == CLASS_SABER_DROID )
2276 {//each bone has only 1 axis of rotation!
2277 //no thoracic
2278 VectorClear( thoracicAngles );
2279 //cervical only yaws
2280 neckAngles[PITCH] = 0.0f;
2281 neckAngles[YAW] = lA[YAW];
2282 neckAngles[ROLL] = 0.0f;
2283 //cranium only pitches
2284 headAngles[PITCH] = lA[PITCH];
2285 headAngles[YAW] = 0.0f;
2286 headAngles[ROLL] = 0.0f;
2287 //none of the bones roll
2288 }
2289 else
2290 {
2291 if ( thoracicAngles[PITCH] )
2292 {//already been set above, blend them
2293 thoracicAngles[PITCH] = (thoracicAngles[PITCH] + (lA[PITCH] * 0.4f)) * 0.5f;
2294 }
2295 else
2296 {
2297 thoracicAngles[PITCH] = lA[PITCH] * 0.4f;
2298 }
2299 if ( thoracicAngles[YAW] )
2300 {//already been set above, blend them
2301 thoracicAngles[YAW] = (thoracicAngles[YAW] + (lA[YAW] * 0.1f)) * 0.5f;
2302 }
2303 else
2304 {
2305 thoracicAngles[YAW] = lA[YAW] * 0.1f;
2306 }
2307 if ( thoracicAngles[ROLL] )
2308 {//already been set above, blend them
2309 thoracicAngles[ROLL] = (thoracicAngles[ROLL] + (lA[ROLL] * 0.1f)) * 0.5f;
2310 }
2311 else
2312 {
2313 thoracicAngles[ROLL] = lA[ROLL] * 0.1f;
2314 }
2315
2316 neckAngles[PITCH] = lA[PITCH] * 0.2f;
2317 neckAngles[YAW] = lA[YAW] * 0.3f;
2318 neckAngles[ROLL] = lA[ROLL] * 0.3f;
2319
2320 headAngles[PITCH] = lA[PITCH] * 0.4f;
2321 headAngles[YAW] = lA[YAW] * 0.6f;
2322 headAngles[ROLL] = lA[ROLL] * 0.6f;
2323 }
2324
2325 if ( G_IsRidingVehicle( cent->gent ) )// && type == VH_SPEEDER ?
2326 {//aim torso forward too
2327 headAngles[YAW] = neckAngles[YAW] = thoracicAngles[YAW] = 0;
2328
2329 // Only if they have weapon can they pitch forward/back.
2330 if ( cent->gent->client->ps.weapon == WP_NONE || cent->gent->client->ps.weapon == WP_SABER )
2331 {
2332 thoracicAngles[PITCH] = 0;
2333 }
2334
2335 /* ABORTED ATTEMPT AT AIMING GUN WITH SHOULDER WHEN ON BIKE... POSSIBLY RETURN TO THIS LATER
2336 if ( cent->gent &&
2337 cent->gent->client &&
2338 cent->gent->enemy &&
2339 cent->gent->humerusRBone!=-1 &&
2340 (cent->gent->client->ps.torsoAnim==BOTH_VS_ATR_G || cent->gent->client->ps.torsoAnim==BOTH_VS_ATF_G))
2341 {
2342 vec3_t toEnemy;
2343 vec3_t toEnemyAngles;
2344 float toEnemyDistance;
2345 gentity_t* actor = cent->gent;
2346 vec3_t actorPos;
2347 vec3_t actorAim;
2348 vec3_t actorAngles;
2349 float actorAimDot;
2350
2351 mdxaBone_t boltMatrix;
2352 vec3_t boltAngles;
2353
2354 gi.G2API_GetBoltMatrix( actor->ghoul2, actor->playerModel, actor->humerusRBone, &boltMatrix, vec3_origin, cent->lerpOrigin, cg.time, cgs.model_draw, cent->currentState.modelScale );
2355 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, actorPos );
2356
2357 VectorSubtract(actorPos, actor->enemy->currentOrigin, toEnemy);
2358 toEnemyDistance = VectorNormalize(toEnemy);
2359
2360 AngleVectors(actor->currentAngles, actorAim, 0, 0);
2361 actorAimDot = DotProduct(toEnemy, actorAim);
2362
2363 if (actorAimDot>0.9f || (actorAimDot<0.1f && actorAimDot>-0.1f))
2364 {
2365 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, actorAim );
2366 vectoangles( actorAim, actorAngles );
2367 vectoangles( toEnemy, toEnemyAngles );
2368
2369 boltAngles[0] = AngleDelta(actorAngles[0], toEnemyAngles[0]);
2370 boltAngles[1] = AngleDelta(actorAngles[1], toEnemyAngles[1]);
2371 boltAngles[2] = AngleDelta(actorAngles[2], toEnemyAngles[2]);
2372
2373 BG_G2SetBoneAngles( cent, actor, actor->humerusRBone, boltAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2374 }
2375 }*/
2376 }
2377 if ( G_ClassHasBadBones( cent->gent->client->NPC_class ) )
2378 {
2379 Eorientations oUp, oRt, oFwd;
2380 if ( cent->gent->client->NPC_class != CLASS_RANCOR )
2381 {//Rancor doesn't use cranium and cervical
2382 G_BoneOrientationsForClass( cent->gent->client->NPC_class, "cranium", &oUp, &oRt, &oFwd );
2383 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, headAngles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, cgs.model_draw );
2384 G_BoneOrientationsForClass( cent->gent->client->NPC_class, "cervical", &oUp, &oRt, &oFwd );
2385 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->cervicalBone, neckAngles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, cgs.model_draw);
2386 }
2387 if ( cent->gent->client->NPC_class != CLASS_SABER_DROID )
2388 {//saber droid doesn't use thoracic
2389 if ( cent->gent->client->NPC_class == CLASS_RANCOR )
2390 {
2391 thoracicAngles[YAW] = thoracicAngles[ROLL] = 0.0f;
2392 }
2393 G_BoneOrientationsForClass( cent->gent->client->NPC_class, "thoracic", &oUp, &oRt, &oFwd );
2394 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, thoracicAngles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, cgs.model_draw);
2395 }
2396 }
2397 else
2398 {
2399 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, headAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2400 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->cervicalBone, neckAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2401 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2402 }
2403 }
2404
CG_UpdateLookAngles(centity_t * cent,vec3_t lookAngles,float lookSpeed,float minPitch,float maxPitch,float minYaw,float maxYaw,float minRoll,float maxRoll)2405 static void CG_UpdateLookAngles( centity_t *cent, vec3_t lookAngles, float lookSpeed, float minPitch, float maxPitch, float minYaw, float maxYaw, float minRoll, float maxRoll )
2406 {
2407 if ( !cent || !cent->gent || !cent->gent->client )
2408 {
2409 return;
2410 }
2411 if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
2412 {
2413 //clamp so don't get "Exorcist" effect
2414 if ( lookAngles[PITCH] > maxPitch )
2415 {
2416 lookAngles[PITCH] = maxPitch;
2417 }
2418 else if ( lookAngles[PITCH] < minPitch )
2419 {
2420 lookAngles[PITCH] = minPitch;
2421 }
2422 if ( lookAngles[YAW] > maxYaw )
2423 {
2424 lookAngles[YAW] = maxYaw;
2425 }
2426 else if ( lookAngles[YAW] < minYaw )
2427 {
2428 lookAngles[YAW] = minYaw;
2429 }
2430 if ( lookAngles[ROLL] > maxRoll )
2431 {
2432 lookAngles[ROLL] = maxRoll;
2433 }
2434 else if ( lookAngles[ROLL] < minRoll )
2435 {
2436 lookAngles[ROLL] = minRoll;
2437 }
2438
2439 //slowly lerp to this new value
2440 //Remember last headAngles
2441 vec3_t oldLookAngles;
2442 VectorCopy( cent->gent->client->renderInfo.lastHeadAngles, oldLookAngles );
2443 vec3_t lookAnglesDiff;
2444 VectorSubtract( lookAngles, oldLookAngles, lookAnglesDiff );
2445
2446 for ( int ang = 0; ang < 3; ang++ )
2447 {
2448 lookAnglesDiff[ang] = AngleNormalize180( lookAnglesDiff[ang] );
2449 }
2450
2451 if( VectorLengthSquared( lookAnglesDiff ) )
2452 {
2453 lookAngles[PITCH] = AngleNormalize180( oldLookAngles[PITCH]+(lookAnglesDiff[PITCH]*cg.frameInterpolation*lookSpeed) );
2454 lookAngles[YAW] = AngleNormalize180( oldLookAngles[YAW]+(lookAnglesDiff[YAW]*cg.frameInterpolation*lookSpeed) );
2455 lookAngles[ROLL] = AngleNormalize180( oldLookAngles[ROLL]+(lookAnglesDiff[ROLL]*cg.frameInterpolation*lookSpeed) );
2456 }
2457 }
2458 //Remember current lookAngles next time
2459 VectorCopy( lookAngles, cent->gent->client->renderInfo.lastHeadAngles );
2460 }
2461
2462 /*
2463 ===============
2464 CG_PlayerAngles
2465
2466 Handles seperate torso motion
2467
2468 legs pivot based on direction of movement
2469
2470 head always looks exactly at cent->lerpAngles
2471
2472 if motion < 20 degrees, show in head only
2473 if < 45 degrees, also show in torso
2474
2475 ===============
2476 */
2477 extern int PM_TurnAnimForLegsAnim( gentity_t *gent, int anim );
2478 extern float PM_GetTimeScaleMod( gentity_t *gent );
CG_G2PlayerAngles(centity_t * cent,vec3_t legs[3],vec3_t angles)2479 static void CG_G2PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t angles )
2480 {
2481 vec3_t headAngles, neckAngles, chestAngles, thoracicAngles = {0,0,0};//legsAngles, torsoAngles,
2482 vec3_t ulAngles, llAngles;
2483 //float speed;
2484 //vec3_t velocity;
2485 vec3_t lookAngles, viewAngles;
2486 /*
2487 float headYawClampMin, headYawClampMax;
2488 float headPitchClampMin, headPitchClampMax;
2489 float torsoYawSwingTolMin, torsoYawSwingTolMax;
2490 float torsoYawClampMin, torsoYawClampMax;
2491 float torsoPitchSwingTolMin, torsoPitchSwingTolMax;
2492 float torsoPitchClampMin, torsoPitchClampMax;
2493 float legsYawSwingTolMin, legsYawSwingTolMax;
2494 float yawSpeed, maxYawSpeed, lookingSpeed;
2495 */
2496 float lookAngleSpeed = LOOK_TALKING_SPEED;//shut up the compiler
2497 //float swing, scale;
2498 //int i;
2499 qboolean looking = qfalse, talking = qfalse;
2500
2501 if ( cent->gent
2502 && (cent->gent->flags&FL_NO_ANGLES) )
2503 {//flatten out all bone angles we might have been overriding
2504 cent->lerpAngles[PITCH] = cent->lerpAngles[ROLL] = 0;
2505 VectorCopy( cent->lerpAngles, angles );
2506
2507 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2508 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->cervicalBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2509 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2510
2511 cent->pe.torso.pitchAngle = 0;
2512 cent->pe.torso.yawAngle = 0;
2513 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2514 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2515
2516 cent->pe.legs.pitchAngle = angles[0];
2517 cent->pe.legs.yawAngle = angles[1];
2518 if ( cent->gent->client )
2519 {
2520 cent->gent->client->renderInfo.legsYaw = angles[1];
2521 }
2522 AnglesToAxis( angles, legs );
2523 return;
2524 }
2525 // Dead entity
2526 if ( cent->gent && cent->gent->health <= 0 )
2527 {
2528 if ( cent->gent->hipsBone != -1 )
2529 {
2530 gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
2531 }
2532
2533 VectorCopy( cent->lerpAngles, angles );
2534
2535 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2536 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->cervicalBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2537 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2538
2539 cent->pe.torso.pitchAngle = 0;
2540 cent->pe.torso.yawAngle = 0;
2541 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->upperLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2542 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw );
2543
2544 cent->pe.legs.pitchAngle = angles[0];
2545 cent->pe.legs.yawAngle = angles[1];
2546 if ( cent->gent->client )
2547 {
2548 cent->gent->client->renderInfo.legsYaw = angles[1];
2549 }
2550 AnglesToAxis( angles, legs );
2551 return;
2552 }
2553
2554 if ( cent->gent && cent->gent->client
2555 && (cent->gent->client->NPC_class != CLASS_GONK )
2556 && (cent->gent->client->NPC_class != CLASS_INTERROGATOR)
2557 && (cent->gent->client->NPC_class != CLASS_SENTRY)
2558 && (cent->gent->client->NPC_class != CLASS_PROBE )
2559 && (cent->gent->client->NPC_class != CLASS_R2D2 )
2560 && (cent->gent->client->NPC_class != CLASS_R5D2)
2561 && (cent->gent->client->NPC_class != CLASS_ATST||!cent->gent->s.number) )
2562 {// If we are rendering third person, we should just force the player body to always fully face
2563 // whatever way they are looking, otherwise, you can end up with gun shots coming off of the
2564 // gun at angles that just look really wrong.
2565
2566 //NOTENOTE: shots are coming out of the gun at ridiculous angles. The head & torso
2567 //should pitch *some* when looking up and down...
2568 VectorCopy( cent->lerpAngles, angles );
2569 angles[PITCH] = 0;
2570
2571 if ( cent->gent->client )
2572 {
2573 if ( cent->gent->client->NPC_class != CLASS_ATST )
2574 {
2575 if ( !PM_SpinningSaberAnim( cent->currentState.legsAnim ) )
2576 {//don't turn legs if in a spinning saber transition
2577 //FIXME: use actual swing/clamp tolerances?
2578 if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE && !PM_InRoll( ¢->gent->client->ps ) )
2579 {//on the ground
2580 CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
2581 }
2582 else
2583 {//face legs to front
2584 CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
2585 }
2586 }
2587 }
2588 }
2589
2590 //VectorClear( viewAngles );
2591 VectorCopy( cent->lerpAngles, viewAngles );
2592 viewAngles[YAW] = viewAngles[ROLL] = 0;
2593 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_RANCOR )
2594 {//rancor uses full pitch
2595 if ( cent->gent->count )
2596 {//don't look up or down at enemy when he's in your hand...
2597 viewAngles[PITCH] = 0.0f;
2598 }
2599 else if ( cent->gent->enemy )
2600 {
2601 if ( cent->gent->enemy->s.solid == SOLID_BMODEL )
2602 {//don't look up or down at architecture?
2603 viewAngles[PITCH] = 0.0f;
2604 }
2605 else if ( cent->gent->client->ps.legsAnim == BOTH_MELEE1 )
2606 {//don't look up or down when smashing the ground
2607 viewAngles[PITCH] = 0.0f;
2608 }
2609 else
2610 {
2611 vec3_t eDir, eAngles, lookFrom;
2612 VectorCopy( cent->lerpOrigin, lookFrom );
2613 lookFrom[2] += cent->gent->maxs[2]*0.6f;
2614 VectorSubtract( cg_entities[cent->gent->enemy->s.number].lerpOrigin, lookFrom, eDir );
2615 vectoangles( eDir, eAngles );
2616 viewAngles[PITCH] = AngleNormalize180(eAngles[0]);
2617 if ( cent->gent->client->ps.legsAnim == BOTH_ATTACK2 )
2618 {//swinging at something on the ground
2619 if ( viewAngles[PITCH] > 0.0f )
2620 {//don't look down
2621 viewAngles[PITCH] = 0.0f;
2622 }
2623 }
2624 else if ( cent->gent->client->ps.legsAnim == BOTH_ATTACK4 )
2625 {//in breath attack anim
2626 if ( viewAngles[PITCH] > 0.0f )
2627 {//don't look down
2628 viewAngles[PITCH] = 0.0f;
2629 }
2630 else
2631 {//exaggerate looking up
2632 viewAngles[PITCH] *= 2.0f;
2633 }
2634 }
2635 else if ( viewAngles[PITCH] > 0.0f )
2636 {//reduce looking down
2637 viewAngles[PITCH] *= 0.5f;
2638 }
2639 //clamp?
2640 /*
2641 if ( viewAngles[PITCH] > 30.0f )
2642 {
2643 viewAngles[PITCH] > 30.0f;
2644 }
2645 if ( viewAngles[PITCH] < -75.0f )
2646 {
2647 viewAngles[PITCH] = -75.0f;
2648 }
2649 */
2650 }
2651 }
2652 else
2653 {
2654 viewAngles[PITCH] = 0.0f;
2655 }
2656 }
2657 else
2658 {
2659 viewAngles[PITCH] *= 0.5;
2660 }
2661 VectorCopy( viewAngles, lookAngles );
2662
2663 // if ( cent->gent && !Q_stricmp( "atst", cent->gent->NPC_type ) )
2664 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
2665 {
2666 lookAngles[YAW] = 0;
2667 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2668 VectorCopy( viewAngles, lookAngles );
2669 }
2670 else
2671 {
2672 if ( cg_turnAnims.integer && !in_camera && cent->gent->hipsBone >= 0 )
2673 {
2674 //override the hips bone with a turn anim when turning
2675 //and clear it when we're not... does blend from and to parent actually work?
2676 int startFrame, endFrame;
2677 const qboolean animatingHips = gi.G2API_GetAnimRangeIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone, &startFrame, &endFrame );
2678
2679 //FIXME: make legs lag behind when turning in place, only play turn anim when legs have to catch up
2680 if ( angles[YAW] == cent->pe.legs.yawAngle )
2681 {
2682 gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
2683 }
2684 else if ( VectorCompare( vec3_origin, cent->gent->client->ps.velocity ) )
2685 {//FIXME: because of LegsYawFromMovement, we play the turnAnims when we stop running, which looks really bad.
2686 int turnAnim = PM_TurnAnimForLegsAnim( cent->gent, cent->gent->client->ps.legsAnim );
2687 if ( turnAnim != -1 && cent->gent->health > 0 )
2688 {
2689 animation_t *animations = level.knownAnimFileSets[cent->gent->client->clientInfo.animFileIndex].animations;
2690
2691 if ( !animatingHips || ( animations[turnAnim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again
2692 {
2693 float animSpeed = 50.0f / animations[turnAnim].frameLerp * PM_GetTimeScaleMod( cent->gent );
2694
2695 gi.G2API_SetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone,
2696 animations[turnAnim].firstFrame, animations[turnAnim].firstFrame+animations[turnAnim].numFrames,
2697 BONE_ANIM_OVERRIDE_LOOP/*|BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND*/, animSpeed, cg.time, -1, 100 );
2698 }
2699 }
2700 else
2701 {
2702 gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
2703 }
2704 }
2705 else
2706 {
2707 gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
2708 }
2709 }
2710
2711 CG_G2ClientSpineAngles( cent, viewAngles, angles, thoracicAngles, ulAngles, llAngles );
2712 }
2713
2714 vec3_t trailingLegsAngles;
2715 if ( cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
2716 {
2717 CG_ATSTLegsYaw( cent, trailingLegsAngles );
2718 AnglesToAxis( trailingLegsAngles, legs );
2719 angles[YAW] = trailingLegsAngles[YAW];
2720 }
2721 /*
2722 else if ( cent->gent->client && cent->gent->client->NPC_class == CLASS_WAMPA )
2723 {
2724 CG_ATSTLegsYaw( cent, trailingLegsAngles );
2725 AnglesToAxis( trailingLegsAngles, legs );
2726 angles[YAW] = trailingLegsAngles[YAW];
2727 }
2728 */
2729 // either riding a vehicle or we are a vehicle
2730 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_VEHICLE )
2731 {//you are a vehicle, just use your lerpAngles which comes from m_vOrientation
2732 cent->pe.legs.yawing = qfalse;
2733 cent->pe.legs.yawAngle = cent->lerpAngles[YAW];
2734 if ( cent->gent->client )
2735 {
2736 cent->gent->client->renderInfo.legsYaw = cent->lerpAngles[YAW];
2737 }
2738 AnglesToAxis( cent->lerpAngles, legs );
2739 if ( cent->gent->m_pVehicle )
2740 {
2741 if ( cent->gent->m_pVehicle->m_pVehicleInfo )
2742 {
2743 if ( cent->gent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER
2744 || cent->gent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER )
2745 {
2746 VectorCopy( cent->lerpAngles, angles );
2747 }
2748 }
2749 }
2750 }
2751 else if ( G_IsRidingVehicle( cent->gent ) )
2752 {//riding a vehicle, get the vehicle's lerpAngles (which comes from m_vOrientation)
2753 cent->pe.legs.yawing = qfalse;
2754 cent->pe.legs.yawAngle = cg_entities[cent->gent->owner->s.number].lerpAngles[YAW];
2755 if ( cent->gent->client )
2756 {
2757 cent->gent->client->renderInfo.legsYaw = cg_entities[cent->gent->owner->s.number].lerpAngles[YAW];
2758 }
2759 AnglesToAxis( cg_entities[cent->gent->owner->s.number].lerpAngles, legs );
2760 }
2761 else
2762 {
2763
2764 //set the legs.yawing field so we play the turning anim when turning in place
2765 if ( angles[YAW] == cent->pe.legs.yawAngle )
2766 {
2767 cent->pe.legs.yawing = qfalse;
2768 }
2769 else
2770 {
2771 cent->pe.legs.yawing = qtrue;
2772 }
2773 cent->pe.legs.yawAngle = angles[YAW];
2774 if ( cent->gent->client )
2775 {
2776 cent->gent->client->renderInfo.legsYaw = angles[YAW];
2777 }
2778 if ( ((cent->gent->client->ps.eFlags&EF_FORCE_GRIPPED)||((cent->gent->client->NPC_class == CLASS_BOBAFETT||cent->gent->client->NPC_class == CLASS_ROCKETTROOPER)&¢->gent->client->moveType==MT_FLYSWIM))
2779 && cent->gent->client->ps.groundEntityNum == ENTITYNUM_NONE )
2780 {
2781 vec3_t centFwd, centRt;
2782 float divFactor = 1.0f;
2783 if ( (cent->gent->client->NPC_class == CLASS_BOBAFETT||cent->gent->client->NPC_class == CLASS_ROCKETTROOPER)
2784 && cent->gent->client->moveType == MT_FLYSWIM )
2785 {
2786 divFactor = 3.0f;
2787 }
2788
2789 AngleVectors( cent->lerpAngles, centFwd, centRt, NULL );
2790 angles[PITCH] = AngleNormalize180( DotProduct( cent->gent->client->ps.velocity, centFwd )/(2*divFactor) );
2791 if ( angles[PITCH] > 90 )
2792 {
2793 angles[PITCH] = 90;
2794 }
2795 else if ( angles[PITCH] < -90 )
2796 {
2797 angles[PITCH] = -90;
2798 }
2799 angles[ROLL] = AngleNormalize180( DotProduct( cent->gent->client->ps.velocity, centRt )/(10*divFactor) );
2800 if ( angles[ROLL] > 90 )
2801 {
2802 angles[ROLL] = 90;
2803 }
2804 else if ( angles[ROLL] < -90 )
2805 {
2806 angles[ROLL] = -90;
2807 }
2808 }
2809 AnglesToAxis( angles, legs );
2810 }
2811
2812 //clamp relative to forward of cervical bone!
2813 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
2814 {
2815 looking = qfalse;
2816 VectorCopy( vec3_origin, chestAngles );
2817 }
2818 else
2819 {
2820 //look at lookTarget!
2821 float lookingSpeed = 0.3f;
2822 looking = CG_CheckLookTarget( cent, lookAngles, &lookingSpeed );
2823 //Now add head bob when talking
2824 talking = CG_AddHeadBob( cent, lookAngles );
2825
2826 //NOTE: previously, lookAngleSpeed was always 0.25f for the player
2827 //Figure out how fast head should be turning
2828 if ( cent->pe.torso.yawing || cent->pe.torso.pitching )
2829 {//If torso is turning, we want to turn head just as fast
2830 if ( cent->gent->NPC )
2831 {
2832 lookAngleSpeed = cent->gent->NPC->stats.yawSpeed/150;//about 0.33 normally
2833 }
2834 else
2835 {
2836 lookAngleSpeed = CG_SWINGSPEED;
2837 }
2838 }
2839 else if ( talking )
2840 {//Slow for head bobbing
2841 lookAngleSpeed = LOOK_TALKING_SPEED;
2842 }
2843 else if ( looking )
2844 {//Not talking, set it up for looking at enemy, CheckLookTarget will scale it down if neccessary
2845 lookAngleSpeed = lookingSpeed;
2846 }
2847 else if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
2848 {//Not looking, not talking, head is returning from a talking head bob, use talking speed
2849 lookAngleSpeed = LOOK_TALKING_SPEED;
2850 }
2851
2852 if ( looking || talking )
2853 {//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap)
2854 //we should have a relative look angle, normalized to 180
2855 cent->gent->client->renderInfo.lookingDebounceTime = cg.time + 1000;
2856 }
2857 else
2858 {
2859 //still have a relative look angle from above
2860 }
2861
2862 if ( cent->gent->client->NPC_class == CLASS_RANCOR )
2863 {//always use the viewAngles we calced
2864 VectorCopy( viewAngles, lookAngles );
2865 }
2866 CG_UpdateLookAngles( cent, lookAngles, lookAngleSpeed, -50.0f, 50.0f, -70.0f, 70.0f, -30.0f, 30.0f );
2867 }
2868
2869 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
2870 {
2871 VectorCopy( cent->lerpAngles, lookAngles );
2872 lookAngles[0] = lookAngles[2] = 0;
2873 lookAngles[YAW] -= trailingLegsAngles[YAW];
2874 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2875 }
2876 else
2877 {
2878 vec3_t headClampMinAngles = {-25,-55,-10}, headClampMaxAngles = {50,50,10};
2879 CG_G2ClientNeckAngles( cent, lookAngles, headAngles, neckAngles, thoracicAngles, headClampMinAngles, headClampMaxAngles );
2880 }
2881 return;
2882 }
2883 // All other entities
2884 else if ( cent->gent && cent->gent->client )
2885 {
2886 if ( (cent->gent->client->NPC_class == CLASS_PROBE )
2887 || (cent->gent->client->NPC_class == CLASS_R2D2 )
2888 || (cent->gent->client->NPC_class == CLASS_R5D2)
2889 || (cent->gent->client->NPC_class == CLASS_RANCOR)
2890 || (cent->gent->client->NPC_class == CLASS_WAMPA)
2891 || (cent->gent->client->NPC_class == CLASS_ATST) )
2892 {
2893 VectorCopy( cent->lerpAngles, angles );
2894 angles[PITCH] = 0;
2895
2896 //FIXME: use actual swing/clamp tolerances?
2897 if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE )
2898 {//on the ground
2899 CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
2900 }
2901 else
2902 {//face legs to front
2903 CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
2904 }
2905
2906 VectorCopy( cent->lerpAngles, viewAngles );
2907 // viewAngles[YAW] = viewAngles[ROLL] = 0;
2908 viewAngles[PITCH] *= 0.5;
2909 VectorCopy( viewAngles, lookAngles );
2910
2911 lookAngles[1] = 0;
2912
2913 if ( cent->gent->client->NPC_class == CLASS_ATST )
2914 {//body pitch
2915 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT,POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
2916 }
2917
2918 VectorCopy( viewAngles, lookAngles );
2919
2920 vec3_t trailingLegsAngles;
2921 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
2922 {
2923 CG_ATSTLegsYaw( cent, trailingLegsAngles );
2924 AnglesToAxis( trailingLegsAngles, legs );
2925 }
2926 /*
2927 else if ( cent->gent->client
2928 && (cent->gent->client->NPC_class == CLASS_WAMPA||cent->gent->client->NPC_class == CLASS_RANCOR) )
2929 {
2930 CG_ATSTLegsYaw( cent, trailingLegsAngles );
2931 AnglesToAxis( trailingLegsAngles, legs );
2932 }
2933 */
2934 else
2935 {
2936 //FIXME: this needs to properly set the legs.yawing field so we don't erroneously play the turning anim, but we do play it when turning in place
2937 if ( angles[YAW] == cent->pe.legs.yawAngle )
2938 {
2939 cent->pe.legs.yawing = qfalse;
2940 }
2941 else
2942 {
2943 cent->pe.legs.yawing = qtrue;
2944 }
2945
2946 cent->pe.legs.yawAngle = angles[YAW];
2947 if ( cent->gent->client )
2948 {
2949 cent->gent->client->renderInfo.legsYaw = angles[YAW];
2950 }
2951 AnglesToAxis( angles, legs );
2952 }
2953
2954 // if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST )
2955 // {
2956 // looking = qfalse;
2957 // }
2958 // else
2959 { //look at lookTarget!
2960 //FIXME: snaps to side when lets go of lookTarget... ?
2961 float lookingSpeed = 0.3f;
2962 looking = CG_CheckLookTarget( cent, lookAngles, &lookingSpeed );
2963 lookAngles[PITCH] = lookAngles[ROLL] = 0;//droids can't pitch or roll their heads
2964 if ( looking )
2965 {//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap)
2966 cent->gent->client->renderInfo.lookingDebounceTime = cg.time + 1000;
2967 }
2968 }
2969 if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
2970 { //adjust for current body orientation
2971 lookAngles[YAW] -= cent->pe.torso.yawAngle;
2972 lookAngles[YAW] -= cent->pe.legs.yawAngle;
2973
2974 //normalize
2975 lookAngles[YAW] = AngleNormalize180( lookAngles[YAW] );
2976
2977 //slowly lerp to this new value
2978 //Remember last headAngles
2979 vec3_t oldLookAngles;
2980 VectorCopy( cent->gent->client->renderInfo.lastHeadAngles, oldLookAngles );
2981 if( VectorCompare( oldLookAngles, lookAngles ) == qfalse )
2982 {
2983 //FIXME: This clamp goes off viewAngles,
2984 //but really should go off the tag_torso's axis[0] angles, no?
2985 lookAngles[YAW] = oldLookAngles[YAW]+(lookAngles[YAW]-oldLookAngles[YAW])*cg.frameInterpolation*0.25;
2986 }
2987 //Remember current lookAngles next time
2988 VectorCopy( lookAngles, cent->gent->client->renderInfo.lastHeadAngles );
2989 }
2990 else
2991 {//Remember current lookAngles next time
2992 VectorCopy( lookAngles, cent->gent->client->renderInfo.lastHeadAngles );
2993 }
2994 if ( cent->gent->client->NPC_class == CLASS_ATST )
2995 {
2996 VectorCopy( cent->lerpAngles, lookAngles );
2997 lookAngles[0] = lookAngles[2] = 0;
2998 lookAngles[YAW] -= trailingLegsAngles[YAW];
2999 }
3000 else
3001 {
3002 lookAngles[PITCH] = lookAngles[ROLL] = 0;
3003 lookAngles[YAW] -= cent->pe.legs.yawAngle;
3004 }
3005 if ( cent->gent->client->NPC_class == CLASS_WAMPA )
3006 {
3007 Eorientations oUp, oRt, oFwd;
3008 G_BoneOrientationsForClass( cent->gent->client->NPC_class, "cranium", &oUp, &oRt, &oFwd );
3009 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, cgs.model_draw );
3010 }
3011 else
3012 {
3013 BG_G2SetBoneAngles( cent, cent->gent, cent->gent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
3014 }
3015 //return;
3016 }
3017 else//if ( (cent->gent->client->NPC_class == CLASS_GONK ) || (cent->gent->client->NPC_class == CLASS_INTERROGATOR) || (cent->gent->client->NPC_class == CLASS_SENTRY) )
3018 {
3019 VectorCopy( cent->lerpAngles, angles );
3020 cent->pe.torso.pitchAngle = 0;
3021 cent->pe.torso.yawAngle = 0;
3022 cent->pe.legs.pitchAngle = angles[0];
3023 cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = angles[1];
3024 AnglesToAxis( angles, legs );
3025 //return;
3026 }
3027 }
3028 }
3029
CG_PlayerAngles(centity_t * cent,vec3_t legs[3],vec3_t torso[3],vec3_t head[3])3030 static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] )
3031 {
3032 vec3_t legsAngles, torsoAngles, headAngles;
3033 vec3_t lookAngles, viewAngles;
3034 float headYawClampMin, headYawClampMax;
3035 float headPitchClampMin, headPitchClampMax;
3036 float torsoYawSwingTolMin, torsoYawSwingTolMax;
3037 float torsoYawClampMin, torsoYawClampMax;
3038 float torsoPitchSwingTolMin, torsoPitchSwingTolMax;
3039 float torsoPitchClampMin, torsoPitchClampMax;
3040 float legsYawSwingTolMin, legsYawSwingTolMax;
3041 float maxYawSpeed, yawSpeed, lookingSpeed;
3042 float lookAngleSpeed = LOOK_TALKING_SPEED;//shut up the compiler
3043 float swing, scale;
3044 int i;
3045 qboolean looking = qfalse, talking = qfalse;
3046
3047 if ( cg.renderingThirdPerson && cent->gent && cent->gent->s.number == 0 )
3048 {
3049 // If we are rendering third person, we should just force the player body to always fully face
3050 // whatever way they are looking, otherwise, you can end up with gun shots coming off of the
3051 // gun at angles that just look really wrong.
3052
3053 //NOTENOTE: shots are coming out of the gun at ridiculous angles. The head & torso
3054 //should pitch *some* when looking up and down...
3055
3056 //VectorClear( viewAngles );
3057 VectorCopy( cent->lerpAngles, viewAngles );
3058
3059 viewAngles[YAW] = viewAngles[ROLL] = 0;
3060 viewAngles[PITCH] *= 0.5;
3061 AnglesToAxis( viewAngles, head );
3062
3063 viewAngles[PITCH] *= 0.75;
3064 cent->pe.torso.pitchAngle = viewAngles[PITCH];
3065 cent->pe.torso.yawAngle = viewAngles[YAW];
3066 AnglesToAxis( viewAngles, torso );
3067
3068 VectorCopy( cent->lerpAngles, lookAngles );
3069 lookAngles[PITCH] = 0;
3070
3071 //FIXME: this needs to properly set the legs.yawing field so we don't erroneously play the turning anim, but we do play it when turning in place
3072 if ( lookAngles[YAW] == cent->pe.legs.yawAngle )
3073 {
3074 cent->pe.legs.yawing = qfalse;
3075 }
3076 else
3077 {
3078 cent->pe.legs.yawing = qtrue;
3079 }
3080
3081 if ( cent->gent->client->ps.velocity[0] || cent->gent->client->ps.velocity[1] )
3082 {
3083 float moveYaw;
3084 moveYaw = vectoyaw( cent->gent->client->ps.velocity );
3085 lookAngles[YAW] = cent->lerpAngles[YAW] + AngleDelta( cent->lerpAngles[YAW], moveYaw );
3086 }
3087
3088 cent->pe.legs.yawAngle = lookAngles[YAW];
3089 if ( cent->gent->client )
3090 {
3091 cent->gent->client->renderInfo.legsYaw = lookAngles[YAW];
3092 }
3093 AnglesToAxis( lookAngles, legs );
3094
3095 return;
3096 }
3097
3098 if ( cent->currentState.clientNum != 0 )
3099 {
3100 headYawClampMin = -cent->gent->client->renderInfo.headYawRangeLeft;
3101 headYawClampMax = cent->gent->client->renderInfo.headYawRangeRight;
3102 //These next two are only used for a calc below- this clamp is done in PM_UpdateViewAngles
3103 headPitchClampMin = -cent->gent->client->renderInfo.headPitchRangeUp;
3104 headPitchClampMax = cent->gent->client->renderInfo.headPitchRangeDown;
3105
3106 torsoYawSwingTolMin = headYawClampMin * 0.3;
3107 torsoYawSwingTolMax = headYawClampMax * 0.3;
3108 torsoPitchSwingTolMin = headPitchClampMin * 0.5;
3109 torsoPitchSwingTolMax = headPitchClampMax * 0.5;
3110 torsoYawClampMin = -cent->gent->client->renderInfo.torsoYawRangeLeft;
3111 torsoYawClampMax = cent->gent->client->renderInfo.torsoYawRangeRight;
3112 torsoPitchClampMin = -cent->gent->client->renderInfo.torsoPitchRangeUp;
3113 torsoPitchClampMax = cent->gent->client->renderInfo.torsoPitchRangeDown;
3114
3115 legsYawSwingTolMin = torsoYawClampMin * 0.5;
3116 legsYawSwingTolMax = torsoYawClampMax * 0.5;
3117
3118 if ( cent->gent && cent->gent->next_roff_time && cent->gent->next_roff_time >= cg.time )
3119 {//Following a roff, body must keep up with head, yaw-wise
3120 headYawClampMin =
3121 headYawClampMax =
3122 torsoYawSwingTolMin =
3123 torsoYawSwingTolMax =
3124 torsoYawClampMin =
3125 torsoYawClampMax =
3126 legsYawSwingTolMin =
3127 legsYawSwingTolMax = 0;
3128 }
3129
3130 yawSpeed = maxYawSpeed = cent->gent->NPC->stats.yawSpeed/150;//about 0.33 normally
3131 }
3132 else
3133 {
3134 headYawClampMin = -70;
3135 headYawClampMax = 70;
3136
3137 //These next two are only used for a calc below- this clamp is done in PM_UpdateViewAngles
3138 headPitchClampMin = -90;
3139 headPitchClampMax = 90;
3140
3141 torsoYawSwingTolMin = -90;
3142 torsoYawSwingTolMax = 90;
3143 torsoPitchSwingTolMin = -90;
3144 torsoPitchSwingTolMax = 90;
3145 torsoYawClampMin = -90;
3146 torsoYawClampMax = 90;
3147 torsoPitchClampMin = -90;
3148 torsoPitchClampMax = 90;
3149
3150 legsYawSwingTolMin = -90;
3151 legsYawSwingTolMax = 90;
3152
3153 yawSpeed = maxYawSpeed = CG_SWINGSPEED;
3154 }
3155
3156 if(yawSpeed <= 0)
3157 {//Just in case
3158 yawSpeed = 0.5f; //was 0.33
3159 }
3160
3161 lookingSpeed = yawSpeed;
3162
3163 VectorCopy( cent->lerpAngles, headAngles );
3164 headAngles[YAW] = AngleNormalize360( headAngles[YAW] );
3165 VectorClear( legsAngles );
3166 VectorClear( torsoAngles );
3167
3168 // --------- yaw -------------
3169
3170 //Clamp and swing the legs
3171 legsAngles[YAW] = headAngles[YAW];
3172
3173 if(cent->gent->client->renderInfo.renderFlags & RF_LOCKEDANGLE)
3174 {
3175 cent->gent->client->renderInfo.legsYaw = cent->pe.legs.yawAngle = cent->gent->client->renderInfo.lockYaw;
3176 cent->pe.legs.yawing = qfalse;
3177 legsAngles[YAW] = cent->pe.legs.yawAngle;
3178 }
3179 else
3180 {
3181 qboolean alwaysFace = qfalse;
3182 if ( cent->gent && cent->gent->health > 0 )
3183 {
3184 if ( cent->gent->enemy )
3185 {
3186 alwaysFace = qtrue;
3187 }
3188 if ( CG_PlayerLegsYawFromMovement( cent, cent->gent->client->ps.velocity, &legsAngles[YAW], headAngles[YAW], torsoYawClampMin, torsoYawClampMax, alwaysFace ) )
3189 {
3190 if ( legsAngles[YAW] == cent->pe.legs.yawAngle )
3191 {
3192 cent->pe.legs.yawing = qfalse;
3193 }
3194 else
3195 {
3196 cent->pe.legs.yawing = qtrue;
3197 }
3198 cent->pe.legs.yawAngle = legsAngles[YAW];
3199 if ( cent->gent->client )
3200 {
3201 cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
3202 }
3203 }
3204 else
3205 {
3206 CG_SwingAngles( legsAngles[YAW], legsYawSwingTolMin, legsYawSwingTolMax, torsoYawClampMin, torsoYawClampMax, maxYawSpeed, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
3207 legsAngles[YAW] = cent->pe.legs.yawAngle;
3208 if ( cent->gent->client )
3209 {
3210 cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
3211 }
3212 }
3213 }
3214 else
3215 {
3216 CG_SwingAngles( legsAngles[YAW], legsYawSwingTolMin, legsYawSwingTolMax, torsoYawClampMin, torsoYawClampMax, maxYawSpeed, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
3217 legsAngles[YAW] = cent->pe.legs.yawAngle;
3218 if ( cent->gent->client )
3219 {
3220 cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
3221 }
3222 }
3223 }
3224
3225 /*
3226 legsAngles[YAW] = cent->pe.legs.yawAngle;
3227 if ( cent->gent->client )
3228 {
3229 cent->gent->client->renderInfo.legsYaw = legsAngles[YAW];
3230 }
3231 */
3232
3233 // torso
3234 // If applicable, swing the lower parts to catch up with the head
3235 CG_SwingAngles( headAngles[YAW], torsoYawSwingTolMin, torsoYawSwingTolMax, headYawClampMin, headYawClampMax, yawSpeed, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing);
3236 torsoAngles[YAW] = cent->pe.torso.yawAngle;
3237
3238 // ---------- pitch -----------
3239
3240 //As the body twists to its extents, the back tends to arch backwards
3241
3242
3243 float dest;
3244 // only show a fraction of the pitch angle in the torso
3245 if ( headAngles[PITCH] > 180 )
3246 {
3247 dest = (-360 + headAngles[PITCH]) * 0.75;
3248 }
3249 else
3250 {
3251 dest = headAngles[PITCH] * 0.75;
3252 }
3253
3254 CG_SwingAngles( dest, torsoPitchSwingTolMin, torsoPitchSwingTolMax, torsoPitchClampMin, torsoPitchClampMax, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching );
3255 torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
3256 // --------- roll -------------
3257
3258 // pain twitch - FIXME: don't do this if you have no head (like droids?)
3259 // Maybe need to have clamp angles for roll as well as pitch and yaw?
3260 //CG_AddPainTwitch( cent, torsoAngles );
3261
3262 //----------- Special head looking ---------------
3263
3264 //FIXME: to clamp the head angles, figure out tag_head's offset from tag_torso and add
3265 // that to whatever offset we're getting here... so turning the head in an
3266 // anim that also turns the head doesn't allow the head to turn out of range.
3267
3268 //Start with straight ahead
3269 VectorCopy( headAngles, viewAngles );
3270 VectorCopy( headAngles, lookAngles );
3271
3272 //Remember last headAngles
3273 VectorCopy( cent->gent->client->renderInfo.lastHeadAngles, headAngles );
3274
3275 //See if we're looking at someone/thing
3276 looking = CG_CheckLookTarget( cent, lookAngles, &lookingSpeed );
3277
3278 //Now add head bob when talking
3279 /* if ( cent->gent->client->clientInfo.extensions )
3280 {
3281 talking = CG_AddHeadBob( cent, lookAngles );
3282 }
3283 */
3284 //Figure out how fast head should be turning
3285 if ( cent->pe.torso.yawing || cent->pe.torso.pitching )
3286 {//If torso is turning, we want to turn head just as fast
3287 lookAngleSpeed = yawSpeed;
3288 }
3289 else if ( talking )
3290 {//Slow for head bobbing
3291 lookAngleSpeed = LOOK_TALKING_SPEED;
3292 }
3293 else if ( looking )
3294 {//Not talking, set it up for looking at enemy, CheckLookTarget will scale it down if neccessary
3295 lookAngleSpeed = lookingSpeed;
3296 }
3297 else if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
3298 {//Not looking, not talking, head is returning from a talking head bob, use talking speed
3299 lookAngleSpeed = LOOK_TALKING_SPEED;
3300 }
3301
3302 if ( looking || talking )
3303 {//Keep this type of looking for a second after stopped looking
3304 cent->gent->client->renderInfo.lookingDebounceTime = cg.time + 1000;
3305 }
3306
3307 if ( cent->gent->client->renderInfo.lookingDebounceTime > cg.time )
3308 {
3309 //Calc our actual desired head angles
3310 for ( i = 0; i < 3; i++ )
3311 {
3312 lookAngles[i] = AngleNormalize360( cent->gent->client->renderInfo.headBobAngles[i] + lookAngles[i] );
3313 }
3314
3315 if( VectorCompare( headAngles, lookAngles ) == qfalse )
3316 {
3317 //FIXME: This clamp goes off viewAngles,
3318 //but really should go off the tag_torso's axis[0] angles, no?
3319 CG_UpdateAngleClamp( lookAngles[PITCH], headPitchClampMin/1.25, headPitchClampMax/1.25, lookAngleSpeed, &headAngles[PITCH], viewAngles[PITCH] );
3320 CG_UpdateAngleClamp( lookAngles[YAW], headYawClampMin/1.25, headYawClampMax/1.25, lookAngleSpeed, &headAngles[YAW], viewAngles[YAW] );
3321 CG_UpdateAngleClamp( lookAngles[ROLL], -10, 10, lookAngleSpeed, &headAngles[ROLL], viewAngles[ROLL] );
3322 }
3323
3324 if ( !cent->gent->enemy || cent->gent->enemy->s.number != cent->gent->client->renderInfo.lookTarget )
3325 {
3326 //NOTE: Hacky, yes, I know, but necc.
3327 //We want to turn the body to follow the lookTarget
3328 //ONLY IF WE DON'T HAVE AN ENEMY OR OUR ENEMY IS NOT OUR LOOKTARGET
3329 //This is the piece of code that was making the enemies not face where
3330 //they were actually aiming.
3331
3332 //Yaw change
3333 swing = AngleSubtract( legsAngles[YAW], headAngles[YAW] );
3334 scale = fabs( swing ) / ( torsoYawClampMax + 0.01 ); //NOTENOTE: Some ents have a clamp of 0, which is bad for division
3335
3336 scale *= LOOK_SWING_SCALE;
3337 torsoAngles[YAW] = legsAngles[YAW] - ( swing * scale );
3338
3339 //Pitch change
3340 swing = AngleSubtract( legsAngles[PITCH], headAngles[PITCH] );
3341 scale = fabs( swing ) / ( torsoPitchClampMax + 0.01 ); //NOTENOTE: Some ents have a clamp of 0, which is bad for division
3342
3343 scale *= LOOK_SWING_SCALE;
3344 torsoAngles[PITCH] = legsAngles[PITCH] - ( swing * scale );
3345 }
3346 }
3347 else
3348 {//Look straight ahead
3349 VectorCopy( viewAngles, headAngles );
3350 }
3351
3352 //Remember current headAngles next time
3353 VectorCopy( headAngles, cent->gent->client->renderInfo.lastHeadAngles );
3354
3355 //-------------------------------------------------------------
3356
3357 // pull the angles back out of the hierarchial chain
3358 AnglesSubtract( headAngles, torsoAngles, headAngles );
3359 AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
3360 AnglesToAxis( legsAngles, legs );
3361 AnglesToAxis( torsoAngles, torso );
3362 AnglesToAxis( headAngles, head );
3363 }
3364
3365
3366 //==========================================================================
3367
3368
3369 /*
3370 ===============
3371 CG_TrailItem
3372 ===============
3373 */
3374 /*
3375 static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) {
3376 refEntity_t ent;
3377 vec3_t angles;
3378 vec3_t axis[3];
3379
3380 VectorCopy( cent->lerpAngles, angles );
3381 angles[PITCH] = 0;
3382 angles[ROLL] = 0;
3383 AnglesToAxis( angles, axis );
3384
3385 memset( &ent, 0, sizeof( ent ) );
3386 VectorMA( cent->lerpOrigin, -24, axis[0], ent.origin );
3387 ent.origin[2] += 20;
3388 VectorScale( cg.autoAxis[0], 0.75, ent.axis[0] );
3389 VectorScale( cg.autoAxis[1], 0.75, ent.axis[1] );
3390 VectorScale( cg.autoAxis[2], 0.75, ent.axis[2] );
3391 ent.hModel = hModel;
3392 cgi_R_AddRefEntityToScene( &ent );
3393 }
3394 */
3395
3396 /*
3397 ===============
3398 CG_PlayerPowerups
3399 ===============
3400 */
3401 extern void CG_Seeker( centity_t *cent );
CG_PlayerPowerups(centity_t * cent)3402 static void CG_PlayerPowerups( centity_t *cent )
3403 {
3404 if ( !cent->currentState.powerups )
3405 {
3406 return;
3407 }
3408 /*
3409
3410 // quad gives a dlight
3411 if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) {
3412 cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 0.2, 1 );
3413 }
3414
3415 // redflag
3416 if ( cent->currentState.powerups & ( 1 << PW_REDFLAG ) ) {
3417 CG_TrailItem( cent, cgs.media.redFlagModel );
3418 cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1, 0.2, 0.2 );
3419 }
3420
3421 // blueflag
3422 if ( cent->currentState.powerups & ( 1 << PW_BLUEFLAG ) ) {
3423 CG_TrailItem( cent, cgs.media.blueFlagModel );
3424 cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 0.2, 1 );
3425 }
3426 */
3427 // invul gives a dlight
3428 // if ( cent->currentState.powerups & ( 1 << PW_BATTLESUIT ) )
3429 // {
3430 // cgi_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.8f, 0.8f, 0.2f );
3431 // }
3432
3433 // seeker coolness
3434 /* if ( cent->currentState.powerups & ( 1 << PW_SEEKER ) )
3435 {
3436 // CG_Seeker(cent);
3437 }*/
3438 }
3439
3440 #define SHADOW_DISTANCE 128
_PlayerShadow(const vec3_t origin,const float orientation,float * const shadowPlane,const float radius,qhandle_t markShader)3441 static qboolean _PlayerShadow( const vec3_t origin, const float orientation, float *const shadowPlane, const float radius, qhandle_t markShader ) {
3442 vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2};
3443 trace_t trace;
3444 float alpha;
3445
3446 // send a trace down from the player to the ground
3447 VectorCopy( origin, end );
3448 end[2] -= SHADOW_DISTANCE;
3449
3450 cgi_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID );
3451
3452 // no shadow if too high
3453 if ( trace.fraction == 1.0 || (trace.startsolid && trace.allsolid) ) {
3454 return qfalse;
3455 }
3456
3457 *shadowPlane = trace.endpos[2] + 1;
3458
3459 // no mark for stencil or projection shadows
3460 if ( cg_shadows.integer == 1
3461 || (in_camera && cg_shadows.integer == 2) )//don't want stencil shadows during a cinematic
3462 {
3463 // fade the shadow out with height
3464 alpha = 1.0 - trace.fraction;
3465
3466 // add the mark as a temporary, so it goes directly to the renderer
3467 // without taking a spot in the cg_marks array
3468 CG_ImpactMark( markShader, trace.endpos, trace.plane.normal,
3469 orientation, 1,1,1,alpha, qfalse, radius, qtrue );
3470 }
3471 return qtrue;
3472 }
3473
3474 /*
3475 ===============
3476 CG_PlayerShadow
3477
3478 Returns the Z component of the surface being shadowed
3479
3480 should it return a full plane instead of a Z?
3481 ===============
3482 */
CG_PlayerShadow(centity_t * const cent,float * const shadowPlane)3483 static qboolean CG_PlayerShadow( centity_t *const cent, float *const shadowPlane ) {
3484 *shadowPlane = 0;
3485
3486 if ( cg_shadows.integer == 0 ) {
3487 return qfalse;
3488 }
3489
3490 // no shadows when cloaked
3491 if ( cent->currentState.powerups & ( 1 << PW_CLOAKED ))
3492 {
3493 return qfalse;
3494 }
3495
3496 if ( cent->gent->client->NPC_class == CLASS_SAND_CREATURE )
3497 {//sand creatures have no shadow
3498 return qfalse;
3499 }
3500
3501 vec3_t rootOrigin;
3502 vec3_t tempAngles;
3503 tempAngles[PITCH] = 0;
3504 tempAngles[YAW] = cent->pe.legs.yawAngle;
3505 tempAngles[ROLL] = 0;
3506 if (cent->gent->rootBone>=0 && cent->gent->ghoul2.IsValid() && cent->gent->ghoul2[0].animModelIndexOffset)//If it has an animOffset it's a cinematic anim
3507 { //i might be running out of my bounding box, so get my root origin
3508 mdxaBone_t boltMatrix;
3509 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->rootBone,
3510 &boltMatrix, tempAngles, cent->lerpOrigin,
3511 cg.time, cgs.model_draw, cent->currentState.modelScale);
3512 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, rootOrigin );
3513 }
3514 else
3515 {
3516 VectorCopy(cent->lerpOrigin,rootOrigin);
3517 }
3518
3519 if ( DistanceSquared( cg.refdef.vieworg, rootOrigin ) > cg_shadowCullDistance.value * cg_shadowCullDistance.value )
3520 {
3521 // Shadow is too far away, don't do any traces, don't do any marks...blah
3522 return qfalse;
3523 }
3524
3525 if (cent->gent->client->NPC_class == CLASS_ATST)
3526 {
3527 qboolean bShadowed;
3528 mdxaBone_t boltMatrix;
3529 vec3_t sideOrigin;
3530
3531 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footLBolt,
3532 &boltMatrix, tempAngles, cent->lerpOrigin,
3533 cg.time, cgs.model_draw, cent->currentState.modelScale);
3534 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
3535 sideOrigin[2] += 30; //fudge up a bit for coplaner
3536 bShadowed = _PlayerShadow(sideOrigin, 0, shadowPlane, 28, cgs.media.shadowMarkShader);
3537
3538 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footRBolt,
3539 &boltMatrix, tempAngles, cent->lerpOrigin, cg.time,
3540 cgs.model_draw, cent->currentState.modelScale);
3541 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
3542 sideOrigin[2] += 30; //fudge up a bit for coplaner
3543 bShadowed = (qboolean)(_PlayerShadow(sideOrigin, 0, shadowPlane, 28, cgs.media.shadowMarkShader) || bShadowed);
3544
3545 bShadowed = (qboolean)( _PlayerShadow(rootOrigin, cent->pe.legs.yawAngle, shadowPlane, 64, cgs.media.shadowMarkShader) || bShadowed);
3546 return bShadowed;
3547 }
3548 else if ( cent->gent->client->NPC_class == CLASS_RANCOR )
3549 {
3550 return _PlayerShadow(rootOrigin, cent->pe.legs.yawAngle, shadowPlane, 64, cgs.media.shadowMarkShader);
3551 }
3552 else
3553 {
3554 return _PlayerShadow(rootOrigin, cent->pe.legs.yawAngle, shadowPlane, 16, cgs.media.shadowMarkShader);
3555 }
3556
3557 }
3558
CG_LandingEffect(vec3_t origin,vec3_t normal,int material)3559 void CG_LandingEffect( vec3_t origin, vec3_t normal, int material )
3560 {
3561 int effectID = -1;
3562 switch ( material )
3563 {
3564 case MATERIAL_MUD:
3565 effectID = cgs.effects.landingMud;
3566 break;
3567 case MATERIAL_DIRT:
3568 effectID = cgs.effects.landingDirt;
3569 break;
3570 case MATERIAL_SAND:
3571 effectID = cgs.effects.landingSand;
3572 break;
3573 case MATERIAL_SNOW:
3574 effectID = cgs.effects.landingSnow;
3575 break;
3576 case MATERIAL_GRAVEL:
3577 effectID = cgs.effects.landingGravel;
3578 break;
3579 }
3580
3581 if ( effectID != -1 )
3582 {
3583 theFxScheduler.PlayEffect( effectID, origin, normal );
3584 }
3585 }
3586 #define FOOTSTEP_DISTANCE 32
_PlayerFootStep(const vec3_t origin,const vec3_t traceDir,const float orientation,const float radius,centity_t * const cent,footstepType_t footStepType)3587 static void _PlayerFootStep( const vec3_t origin,
3588 const vec3_t traceDir,
3589 const float orientation,
3590 const float radius,
3591 centity_t *const cent, footstepType_t footStepType )
3592 {
3593 vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2};
3594 trace_t trace;
3595 footstep_t soundType = FOOTSTEP_TOTAL;
3596 bool bMark = false;
3597 int effectID = -1;
3598 //float alpha;
3599
3600 // send a trace down from the player to the ground
3601 VectorCopy( origin, end );
3602 VectorMA( origin, FOOTSTEP_DISTANCE, traceDir, end );//was end[2] -= FOOTSTEP_DISTANCE;
3603
3604 cgi_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID );
3605
3606 // no shadow if too high
3607 if ( trace.fraction >= 1.0f )
3608 {
3609 return;
3610 }
3611
3612 //check for foot-steppable surface flag
3613 switch( trace.surfaceFlags & MATERIAL_MASK )
3614 {
3615 case MATERIAL_MUD:
3616 bMark = true;
3617 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3618 soundType = FOOTSTEP_MUDRUN;
3619 } else {
3620 soundType = FOOTSTEP_MUDWALK;
3621 }
3622 effectID = cgs.effects.footstepMud;
3623 break;
3624 case MATERIAL_DIRT:
3625 bMark = true;
3626 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3627 soundType = FOOTSTEP_DIRTRUN;
3628 } else {
3629 soundType = FOOTSTEP_DIRTWALK;
3630 }
3631 effectID = cgs.effects.footstepSand;
3632 break;
3633 case MATERIAL_SAND:
3634 bMark = true;
3635 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3636 soundType = FOOTSTEP_SANDRUN;
3637 } else {
3638 soundType = FOOTSTEP_SANDWALK;
3639 }
3640 effectID = cgs.effects.footstepSand;
3641 break;
3642 case MATERIAL_SNOW:
3643 bMark = true;
3644 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3645 soundType = FOOTSTEP_SNOWRUN;
3646 } else {
3647 soundType = FOOTSTEP_SNOWWALK;
3648 }
3649 effectID = cgs.effects.footstepSnow;
3650 break;
3651 case MATERIAL_SHORTGRASS:
3652 case MATERIAL_LONGGRASS:
3653 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3654 soundType = FOOTSTEP_GRASSRUN;
3655 } else {
3656 soundType = FOOTSTEP_GRASSWALK;
3657 }
3658 break;
3659 case MATERIAL_SOLIDMETAL:
3660 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3661 soundType = FOOTSTEP_METALRUN;
3662 } else {
3663 soundType = FOOTSTEP_METALWALK;
3664 }
3665 break;
3666 case MATERIAL_HOLLOWMETAL:
3667 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3668 soundType = FOOTSTEP_PIPERUN;
3669 } else {
3670 soundType = FOOTSTEP_PIPEWALK;
3671 }
3672 break;
3673 case MATERIAL_GRAVEL:
3674 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3675 soundType = FOOTSTEP_GRAVELRUN;
3676 } else {
3677 soundType = FOOTSTEP_GRAVELWALK;
3678 }
3679 effectID = cgs.effects.footstepGravel;
3680 break;
3681 case MATERIAL_CARPET:
3682 case MATERIAL_FABRIC:
3683 case MATERIAL_CANVAS:
3684 case MATERIAL_RUBBER:
3685 case MATERIAL_PLASTIC:
3686 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3687 soundType = FOOTSTEP_RUGRUN;
3688 } else {
3689 soundType = FOOTSTEP_RUGWALK;
3690 }
3691 break;
3692 case MATERIAL_SOLIDWOOD:
3693 case MATERIAL_HOLLOWWOOD:
3694 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3695 soundType = FOOTSTEP_WOODRUN;
3696 } else {
3697 soundType = FOOTSTEP_WOODWALK;
3698 }
3699 break;
3700
3701 default:
3702 //fall through
3703 case MATERIAL_GLASS:
3704 case MATERIAL_WATER:
3705 case MATERIAL_FLESH:
3706 case MATERIAL_BPGLASS:
3707 case MATERIAL_DRYLEAVES:
3708 case MATERIAL_GREENLEAVES:
3709 case MATERIAL_TILES:
3710 case MATERIAL_PLASTER:
3711 case MATERIAL_SHATTERGLASS:
3712 case MATERIAL_ARMOR:
3713 case MATERIAL_COMPUTER:
3714
3715 case MATERIAL_CONCRETE:
3716 case MATERIAL_ROCK:
3717 case MATERIAL_ICE:
3718 case MATERIAL_MARBLE:
3719 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) {
3720 soundType = FOOTSTEP_STONERUN;
3721 } else {
3722 soundType = FOOTSTEP_STONEWALK;
3723 }
3724 break;
3725 }
3726
3727 if (soundType < FOOTSTEP_TOTAL)
3728 {
3729 cgi_S_StartSound( NULL, cent->currentState.clientNum, CHAN_BODY, cgs.media.footsteps[soundType][Q_irand( 0, 3)] );
3730 }
3731
3732 if ( cg_footsteps.integer < 4 )
3733 {//debugging - 4 always does footstep effect
3734 if ( cg_footsteps.integer < 2 ) //1 for sounds, 2 for effects, 3 for marks
3735 {
3736 return;
3737 }
3738 }
3739
3740 if ( effectID != -1 )
3741 {
3742 theFxScheduler.PlayEffect( effectID, trace.endpos, trace.plane.normal );
3743 }
3744
3745 if ( cg_footsteps.integer < 4 )
3746 {//debugging - 4 always does footprint decal
3747 if (!bMark || cg_footsteps.integer < 3) //1 for sounds, 2 for effects, 3 for marks
3748 {
3749 return;
3750 }
3751 }
3752 qhandle_t footMarkShader;
3753 switch ( footStepType )
3754 {
3755 case FOOTSTEP_HEAVY_R:
3756 footMarkShader = cgs.media.fshrMarkShader;
3757 break;
3758 case FOOTSTEP_HEAVY_L:
3759 footMarkShader = cgs.media.fshlMarkShader;
3760 break;
3761 case FOOTSTEP_R:
3762 footMarkShader = cgs.media.fsrMarkShader;
3763 break;
3764 default:
3765 case FOOTSTEP_L:
3766 footMarkShader = cgs.media.fslMarkShader;
3767 break;
3768 }
3769
3770 // fade the shadow out with height
3771 // alpha = 1.0 - trace.fraction;
3772
3773 // add the mark as a temporary, so it goes directly to the renderer
3774 // without taking a spot in the cg_marks array
3775
3776 vec3_t projNormal;
3777 VectorCopy(trace.plane.normal,projNormal);
3778 if (projNormal[2]>0.5f)
3779 {
3780 // footsteps will not have the correct orientation for all surfaces, so punt and set the projection to Z
3781 projNormal[0]=0.0f;
3782 projNormal[1]=0.0f;
3783 projNormal[2]=1.0f;
3784 }
3785 CG_ImpactMark( footMarkShader, trace.endpos,projNormal,
3786 orientation, 1,1,1, 1.0f, qfalse, radius, qfalse );
3787 }
3788
3789 extern vmCvar_t cg_footsteps;
CG_PlayerFootsteps(centity_t * const cent,footstepType_t footStepType)3790 static void CG_PlayerFootsteps( centity_t *const cent, footstepType_t footStepType )
3791 {
3792 if ( cg_footsteps.integer == 0 )
3793 {
3794 return;
3795 }
3796
3797 //FIXME: make this a feature of NPCs in the NPCs.cfg? Specify a footstep shader, if any?
3798 if ( cent->gent->client->NPC_class != CLASS_ATST
3799 && cent->gent->client->NPC_class != CLASS_CLAW
3800 && cent->gent->client->NPC_class != CLASS_FISH
3801 && cent->gent->client->NPC_class != CLASS_FLIER2
3802 && cent->gent->client->NPC_class != CLASS_GLIDER
3803 && cent->gent->client->NPC_class != CLASS_INTERROGATOR
3804 && cent->gent->client->NPC_class != CLASS_MURJJ
3805 && cent->gent->client->NPC_class != CLASS_PROBE
3806 && cent->gent->client->NPC_class != CLASS_R2D2
3807 && cent->gent->client->NPC_class != CLASS_R5D2
3808 && cent->gent->client->NPC_class != CLASS_REMOTE
3809 && cent->gent->client->NPC_class != CLASS_SEEKER
3810 && cent->gent->client->NPC_class != CLASS_SENTRY
3811 && cent->gent->client->NPC_class != CLASS_SWAMP )
3812 {
3813 mdxaBone_t boltMatrix;
3814 vec3_t tempAngles, sideOrigin, footDownDir;
3815
3816 tempAngles[PITCH] = 0;
3817 tempAngles[YAW] = cent->pe.legs.yawAngle;
3818 tempAngles[ROLL] = 0;
3819
3820 int footBolt = cent->gent->footLBolt;
3821 if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_R)
3822 {
3823 footBolt = cent->gent->footRBolt;
3824 }
3825 //FIXME: get yaw orientation of the foot and use on decal
3826 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, footBolt,
3827 &boltMatrix, tempAngles, cent->lerpOrigin,
3828 cg.time, cgs.model_draw, cent->currentState.modelScale);
3829 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
3830 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, footDownDir );
3831 VectorMA( sideOrigin, -8.0f, footDownDir, sideOrigin );//was [2] += 15; //fudge up a bit for coplanar
3832 _PlayerFootStep( sideOrigin, footDownDir, cent->pe.legs.yawAngle, 6, cent, footStepType );
3833 }
3834 }
3835
_PlayerSplash(const vec3_t origin,const vec3_t velocity,const float radius,const int maxUp)3836 static void _PlayerSplash( const vec3_t origin, const vec3_t velocity, const float radius, const int maxUp )
3837 {
3838 static vec3_t WHITE={1,1,1};
3839 vec3_t start, end;
3840 trace_t trace;
3841 int contents;
3842
3843 VectorCopy( origin, end );
3844 end[2] -= 24;
3845
3846 // if the feet aren't in liquid, don't make a mark
3847 // this won't handle moving water brushes, but they wouldn't draw right anyway...
3848 contents = cgi_CM_PointContents( end, 0 );
3849 if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) )
3850 {
3851 return;
3852 }
3853
3854 VectorCopy( origin, start );
3855 if ( maxUp < 32 )
3856 {//our head may actually be lower than 32 above our origin
3857 start[2] += maxUp;
3858 }
3859 else
3860 {
3861 start[2] += 32;
3862 }
3863
3864 // if the head isn't out of liquid, don't make a mark
3865 contents = cgi_CM_PointContents( start, 0 );
3866 if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
3867 {
3868 return;
3869 }
3870
3871 // trace down to find the surface
3872 cgi_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) );
3873
3874 if ( trace.fraction == 1.0 )
3875 {
3876 return;
3877 }
3878
3879 VectorCopy( trace.endpos, end );
3880
3881 end[0] += Q_flrand(-1.0f, 1.0f) * 3.0f;
3882 end[1] += Q_flrand(-1.0f, 1.0f) * 3.0f;
3883 end[2] += 1.0f; //fudge up
3884
3885 int t = VectorLengthSquared( velocity );
3886
3887 if ( t > 8192 ) // oh, magic number
3888 {
3889 t = 8192;
3890 }
3891
3892 float alpha = ( t / 8192.0f ) * 0.6f + 0.2f;
3893
3894 FX_AddOrientedParticle( -1, end, trace.plane.normal, NULL, NULL,
3895 6.0f, radius + Q_flrand(0.0f, 1.0f) * 48.0f, 0,
3896 alpha, 0.0f, 0.0f,
3897 WHITE, WHITE, 0.0f,
3898 Q_flrand(0.0f, 1.0f) * 360, Q_flrand(-1.0f, 1.0f) * 6.0f, NULL, NULL, 0.0f, 0 ,0, 1200,
3899 cgs.media.wakeMarkShader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR );
3900 }
3901
3902 /*
3903 ===============
3904 CG_PlayerSplash
3905
3906 Draw a mark at the water surface
3907 ===============
3908 */
CG_PlayerSplash(centity_t * cent)3909 static void CG_PlayerSplash( centity_t *cent )
3910 {
3911 if ( !cg_shadows.integer )
3912 {
3913 return;
3914 }
3915
3916 if ( cent->gent && cent->gent->client )
3917 {
3918 gclient_t *cl = cent->gent->client;
3919
3920 if ( cent->gent->disconnectDebounceTime < cg.time ) // can't do these expanding ripples all the time
3921 {
3922 if ( cl->NPC_class == CLASS_ATST )
3923 {
3924 mdxaBone_t boltMatrix;
3925 vec3_t tempAngles, sideOrigin;
3926
3927 tempAngles[PITCH] = 0;
3928 tempAngles[YAW] = cent->pe.legs.yawAngle;
3929 tempAngles[ROLL] = 0;
3930
3931 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footLBolt,
3932 &boltMatrix, tempAngles, cent->lerpOrigin,
3933 cg.time, cgs.model_draw, cent->currentState.modelScale );
3934 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
3935 sideOrigin[2] += 22; //fudge up a bit for coplaner
3936 _PlayerSplash( sideOrigin, cl->ps.velocity, 42, cent->gent->maxs[2] );
3937
3938 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footRBolt,
3939 &boltMatrix, tempAngles, cent->lerpOrigin, cg.time,
3940 cgs.model_draw, cent->currentState.modelScale);
3941 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, sideOrigin );
3942 sideOrigin[2] += 22; //fudge up a bit for coplaner
3943
3944 _PlayerSplash( sideOrigin, cl->ps.velocity, 42, cent->gent->maxs[2] );
3945 }
3946 else
3947 {
3948 // player splash mark
3949 _PlayerSplash( cent->lerpOrigin, cl->ps.velocity, 36, cl->renderInfo.eyePoint[2] - cent->lerpOrigin[2] + 5 );
3950 }
3951
3952 cent->gent->disconnectDebounceTime = cg.time + 125 + Q_flrand(0.0f, 1.0f) * 50.0f;
3953 }
3954 }
3955 }
3956
3957
3958 /*
3959 ===============
3960 CG_LightningBolt
3961 ===============
3962 */
3963 #if 0
3964 static void CG_LightningBolt( centity_t *cent, vec3_t origin )
3965 {
3966 // FIXME: This sound also plays when the weapon first fires which causes little sputtering sounds..not exactly cool
3967 // Must be currently firing
3968 if ( !( cent->currentState.eFlags & EF_FIRING ) )
3969 return;
3970
3971 //Must be a durational weapon
3972 // if ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.eFlags & EF_ALT_FIRING )
3973 // { /*nothing*/ }
3974 // else
3975 {
3976 return;
3977 }
3978
3979 /* trace_t trace;
3980 gentity_t *traceEnt;
3981 vec3_t end, forward, org, angs;
3982 qboolean spark = qfalse, impact = qtrue;//, weak = qfalse;
3983
3984 // for lightning weapons coming from the player, it had better hit the crosshairs or else..
3985 if ( cent->gent->s.number )
3986 {
3987 VectorCopy( origin, org );
3988 }
3989 else
3990 {
3991 VectorCopy( cg.refdef.vieworg, org );
3992 }
3993
3994 // Find the impact point of the beam
3995 VectorCopy( cent->lerpAngles, angs );
3996
3997 AngleVectors( angs, forward, NULL, NULL );
3998
3999 VectorMA( org, weaponData[cent->currentState.weapon].range, forward, end );
4000
4001 CG_Trace( &trace, org, vec3_origin, vec3_origin, end, cent->currentState.number, MASK_SHOT );
4002 traceEnt = &g_entities[ trace.entityNum ];
4003
4004 // Make sparking be a bit less frame-rate dependent..also never add sparking when we hit a surface with a NOIMPACT flag
4005 if ( cent->gent->fx_time < cg.time && !(trace.surfaceFlags & SURF_NOIMPACT ))
4006 {
4007 spark = qtrue;
4008 cent->gent->fx_time = cg.time + Q_flrand(0.0f, 1.0f) * 100 + 100;
4009 }
4010
4011 // Don't draw certain kinds of impacts when it hits a player and such..or when we hit a surface with a NOIMPACT flag
4012 if ( (traceEnt->takedamage && traceEnt->client) || (trace.surfaceFlags & SURF_NOIMPACT) )
4013 {
4014 impact = qfalse;
4015 }
4016
4017 // Add in the effect
4018 switch ( cent->currentState.weapon )
4019 {
4020 case WP_DEMP2:
4021 // vec3_t org;
4022
4023 extern void FX_DEMP2_AltBeam( vec3_t start, vec3_t end, vec3_t normal, //qboolean spark,
4024 vec3_t targ1, vec3_t targ2 );
4025
4026 // Move the beam back a bit to help cover up the poly edges on the fire beam
4027 // VectorMA( origin, -4, forward, org );
4028 // FIXME: Looks and works like ASS, so don't let people see it until it improves
4029 FX_DEMP2_AltBeam( origin, trace.endpos, trace.plane.normal, cent->gent->pos1, cent->gent->pos2 );
4030 break;
4031
4032 }
4033 */
4034 }
4035 #endif
4036
4037 //-------------------------------------------
4038 #define REFRACT_EFFECT_DURATION 500
4039 void CG_ForcePushBlur( const vec3_t org, qboolean darkSide = qfalse );
CG_ForcePushRefraction(vec3_t org,centity_t * cent)4040 static void CG_ForcePushRefraction( vec3_t org, centity_t *cent )
4041 {
4042 refEntity_t ent;
4043 vec3_t ang;
4044 float scale;
4045 float vLen;
4046 float alpha;
4047 int tDif;
4048
4049 if (!cg_renderToTextureFX.integer)
4050 {
4051 CG_ForcePushBlur(org);
4052 return;
4053 }
4054
4055 if (!cent->gent ||
4056 !cent->gent->client)
4057 { //can only do this for player/npc's
4058 return;
4059 }
4060
4061 if (!cent->gent->client->pushEffectFadeTime)
4062 { //the duration for the expansion and fade
4063 cent->gent->client->pushEffectFadeTime = cg.time + REFRACT_EFFECT_DURATION;
4064 }
4065
4066 //closer tDif is to 0, the closer we are to
4067 //being "done"
4068 tDif = (cent->gent->client->pushEffectFadeTime - cg.time);
4069 if ((REFRACT_EFFECT_DURATION-tDif) < 200)
4070 { //stop following the hand after a little and stay in a fixed spot
4071 //save the initial spot of the effect
4072 VectorCopy(org, cent->gent->client->pushEffectOrigin);
4073 }
4074
4075 //scale from 1.0f to 0.1f then hold at 0.1 for the rest of the duration
4076 if (cent->gent->client->ps.forcePowersActive & ( 1 << FP_PULL ) )
4077 {
4078 scale = (float)(REFRACT_EFFECT_DURATION-tDif)*0.003f;
4079 }
4080 else
4081 {
4082 scale = (float)(tDif)*0.003f;
4083 }
4084 if (scale > 1.0f)
4085 {
4086 scale = 1.0f;
4087 }
4088 else if (scale < 0.2f)
4089 {
4090 scale = 0.2f;
4091 }
4092
4093 //start alpha at 244, fade to 10
4094 alpha = (float)tDif*0.488f;
4095
4096 if (alpha > 244.0f)
4097 {
4098 alpha = 244.0f;
4099 }
4100 else if (alpha < 10.0f)
4101 {
4102 alpha = 10.0f;
4103 }
4104
4105 memset( &ent, 0, sizeof( ent ) );
4106 ent.shaderTime = (cent->gent->client->pushEffectFadeTime-REFRACT_EFFECT_DURATION) / 1000.0f;
4107
4108 VectorCopy( cent->gent->client->pushEffectOrigin, ent.origin );
4109
4110 VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]);
4111 vLen = VectorLength(ent.axis[0]);
4112 if (vLen <= 0.1f)
4113 { // Entity is right on vieworg. quit.
4114 return;
4115 }
4116
4117 vectoangles(ent.axis[0], ang);
4118 ang[ROLL] += 180.0f;
4119
4120 AnglesToAxis(ang, ent.axis);
4121
4122 //radius must be a power of 2, and is the actual captured texture size
4123 if (vLen < 128)
4124 {
4125 ent.radius = 256;
4126 }
4127 else if (vLen < 256)
4128 {
4129 ent.radius = 128;
4130 }
4131 else if (vLen < 512)
4132 {
4133 ent.radius = 64;
4134 }
4135 else
4136 {
4137 ent.radius = 32;
4138 }
4139
4140 VectorScale(ent.axis[0], scale, ent.axis[0]);
4141 VectorScale(ent.axis[1], scale, ent.axis[1]);
4142 VectorScale(ent.axis[2], scale, ent.axis[2]);
4143
4144 ent.hModel = cgs.media.halfShieldModel;
4145 ent.customShader = cgs.media.refractShader;
4146 ent.nonNormalizedAxes = qtrue;
4147
4148 //make it partially transparent so it blends with the background
4149 ent.renderfx = (RF_DISTORTION|RF_ALPHA_FADE);
4150 ent.shaderRGBA[0] = 255.0f;
4151 ent.shaderRGBA[1] = 255.0f;
4152 ent.shaderRGBA[2] = 255.0f;
4153 ent.shaderRGBA[3] = alpha;
4154
4155
4156 cgi_R_AddRefEntityToScene( &ent );
4157 }
4158
CG_ForcePushBlur(const vec3_t org,qboolean darkSide)4159 void CG_ForcePushBlur( const vec3_t org, qboolean darkSide )
4160 {
4161 localEntity_t *ex;
4162
4163 ex = CG_AllocLocalEntity();
4164 ex->leType = LE_PUFF;
4165 ex->refEntity.reType = RT_SPRITE;
4166 ex->radius = 2.0f;
4167 ex->startTime = cg.time;
4168 ex->endTime = ex->startTime + 120;
4169 VectorCopy( org, ex->pos.trBase );
4170 ex->pos.trTime = cg.time;
4171 ex->pos.trType = TR_LINEAR;
4172 VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta );
4173
4174 if ( darkSide )
4175 {//make it red
4176 ex->color[0] = 60;
4177 ex->color[1] = 8;
4178 ex->color[2] = 8;
4179 }
4180 else
4181 {//blue
4182 ex->color[0] = 24;
4183 ex->color[1] = 32;
4184 ex->color[2] = 40;
4185 }
4186 ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/effects/forcePush" );
4187
4188 ex = CG_AllocLocalEntity();
4189 ex->leType = LE_PUFF;
4190 ex->refEntity.reType = RT_SPRITE;
4191 ex->refEntity.rotation = 180.0f;
4192 ex->radius = 2.0f;
4193 ex->startTime = cg.time;
4194 ex->endTime = ex->startTime + 120;
4195 VectorCopy( org, ex->pos.trBase );
4196 ex->pos.trTime = cg.time;
4197 ex->pos.trType = TR_LINEAR;
4198 VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta );
4199
4200 if ( darkSide )
4201 {//make it red
4202 ex->color[0] = 60;
4203 ex->color[1] = 8;
4204 ex->color[2] = 8;
4205 }
4206 else
4207 {//blue
4208 ex->color[0] = 24;
4209 ex->color[1] = 32;
4210 ex->color[2] = 40;
4211 }
4212 ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/effects/forcePush" );
4213 }
4214
CG_ForcePushBodyBlur(centity_t * cent,const vec3_t origin,vec3_t tempAngles)4215 static void CG_ForcePushBodyBlur( centity_t *cent, const vec3_t origin, vec3_t tempAngles )
4216 {
4217 vec3_t fxOrg;
4218 mdxaBone_t boltMatrix;
4219
4220 // Head blur
4221 CG_ForcePushBlur( cent->gent->client->renderInfo.eyePoint );
4222
4223 // Do a torso based blur
4224 if (cent->gent->torsoBolt>=0)
4225 {
4226 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->torsoBolt,
4227 &boltMatrix, tempAngles, origin, cg.time,
4228 cgs.model_draw, cent->currentState.modelScale);
4229 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4230 CG_ForcePushBlur( fxOrg );
4231 }
4232
4233 if (cent->gent->handRBolt>=0)
4234 {
4235 // Do a right-hand based blur
4236 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handRBolt,
4237 &boltMatrix, tempAngles, origin, cg.time,
4238 cgs.model_draw, cent->currentState.modelScale);
4239 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4240 CG_ForcePushBlur( fxOrg );
4241 }
4242
4243 if (cent->gent->handLBolt>=0)
4244 {
4245 // Do a left-hand based blur
4246 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handLBolt,
4247 &boltMatrix, tempAngles, origin, cg.time,
4248 cgs.model_draw, cent->currentState.modelScale);
4249 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4250 CG_ForcePushBlur( fxOrg );
4251 }
4252
4253 // Do the knees
4254 if (cent->gent->kneeLBolt>=0)
4255 {
4256 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->kneeLBolt,
4257 &boltMatrix, tempAngles, origin, cg.time,
4258 cgs.model_draw, cent->currentState.modelScale);
4259 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4260 CG_ForcePushBlur( fxOrg );
4261 }
4262
4263 if (cent->gent->kneeRBolt>=0)
4264 {
4265 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->kneeRBolt,
4266 &boltMatrix, tempAngles, origin, cg.time,
4267 cgs.model_draw, cent->currentState.modelScale);
4268 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4269 CG_ForcePushBlur( fxOrg );
4270 }
4271
4272 if (cent->gent->elbowLBolt>=0)
4273 {
4274 // Do the elbows
4275 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->elbowLBolt,
4276 &boltMatrix, tempAngles, origin, cg.time,
4277 cgs.model_draw, cent->currentState.modelScale);
4278 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4279 CG_ForcePushBlur( fxOrg );
4280 }
4281 if (cent->gent->elbowRBolt>=0)
4282 {
4283 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->elbowRBolt,
4284 &boltMatrix, tempAngles, origin, cg.time,
4285 cgs.model_draw, cent->currentState.modelScale);
4286 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4287 CG_ForcePushBlur( fxOrg );
4288 }
4289 }
4290
CG_ForceElectrocution(centity_t * cent,const vec3_t origin,vec3_t tempAngles,qhandle_t shader,qboolean alwaysDo=qfalse)4291 static void CG_ForceElectrocution( centity_t *cent, const vec3_t origin, vec3_t tempAngles, qhandle_t shader, qboolean alwaysDo = qfalse )
4292 {
4293 // Undoing for now, at least this code should compile if I ( or anyone else ) decides to work on this effect
4294 qboolean found = qfalse;
4295 vec3_t fxOrg, fxOrg2, dir;
4296 vec3_t rgb = {1.0f,1.0f,1.0f};
4297 mdxaBone_t boltMatrix;
4298
4299 int bolt=-1;
4300 int iter=0;
4301 // Pick a random start point
4302 while (bolt<0)
4303 {
4304 int test;
4305 if (iter>5)
4306 {
4307 test=iter-5;
4308 }
4309 else
4310 {
4311 test=Q_irand(0,6);
4312 }
4313 switch(test)
4314 {
4315 case 0:
4316 // Right Elbow
4317 bolt=cent->gent->elbowRBolt;
4318 break;
4319 case 1:
4320 // Left Hand
4321 bolt=cent->gent->handLBolt;
4322 break;
4323 case 2:
4324 // Right hand
4325 bolt=cent->gent->handRBolt;
4326 break;
4327 case 3:
4328 // Left Foot
4329 bolt=cent->gent->footLBolt;
4330 break;
4331 case 4:
4332 // Right foot
4333 bolt=cent->gent->footRBolt;
4334 break;
4335 case 5:
4336 // Torso
4337 bolt=cent->gent->torsoBolt;
4338 break;
4339 case 6:
4340 default:
4341 // Left Elbow
4342 bolt=cent->gent->elbowLBolt;
4343 break;
4344 }
4345 if (++iter==20)
4346 break;
4347 }
4348 if (bolt>=0)
4349 {
4350 found = gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt,
4351 &boltMatrix, tempAngles, origin, cg.time,
4352 cgs.model_draw, cent->currentState.modelScale);
4353 }
4354 // Make sure that it's safe to even try and get these values out of the Matrix, otherwise the values could be garbage
4355 if ( found )
4356 {
4357 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4358 if ( Q_flrand(0.0f, 1.0f) > 0.5f )
4359 {
4360 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, dir );
4361 }
4362 else
4363 {
4364 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
4365 }
4366
4367 // Add some fudge, makes us not normalized, but that isn't really important
4368 dir[0] += Q_flrand(-1.0f, 1.0f) * 0.4f;
4369 dir[1] += Q_flrand(-1.0f, 1.0f) * 0.4f;
4370 dir[2] += Q_flrand(-1.0f, 1.0f) * 0.4f;
4371 }
4372 else
4373 {
4374 // Just use the lerp Origin and a random direction
4375 VectorCopy( cent->lerpOrigin, fxOrg );
4376 VectorSet( dir, Q_flrand(-1.0f, 1.0f), Q_flrand(-1.0f, 1.0f), Q_flrand(-1.0f, 1.0f) ); // Not normalized, but who cares.
4377 if ( cent->gent && cent->gent->client )
4378 {
4379 switch ( cent->gent->client->NPC_class )
4380 {
4381 case CLASS_PROBE:
4382 fxOrg[2] += 50;
4383 break;
4384 case CLASS_MARK1:
4385 fxOrg[2] += 50;
4386 break;
4387 case CLASS_ATST:
4388 fxOrg[2] += 120;
4389 break;
4390 default:
4391 break;
4392 }
4393 }
4394 }
4395
4396 VectorMA( fxOrg, Q_flrand(0.0f, 1.0f) * 40 + 40, dir, fxOrg2 );
4397
4398 trace_t tr;
4399
4400 CG_Trace( &tr, fxOrg, NULL, NULL, fxOrg2, -1, CONTENTS_SOLID );
4401
4402 if ( tr.fraction < 1.0f || Q_flrand(0.0f, 1.0f) > 0.94f || alwaysDo )
4403 {
4404 FX_AddElectricity( -1, fxOrg, tr.endpos,
4405 1.5f, 4.0f, 0.0f,
4406 1.0f, 0.5f, 0.0f,
4407 rgb, rgb, 0.0f,
4408 5.5f, Q_flrand(0.0f, 1.0f) * 50 + 100, shader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR | FX_BRANCH | FX_GROW | FX_TAPER, -1, -1 );
4409 }
4410 }
4411
CG_BoltedEffects(centity_t * cent,const vec3_t origin,vec3_t tempAngles)4412 static void CG_BoltedEffects( centity_t *cent, const vec3_t origin, vec3_t tempAngles )
4413 {
4414 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_VEHICLE )
4415 {
4416 Vehicle_t *pVeh = cent->gent->m_pVehicle;
4417 gentity_t *parent = cent->gent;
4418 if (pVeh->m_ulFlags&VEH_ARMORLOW
4419 && (pVeh->m_iLastFXTime<=cg.time)
4420 && Q_irand(0,1)==0 )
4421 {
4422 pVeh->m_iLastFXTime = cg.time + 50;//Q_irand(50, 100);
4423 CG_PlayEffectIDBolted(pVeh->m_pVehicleInfo->iArmorLowFX, parent->playerModel, parent->crotchBolt, parent->s.number, parent->currentOrigin);
4424 }
4425 }
4426 }
4427
4428 /*
4429 ===============
4430 CG_PlayerCanSeeCent
4431
4432 tests force sight level
4433 ===============
4434 */
CG_PlayerCanSeeCent(centity_t * cent)4435 qboolean CG_PlayerCanSeeCent( centity_t *cent )
4436 {//return true if this cent is in view
4437 //NOTE: this is similar to the func SV_PlayerCanSeeEnt in sv_snapshot
4438 if ( (cent->currentState.eFlags&EF_FORCE_VISIBLE) )
4439 {//can always be seen
4440 return qtrue;
4441 }
4442
4443 if ( g_entities[0].client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2
4444 && cent->currentState.eType != ET_PLAYER )
4445 {//TEST: level 1 only sees force hints and enemies
4446 return qfalse;
4447 }
4448
4449 float dot = 0.25f;//1.0f;
4450 float range = 512.0f;
4451
4452 switch ( g_entities[0].client->ps.forcePowerLevel[FP_SEE] )
4453 {
4454 case FORCE_LEVEL_1:
4455 //dot = 0.95f;
4456 range = 1024.0f;
4457 break;
4458 case FORCE_LEVEL_2:
4459 //dot = 0.7f;
4460 range = 2048.0f;
4461 break;
4462 case FORCE_LEVEL_3:
4463 case FORCE_LEVEL_4:
4464 case FORCE_LEVEL_5:
4465 //dot = 0.4f;
4466 range = 4096.0f;
4467 break;
4468 }
4469
4470 vec3_t centDir, lookDir;
4471 VectorSubtract( cent->lerpOrigin, cg.refdef.vieworg, centDir );
4472 float centDist = VectorNormalize( centDir );
4473
4474 if ( centDist < 128.0f )
4475 {//can always see them if they're really close
4476 return qtrue;
4477 }
4478
4479 if ( centDist > range )
4480 {//too far away to see them
4481 return qfalse;
4482 }
4483
4484 dot += (0.99f-dot)*centDist/range;//the farther away they are, the more in front they have to be
4485
4486 AngleVectors( cg.refdefViewAngles, lookDir, NULL, NULL );
4487
4488 if ( DotProduct( centDir, lookDir ) < dot )
4489 {//not in force sight cone
4490 return qfalse;
4491 }
4492
4493 return qtrue;
4494 }
4495
4496 /*
4497 ===============
4498 CG_AddForceSightShell
4499
4500 Adds the special effect
4501 ===============
4502 */
4503 extern void CG_AddHealthBarEnt( int entNum );
CG_AddForceSightShell(refEntity_t * ent,centity_t * cent)4504 void CG_AddForceSightShell( refEntity_t *ent, centity_t *cent )
4505 {
4506 ent->customShader = cgs.media.forceShell;
4507 ent->renderfx &= ~RF_RGB_TINT;
4508 // See through walls.
4509 ent->renderfx |= (RF_MORELIGHT|RF_NODEPTH);
4510
4511 if ( (cent->currentState.eFlags&EF_FORCE_VISIBLE)
4512 || (cent->currentState.eType == ET_PLAYER && cent->gent && cent->gent->message) )
4513 {
4514 ent->shaderRGBA[0] = 0;
4515 ent->shaderRGBA[1] = 0;
4516 ent->shaderRGBA[2] = 255;
4517 ent->shaderRGBA[3] = 254;
4518
4519 cgi_R_AddRefEntityToScene( ent );
4520 return;
4521 }
4522
4523 ent->shaderRGBA[0] = 255;
4524 ent->shaderRGBA[1] = 255;
4525 ent->shaderRGBA[2] = 0;
4526
4527 //if ( g_entities[0].client->ps.forcePowerLevel[FP_SEE] > FORCE_LEVEL_2 )
4528 //{TEST: level 3 identifies friend or foe with color
4529 team_t team = TEAM_NEUTRAL;
4530 if ( cent->gent && cent->gent->client )
4531 {
4532 team = cent->gent->client->playerTeam;
4533 }
4534 else if ( cent->gent && cent->gent->owner )
4535 {
4536 if ( cent->gent->owner->client )
4537 {
4538 team = cent->gent->owner->client->playerTeam;
4539 }
4540 else
4541 {
4542 team = cent->gent->owner->noDamageTeam;
4543 }
4544 }
4545 switch ( team )
4546 {
4547 case TEAM_ENEMY:
4548 ent->shaderRGBA[0] = 255;
4549 ent->shaderRGBA[1] = 0;
4550 ent->shaderRGBA[2] = 0;
4551 break;
4552 case TEAM_PLAYER:
4553 ent->shaderRGBA[0] = 0;
4554 ent->shaderRGBA[1] = 255;
4555 ent->shaderRGBA[2] = 0;
4556 break;
4557 case TEAM_FREE:
4558 if ( cent->gent && cent->gent->client )
4559 {
4560 if ( cent->gent->client->NPC_class == CLASS_TUSKEN
4561 || cent->gent->client->NPC_class == CLASS_RANCOR
4562 || cent->gent->client->NPC_class == CLASS_WAMPA
4563 || cent->gent->client->NPC_class == CLASS_SAND_CREATURE )
4564 {
4565 ent->shaderRGBA[0] = 255;
4566 ent->shaderRGBA[1] = 0;
4567 ent->shaderRGBA[2] = 0;
4568 }
4569 }
4570 break;
4571 default:
4572 break;
4573 }
4574
4575 if ( g_entities[0].client->ps.forcePowerLevel[FP_SEE] > FORCE_LEVEL_2 )
4576 {//TEST: level 3 also displays health
4577 if ( cent->gent && cent->gent->health > 0 && cent->gent->max_health > 0 )
4578 {//draw a health bar over them
4579 CG_AddHealthBarEnt( cent->currentState.clientNum );
4580 }
4581 }
4582
4583 /*
4584 if ( g_entities[0].client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 )
4585 { //only level 2+ can see players through walls
4586 ent->renderfx &= ~RF_NODEPTH;
4587 }
4588 */
4589
4590 //FIXME: make it darker or more translucent the further away it is?
4591
4592 cgi_R_AddRefEntityToScene( ent );
4593 }
4594
4595 /*
4596 ===============
4597 CG_AddRefEntityWithPowerups
4598
4599 Adds a piece with modifications or duplications for powerups
4600 ===============
4601 */
CG_AddRefEntityWithPowerups(refEntity_t * ent,int powerups,centity_t * cent)4602 void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, centity_t *cent )
4603 {
4604 if ( !cent )
4605 {
4606 cgi_R_AddRefEntityToScene( ent );
4607 return;
4608 }
4609 gentity_t *gent = cent->gent;
4610 if ( !gent )
4611 {
4612 cgi_R_AddRefEntityToScene( ent );
4613 return;
4614 }
4615 if ( gent->client->ps.powerups[PW_DISRUPTION] < cg.time )
4616 {//disruptor
4617 if (( powerups & ( 1 << PW_DISRUPTION )))
4618 {
4619 //stop drawing him after this effect
4620 gent->client->ps.eFlags |= EF_NODRAW;
4621 return;
4622 }
4623 }
4624
4625 // if ( gent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 )
4626 // {
4627 // centity_t *cent = &cg_entities[gent->s.number];
4628 // cgi_S_AddLoopingSound( 0, cent->lerpOrigin, vec3_origin, cgs.media.overchargeLoopSound );
4629 // }
4630
4631 //get the dude's color choice in
4632 ent->shaderRGBA[0] = gent->client->renderInfo.customRGBA[0];
4633 ent->shaderRGBA[1] = gent->client->renderInfo.customRGBA[1];
4634 ent->shaderRGBA[2] = gent->client->renderInfo.customRGBA[2];
4635 ent->shaderRGBA[3] = gent->client->renderInfo.customRGBA[3];
4636
4637 // If certain states are active, we don't want to add in the regular body
4638 if ( !gent->client->ps.powerups[PW_CLOAKED] &&
4639 !gent->client->ps.powerups[PW_UNCLOAKING] &&
4640 !gent->client->ps.powerups[PW_DISRUPTION] )
4641 {
4642 cgi_R_AddRefEntityToScene( ent );
4643 }
4644
4645 // Disruptor Gun Alt-fire
4646 if ( gent->client->ps.powerups[PW_DISRUPTION] )
4647 {
4648 // I guess when something dies, it looks like pos1 gets set to the impact point on death, we can do fun stuff with this
4649 vec3_t tempAng;
4650 VectorSubtract( gent->pos1, ent->origin, ent->oldorigin );
4651 //er, adjust this to get the proper position in model space... account for yaw
4652 float tempLength = VectorNormalize( ent->oldorigin );
4653 vectoangles( ent->oldorigin, tempAng );
4654 tempAng[YAW] -= gent->client->ps.viewangles[YAW];
4655 AngleVectors( tempAng, ent->oldorigin, NULL, NULL );
4656 VectorScale( ent->oldorigin, tempLength, ent->oldorigin );
4657
4658 ent->endTime = gent->fx_time;
4659 ent->renderfx |= (RF_DISINTEGRATE2);
4660
4661 ent->customShader = cgi_R_RegisterShader( "gfx/effects/burn" );
4662 cgi_R_AddRefEntityToScene( ent );
4663
4664 ent->renderfx &= ~(RF_DISINTEGRATE2);
4665 ent->renderfx |= (RF_DISINTEGRATE1);
4666 ent->customShader = 0;
4667 cgi_R_AddRefEntityToScene( ent );
4668
4669 if ( cg.time - ent->endTime < 1000 && (cg_timescale.value * cg_timescale.value * Q_flrand(0.0f, 1.0f)) > 0.05f )
4670 {
4671 vec3_t fxOrg;
4672 mdxaBone_t boltMatrix;
4673
4674 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, gent->playerModel, gent->torsoBolt,
4675 &boltMatrix, gent->currentAngles, ent->origin, cg.time,
4676 cgs.model_draw, gent->s.modelScale);
4677 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg );
4678
4679 VectorMA( fxOrg, -18, cg.refdef.viewaxis[0], fxOrg );
4680 fxOrg[2] += Q_flrand(-1.0f, 1.0f) * 20;
4681 theFxScheduler.PlayEffect( "disruptor/death_smoke", fxOrg );
4682
4683 if ( Q_flrand(0.0f, 1.0f) > 0.5f )
4684 {
4685 theFxScheduler.PlayEffect( "disruptor/death_smoke", fxOrg );
4686 }
4687 }
4688 }
4689
4690 // Cloaking & Uncloaking Technology
4691 //----------------------------------------
4692
4693 if (( powerups & ( 1 << PW_UNCLOAKING )))
4694 {//in the middle of cloaking
4695 if ((cg.snap->ps.forcePowersActive & (1 << FP_SEE))
4696 && cg.snap->ps.clientNum != cent->currentState.number
4697 && CG_PlayerCanSeeCent( cent ))
4698 {//just draw him
4699 cgi_R_AddRefEntityToScene( ent );
4700 }
4701 else
4702 {
4703 float perc = (float)(gent->client->ps.powerups[PW_UNCLOAKING] - cg.time) / 2000.0f;
4704 if (( powerups & ( 1 << PW_CLOAKED )))
4705 {//actually cloaking, so reverse it
4706 perc = 1.0f - perc;
4707 }
4708
4709 if ( perc >= 0.0f && perc <= 1.0f )
4710 {
4711 ent->renderfx &= ~RF_ALPHA_FADE;
4712 ent->renderfx |= RF_RGB_TINT;
4713 ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255.0f * perc;
4714 ent->shaderRGBA[3] = 0;
4715 ent->customShader = cgs.media.cloakedShader;
4716 cgi_R_AddRefEntityToScene( ent );
4717
4718 ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255;
4719 ent->shaderRGBA[3] = 255 * (1.0f - perc); // let model alpha in
4720 ent->customShader = 0; // use regular skin
4721 ent->renderfx &= ~RF_RGB_TINT;
4722 ent->renderfx |= RF_ALPHA_FADE;
4723 cgi_R_AddRefEntityToScene( ent );
4724 }
4725 }
4726 }
4727 else if (( powerups & ( 1 << PW_CLOAKED )))
4728 {//fully cloaked
4729 if ((cg.snap->ps.forcePowersActive & (1 << FP_SEE))
4730 && cg.snap->ps.clientNum != cent->currentState.number
4731 && CG_PlayerCanSeeCent( cent ))
4732 {//just draw him
4733 cgi_R_AddRefEntityToScene( ent );
4734 }
4735 else
4736 {
4737 if (cg_renderToTextureFX.integer && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4)
4738 {
4739 cgi_R_SetRefractProp(1.0f, 0.0f, qfalse, qfalse); //don't need to do this every frame.. but..
4740 ent->customShader = 2; //crazy "refractive" shader
4741 cgi_R_AddRefEntityToScene( ent );
4742 ent->customShader = 0;
4743 }
4744 else
4745 { //stencil buffer's in use, sorry
4746 ent->renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE);
4747 ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = ent->shaderRGBA[3] = 255;
4748 ent->customShader = cgs.media.cloakedShader;
4749 cgi_R_AddRefEntityToScene( ent );
4750 }
4751 }
4752 }
4753
4754 // Electricity
4755 //------------------------------------------------
4756 if ( (powerups & ( 1 << PW_SHOCKED )) )
4757 {
4758 int dif = gent->client->ps.powerups[PW_SHOCKED] - cg.time;
4759
4760 if ( dif > 0 && Q_flrand(0.0f, 1.0f) > 0.4f )
4761 {
4762 // fade out over the last 500 ms
4763 int brightness = 255;
4764
4765 if ( dif < 500 )
4766 {
4767 brightness = floor((dif - 500.0f) / 500.0f * 255.0f );
4768 }
4769
4770 ent->renderfx |= RF_RGB_TINT;
4771 ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = brightness;
4772 ent->shaderRGBA[3] = 255;
4773
4774 if ( rand() & 1 )
4775 {
4776 ent->customShader = cgs.media.electricBodyShader;
4777 }
4778 else
4779 {
4780 ent->customShader = cgs.media.electricBody2Shader;
4781 }
4782
4783 cgi_R_AddRefEntityToScene( ent );
4784
4785 if ( Q_flrand(0.0f, 1.0f) > 0.9f )
4786 cgi_S_StartSound ( ent->origin, gent->s.number, CHAN_AUTO, cgi_S_RegisterSound( "sound/effects/energy_crackle.wav" ) );
4787 }
4788 }
4789
4790 // FORCE speed does blur trails
4791 //------------------------------------------------------
4792 if ( cg_speedTrail.integer
4793 && (gent->client->ps.forcePowersActive & (1 << FP_SPEED) //in force speed
4794 || cent->gent->client->ps.legsAnim == BOTH_FORCELONGLEAP_START//or force long jump - FIXME: only 1st half of that anim?
4795 || cent->gent->client->ps.legsAnim == BOTH_FORCELONGLEAP_ATTACK )//or force long jump attack
4796 && (gent->s.number || cg.renderingThirdPerson) ) // looks dumb doing this with first peron mode on
4797 {
4798 //FIXME: debounce this
4799 localEntity_t *ex;
4800
4801 ex = CG_AllocLocalEntity();
4802 ex->leType = LE_FADE_MODEL;
4803 memcpy( &ex->refEntity, ent, sizeof( refEntity_t ));
4804
4805 ex->refEntity.renderfx |= (RF_ALPHA_FADE | RF_NOSHADOW | RF_G2MINLOD ) ;
4806 //ex->refEntity.renderfx |= RF_ALPHA_FADE;
4807 ex->startTime = cg.time;
4808 ex->endTime = ex->startTime + 75;
4809 VectorCopy( ex->refEntity.origin, ex->pos.trBase );
4810 VectorClear( ex->pos.trDelta );
4811
4812 if ( gent->client->renderInfo.customRGBA[0]
4813 || gent->client->renderInfo.customRGBA[1]
4814 || gent->client->renderInfo.customRGBA[2] )
4815 {
4816 ex->color[0] = gent->client->renderInfo.customRGBA[0];
4817 ex->color[1] = gent->client->renderInfo.customRGBA[1];
4818 ex->color[2] = gent->client->renderInfo.customRGBA[2];
4819 }
4820 else
4821 {
4822 ex->color[0] = ex->color[1] = ex->color[2] = 255.0f;
4823 }
4824 ex->color[3] = 50.0f;
4825 }
4826
4827 // Personal Shields
4828 //------------------------
4829 if ( powerups & ( 1 << PW_BATTLESUIT ))
4830 {
4831 float diff = gent->client->ps.powerups[PW_BATTLESUIT] - cg.time;
4832 float t;
4833
4834 if ( diff > 0 )
4835 {
4836 t = 1.0f - ( diff / (ARMOR_EFFECT_TIME * 2.0f));
4837 // Only display when we have damage
4838 if ( t < 0.0f || t > 1.0f )
4839 {
4840 }
4841 else
4842 {
4843 ent->shaderRGBA[0] = ent->shaderRGBA[1] = ent->shaderRGBA[2] = 255.0f * t;
4844 ent->shaderRGBA[3] = 255;
4845 ent->renderfx &= ~RF_ALPHA_FADE;
4846 ent->renderfx |= RF_RGB_TINT;
4847 ent->customShader = cgs.media.personalShieldShader;
4848
4849 cgi_R_AddRefEntityToScene( ent );
4850 }
4851 }
4852 }
4853
4854 // Galak Mech shield bubble
4855 //------------------------------------------------------
4856 if ( powerups & ( 1 << PW_GALAK_SHIELD ))
4857 {
4858 /* refEntity_t tent;
4859
4860 memset( &tent, 0, sizeof( refEntity_t ));
4861
4862 tent.reType = RT_LATHE;
4863
4864 // Setting up the 2d control points, these get swept around to make a 3D lathed model
4865 VectorSet2( tent.axis[0], 0.5, 0 ); // start point of curve
4866 VectorSet2( tent.axis[1], 50, 85 ); // control point 1
4867 VectorSet2( tent.axis[2], 135, -100 ); // control point 2
4868 VectorSet2( tent.oldorigin, 0, -90 ); // end point of curve
4869
4870 if ( gent->client->poisonTime && gent->client->poisonTime + 1000 > cg.time )
4871 {
4872 VectorCopy( gent->pos4, tent.lightingOrigin );
4873 tent.frame = gent->client->poisonTime;
4874 }
4875
4876 mdxaBone_t boltMatrix;
4877 vec3_t angles = {0,gent->client->ps.legsYaw,0};
4878
4879 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, gent->playerModel, gent->genericBolt1, &boltMatrix, angles, cent->lerpOrigin, cg.time, cgs.model_draw, cent->currentState.modelScale );
4880 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tent.origin );// pass in the emitter origin here
4881
4882 tent.endTime = gent->fx_time + 1000; // if you want the shell to build around the guy, pass in a time that is 1000ms after the start of the turn-on-effect
4883 tent.customShader = cgi_R_RegisterShader( "gfx/effects/irid_shield" );
4884
4885 cgi_R_AddRefEntityToScene( &tent );*/
4886 }
4887
4888 // Invincibility -- effect needs work
4889 //------------------------------------------------------
4890 /*
4891 if ( powerups & ( 1 << PW_INVINCIBLE ))
4892 {
4893 theFxScheduler.PlayEffect( cgs.effects.forceInvincibility, cent->lerpOrigin );
4894 }
4895 */
4896
4897 // Healing -- could use some work....maybe also make it NOT be framerate dependant
4898 //------------------------------------------------------
4899 /* if ( powerups & ( 1 << PW_HEALING ))
4900 {
4901 vec3_t axis[3];
4902
4903 AngleVectors( cent->gent->client->renderInfo.eyeAngles, axis[0], axis[1], axis[2] );
4904
4905 theFxScheduler.PlayEffect( cgs.effects.forceHeal, cent->gent->client->renderInfo.eyePoint, axis );
4906 }
4907 */
4908 // Push Blur
4909 if ( gent->forcePushTime > cg.time && gi.G2API_HaveWeGhoul2Models( cent->gent->ghoul2 ) )
4910 {
4911 CG_ForcePushBlur( ent->origin );
4912 }
4913
4914 //new Jedi Academy force powers
4915 //Rage effect
4916 if ((cent->gent->client->ps.forcePowersActive & (1 << FP_RAGE)) &&
4917 (cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum))
4918 {
4919 //ent->renderfx &= ~RF_FORCE_ENT_ALPHA;
4920 //ent->renderfx &= ~RF_MINLIGHT;
4921
4922 ent->renderfx |= RF_RGB_TINT;
4923 ent->shaderRGBA[0] = 255;
4924 ent->shaderRGBA[1] = ent->shaderRGBA[2] = 0;
4925 ent->shaderRGBA[3] = 255;
4926
4927 if ( rand() & 1 )
4928 {
4929 ent->customShader = cgs.media.electricBodyShader;
4930 }
4931 else
4932 {
4933 ent->customShader = cgs.media.electricBody2Shader;
4934 }
4935
4936 cgi_R_AddRefEntityToScene( ent);
4937 }
4938
4939 //FIXME: Tavion possessed effect? White?
4940 //For now, these two are using the old shield shader. This is just so that you
4941 //can tell it apart from the JM/duel shaders, but it's still very obvious.
4942 if ( (cent->gent->client->ps.forcePowersActive & (1 << FP_PROTECT))
4943 && (cent->gent->client->ps.forcePowersActive & (1 << FP_ABSORB)) )
4944 {//using both at once, save ourselves some rendering
4945 //protect+absorb is represented by cyan..
4946 ent->shaderRGBA[0] = 0;
4947 ent->shaderRGBA[1] = 255;
4948 ent->shaderRGBA[2] = 255;
4949 ent->shaderRGBA[3] = 254;
4950
4951 ent->renderfx &= ~RF_RGB_TINT;
4952 //ent->renderfx &= ~RF_FORCE_ENT_ALPHA;
4953 if ( cent->gent->client->ps.forcePowerLevel[FP_PROTECT] > FORCE_LEVEL_1
4954 || cent->gent->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_1 )
4955 {
4956 ent->customShader = cgs.media.forceShell;
4957 }
4958 else
4959 {
4960 ent->customShader = cgs.media.playerShieldDamage;
4961 }
4962
4963 cgi_R_AddRefEntityToScene( ent );
4964 }
4965 else if ( cent->gent->client->ps.forcePowersActive & (1 << FP_PROTECT) )
4966 { //protect is represented by green..
4967 ent->shaderRGBA[0] = 0;
4968 ent->shaderRGBA[1] = 255;
4969 ent->shaderRGBA[2] = 0;
4970 ent->shaderRGBA[3] = 254;
4971
4972 ent->renderfx &= ~RF_RGB_TINT;
4973 //ent->renderfx &= ~RF_FORCE_ENT_ALPHA;
4974 if ( cent->gent->client->ps.forcePowerLevel[FP_PROTECT] > FORCE_LEVEL_1 )
4975 {
4976 ent->customShader = cgs.media.forceShell;
4977 }
4978 else
4979 {
4980 ent->customShader = cgs.media.playerShieldDamage;
4981 }
4982
4983 cgi_R_AddRefEntityToScene( ent );
4984 }
4985 else if ( cent->gent->client->ps.forcePowersActive & (1 << FP_ABSORB))
4986 { //absorb is represented by blue..
4987 ent->shaderRGBA[0] = 0;
4988 ent->shaderRGBA[1] = 0;
4989 ent->shaderRGBA[2] = 255;
4990 ent->shaderRGBA[3] = 254;
4991
4992 ent->renderfx &= ~RF_RGB_TINT;
4993 //ent->renderfx &= ~RF_FORCE_ENT_ALPHA;
4994 if ( cent->gent->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_1 )
4995 {
4996 ent->customShader = cgs.media.forceShell;
4997 }
4998 else
4999 {
5000 ent->customShader = cgs.media.playerShieldDamage;
5001 }
5002
5003 cgi_R_AddRefEntityToScene( ent );
5004 }
5005
5006 if ((cg.snap->ps.forcePowersActive & (1 << FP_SEE))
5007 && cg.snap->ps.clientNum != cent->currentState.number
5008 && (cent->currentState.eFlags&EF_FORCE_VISIBLE
5009 || ((cent->gent->health > 0 || cent->gent->message )
5010 && cent->currentState.eType == ET_PLAYER//other things handle this in their own render funcs
5011 && CG_PlayerCanSeeCent( cent ))
5012 )
5013 )
5014 {//force sight draws auras around living things
5015 CG_AddForceSightShell( ent, cent );
5016 }
5017
5018 //temp stuff for drain
5019 if ( ( (cent->gent->client->ps.eFlags&EF_FORCE_DRAINED) || cent->gent->client->ps.forcePowersActive&(1<<FP_DRAIN) ) &&
5020 (cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum))
5021 {//draining or being drained
5022 ent->renderfx |= RF_RGB_TINT;
5023 ent->shaderRGBA[0] = 255;
5024 ent->shaderRGBA[1] = ent->shaderRGBA[2] = 0;
5025 ent->shaderRGBA[3] = 255;
5026
5027 if ( rand() & 1 )
5028 {
5029 ent->customShader = cgs.media.electricBodyShader;
5030 }
5031 else
5032 {
5033 ent->customShader = cgs.media.electricBody2Shader;
5034 }
5035
5036 cgi_R_AddRefEntityToScene( ent);
5037 }
5038 }
5039
5040
5041 /*
5042 -------------------------
5043 CG_G2SetHeadBlink
5044 -------------------------
5045 */
CG_G2SetHeadBlink(centity_t * cent,qboolean bStart)5046 static void CG_G2SetHeadBlink( centity_t *cent, qboolean bStart )
5047 {
5048 if ( !cent )
5049 {
5050 return;
5051 }
5052 gentity_t *gent = cent->gent;
5053 //FIXME: get these boneIndices game-side and pass it down?
5054 //FIXME: need a version of this that *doesn't* need the mFileName in the ghoul2
5055 const int hLeye = gi.G2API_GetBoneIndex( &gent->ghoul2[0], "leye", qtrue );
5056 if (hLeye == -1)
5057 {
5058 return;
5059 }
5060
5061 vec3_t desiredAngles = {0};
5062 int blendTime = 80;
5063 qboolean bWink = qfalse;
5064
5065 if (bStart)
5066 {
5067 desiredAngles[YAW] = -38;
5068 if ( !in_camera && Q_flrand(0.0f, 1.0f) > 0.95f )
5069 {
5070 bWink = qtrue;
5071 blendTime /=3;
5072 }
5073 }
5074 gi.G2API_SetBoneAnglesIndex( &gent->ghoul2[gent->playerModel], hLeye, desiredAngles,
5075 BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time );
5076 const int hReye = gi.G2API_GetBoneIndex( &gent->ghoul2[0], "reye", qtrue );
5077 if (hReye == -1)
5078 {
5079 return;
5080 }
5081
5082 if (!bWink)
5083 gi.G2API_SetBoneAnglesIndex( &gent->ghoul2[gent->playerModel], hReye, desiredAngles,
5084 BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time );
5085 }
5086
5087 /*
5088 -------------------------
5089 CG_G2SetHeadAnims
5090 -------------------------
5091 */
CG_G2SetHeadAnim(centity_t * cent,int anim)5092 static void CG_G2SetHeadAnim( centity_t *cent, int anim )
5093 {
5094 gentity_t *gent = cent->gent;
5095 const int blendTime = 50;
5096 const animation_t *animations = level.knownAnimFileSets[gent->client->clientInfo.animFileIndex].animations;
5097 int animFlags = BONE_ANIM_OVERRIDE ;//| BONE_ANIM_BLEND;
5098 // animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps).
5099 // float timeScaleMod = (cg_timescale.value&&gent&&gent->s.clientNum==0&&!player_locked&&!MatrixMode&&gent->client->ps.forcePowersActive&(1<<FP_SPEED))?(1.0/cg_timescale.value):1.0;
5100 const float timeScaleMod = (cg_timescale.value)?(1.0/cg_timescale.value):1.0;
5101 float animSpeed = 50.0f / animations[anim].frameLerp * timeScaleMod;
5102
5103 if (animations[anim].numFrames <= 0)
5104 {
5105 return;
5106 }
5107 if (anim == FACE_DEAD)
5108 {
5109 animFlags |= BONE_ANIM_OVERRIDE_FREEZE;
5110 }
5111 // animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps).
5112 int firstFrame;
5113 int lastFrame;
5114 if ( animSpeed < 0 )
5115 {//play anim backwards
5116
5117 lastFrame = animations[anim].firstFrame -1;
5118 firstFrame = (animations[anim].numFrames -1) + animations[anim].firstFrame ;
5119 }
5120 else
5121 {
5122 firstFrame = animations[anim].firstFrame;
5123 lastFrame = (animations[anim].numFrames) + animations[anim].firstFrame;
5124 }
5125
5126 // first decide if we are doing an animation on the head already
5127 // int startFrame, endFrame;
5128 // const qboolean animatingHead = gi.G2API_GetAnimRangeIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone, &startFrame, &endFrame);
5129
5130 // if (!animatingHead || ( animations[anim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again
5131 {
5132 gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone,
5133 firstFrame, lastFrame, animFlags, animSpeed, cg.time, -1, blendTime);
5134 }
5135 }
5136
CG_G2PlayerHeadAnims(centity_t * cent)5137 static qboolean CG_G2PlayerHeadAnims( centity_t *cent )
5138 {
5139 if(!ValidAnimFileIndex(cent->gent->client->clientInfo.animFileIndex))
5140 {
5141 return qfalse;
5142 }
5143
5144 if (cent->gent->faceBone == BONE_INDEX_INVALID)
5145 { // i don't have a face
5146 return qfalse;
5147 }
5148
5149 int anim = -1;
5150
5151 if ( cent->gent->health <= 0 )
5152 {//Dead people close their eyes and don't make faces!
5153 anim = FACE_DEAD;
5154 }
5155 else
5156 {
5157 if (!cent->gent->client->facial_blink)
5158 { // set the timers
5159 cent->gent->client->facial_blink = cg.time + Q_flrand(4000.0, 8000.0);
5160 cent->gent->client->facial_timer = cg.time + Q_flrand(6000.0, 10000.0);
5161 }
5162
5163 //are we blinking?
5164 if (cent->gent->client->facial_blink < 0)
5165 { // yes, check if we are we done blinking ?
5166 if (-(cent->gent->client->facial_blink) < cg.time)
5167 { // yes, so reset blink timer
5168 cent->gent->client->facial_blink = cg.time + Q_flrand(4000.0, 8000.0);
5169 CG_G2SetHeadBlink( cent, qfalse ); //stop the blink
5170 }
5171 }
5172 else // no we aren't blinking
5173 {
5174 if (cent->gent->client->facial_blink < cg.time)// but should we start ?
5175 {
5176 CG_G2SetHeadBlink( cent, qtrue );
5177 if (cent->gent->client->facial_blink == 1)
5178 {//requested to stay shut by SET_FACEEYESCLOSED
5179 cent->gent->client->facial_blink = -(cg.time + 99999999.0f);// set blink timer
5180 }
5181 else
5182 {
5183 cent->gent->client->facial_blink = -(cg.time + 300.0f);// set blink timer
5184 }
5185 }
5186 }
5187
5188
5189 if (gi.VoiceVolume[cent->gent->s.clientNum] > 0) // if we aren't talking, then it will be 0, -1 for talking but paused
5190 {
5191 anim = FACE_TALK1 + gi.VoiceVolume[cent->gent->s.clientNum] -1;
5192 cent->gent->client->facial_timer = cg.time + Q_flrand(2000.0, 7000.0);
5193 if ( cent->gent->client->breathPuffTime > cg.time + 300 )
5194 {//when talking, do breath puff
5195 cent->gent->client->breathPuffTime = cg.time;
5196 }
5197 }
5198 else if (gi.VoiceVolume[cent->gent->s.clientNum] == -1 )
5199 {//talking but silent
5200 anim = FACE_TALK0;
5201 cent->gent->client->facial_timer = cg.time + Q_flrand(2000.0, 7000.0);
5202 }
5203 else if (gi.VoiceVolume[cent->gent->s.clientNum] == 0) //don't do aux if in a slient part of speech
5204 {//not talking
5205 if (cent->gent->client->facial_timer < 0) // are we auxing ?
5206 { //yes
5207 if (-(cent->gent->client->facial_timer) < cg.time)// are we done auxing ?
5208 { // yes, reset aux timer
5209 cent->gent->client->facial_timer = cg.time + Q_flrand(7000.0, 10000.0);
5210 }
5211 else
5212 { // not yet, so choose anim
5213 anim = cent->gent->client->facial_anim;
5214 }
5215 }
5216 else // no we aren't auxing
5217 { // but should we start ?
5218 if (cent->gent->client->facial_timer < cg.time)
5219 {//yes
5220 cent->gent->client->facial_anim = FACE_ALERT + Q_irand(0,2); //alert, smile, frown
5221 // set aux timer
5222 cent->gent->client->facial_timer = -(cg.time + 2000.0);
5223 anim = cent->gent->client->facial_anim;
5224 }
5225 }
5226 }//talking
5227 }//dead
5228 if (anim != -1)
5229 {
5230 CG_G2SetHeadAnim( cent, anim );
5231 return qtrue;
5232 }
5233 return qfalse;
5234 }
5235
5236 /*
5237 -------------------------
5238 CG_PlayerHeadExtension
5239 -------------------------
5240 */
5241 /*
5242 int CG_PlayerHeadExtension( centity_t *cent, refEntity_t *head )
5243 {
5244 clientInfo_t *ci = ¢->gent->client->clientInfo;;
5245
5246 // if we have facial texture extensions, go get the sound override and add it to the face skin
5247 // if we aren't talking, then it will be 0
5248 if (ci->extensions && (gi.VoiceVolume[cent->gent->s.clientNum] > 0))
5249 {//FIXME: When talking, look at talkTarget, if any
5250 //ALSO: When talking, add a head bob/movement on syllables - when gi.VoiceVolume[] changes drastically
5251
5252 if ( cent->gent->health <= 0 )
5253 {//Dead people close their eyes and don't make faces! They also tell no tales... BUM BUM BAHHHHHHH!
5254 //Make them always blink and frown
5255 head->customSkin = ci->headSkin + 3;
5256 return qtrue;
5257 }
5258
5259 head->customSkin = ci->headSkin + 4+gi.VoiceVolume[cent->gent->s.clientNum];
5260 //reset the frown and blink timers
5261 }
5262 else
5263 // ok, we have facial extensions, but we aren't speaking. Lets decide if we need to frown or blink
5264 if (ci->extensions)
5265 {
5266 int add_in = 0;
5267
5268 // deal with blink first
5269
5270 //Dead people close their eyes and don't make faces! They also tell no tales... BUM BUM BAHHHHHHH!
5271 if ( cent->gent->health <= 0 )
5272 {
5273 //Make them always blink and frown
5274 head->customSkin = ci->headSkin + 3;
5275 return qtrue;
5276 }
5277
5278 if (!cent->gent->client->facial_blink)
5279 { // reset blink timer
5280 cent->gent->client->facial_blink = cg.time + Q_flrand(3000.0, 5000.0);
5281 cent->gent->client->facial_frown = cg.time + Q_flrand(6000.0, 10000.0);
5282 cent->gent->client->facial_aux = cg.time + Q_flrand(6000.0, 10000.0);
5283 }
5284
5285
5286 // now deal with auxing
5287 // are we frowning ?
5288 if (cent->gent->client->facial_aux < 0)
5289 {
5290 // are we done frowning ?
5291 if (-(cent->gent->client->facial_aux) < cg.time)
5292 {
5293 // reset frown timer
5294 cent->gent->client->facial_aux = cg.time + Q_flrand(6000.0, 10000.0);
5295 }
5296 else
5297 {
5298 // yes so set offset to frown
5299 add_in = 4;
5300 }
5301 }
5302 // no we aren't frowning
5303 else
5304 {
5305 // but should we start ?
5306 if (cent->gent->client->facial_aux < cg.time)
5307 {
5308 add_in = 4;
5309 // set blink timer
5310 cent->gent->client->facial_aux = -(cg.time + 3000.0);
5311 }
5312 }
5313
5314 // now, if we aren't auxing - lets see if we should be blinking or frowning
5315 if (!add_in)
5316 {
5317 if( gi.VoiceVolume[cent->gent->s.clientNum] == -1 )
5318 {//then we're talking and don't want to use blinking normal frames, force open eyes.
5319 add_in = 0;
5320 // reset blink timer
5321 cent->gent->client->facial_blink = cg.time + Q_flrand(3000.0, 5000.0);
5322 }
5323 // are we blinking ?
5324 else if (cent->gent->client->facial_blink < 0)
5325 {
5326
5327 // yes so set offset to blink
5328 add_in = 1;
5329
5330 // are we done blinking ?
5331 if (-(cent->gent->client->facial_blink) < cg.time)
5332 {
5333 add_in = 0;
5334 // reset blink timer
5335 cent->gent->client->facial_blink = cg.time + Q_flrand(3000.0, 5000.0);
5336 }
5337 }
5338 // no we aren't blinking
5339 else
5340 {
5341 // but should we start ?
5342 if (cent->gent->client->facial_blink < cg.time)
5343 {
5344 add_in = 1;
5345 // set blink timer
5346 cent->gent->client->facial_blink = -(cg.time + 200.0);
5347 }
5348 }
5349
5350 // now deal with frowning
5351 // are we frowning ?
5352 if (cent->gent->client->facial_frown < 0)
5353 {
5354
5355 // yes so set offset to frown
5356 add_in += 2;
5357
5358 // are we done frowning ?
5359 if (-(cent->gent->client->facial_frown) < cg.time)
5360 {
5361 add_in -= 2;
5362 // reset frown timer
5363 cent->gent->client->facial_frown = cg.time + Q_flrand(6000.0, 10000.0);
5364 }
5365 }
5366 // no we aren't frowning
5367 else
5368 {
5369 // but should we start ?
5370 if (cent->gent->client->facial_frown < cg.time)
5371 {
5372 add_in += 2;
5373 // set blink timer
5374 cent->gent->client->facial_frown = -(cg.time + 3000.0);
5375 }
5376 }
5377 }
5378
5379 // add in whatever we should
5380 head->customSkin = ci->headSkin + add_in;
5381 }
5382 // at this point, we don't have any facial extensions, so who cares ?
5383 else
5384 {
5385 head->customSkin = ci->headSkin;
5386 }
5387
5388 return qtrue;
5389 }
5390 */
5391
5392 //--------------------------------------------------------------
5393 // CG_GetTagWorldPosition
5394 //
5395 // Can pass in NULL for the axis
5396 //--------------------------------------------------------------
CG_GetTagWorldPosition(refEntity_t * model,char * tag,vec3_t pos,vec3_t axis[3])5397 void CG_GetTagWorldPosition( refEntity_t *model, char *tag, vec3_t pos, vec3_t axis[3] )
5398 {
5399 orientation_t orientation;
5400
5401 // Get the requested tag
5402 cgi_R_LerpTag( &orientation, model->hModel, model->oldframe, model->frame,
5403 1.0f - model->backlerp, tag );
5404
5405 VectorCopy( model->origin, pos );
5406 for ( int i = 0 ; i < 3 ; i++ )
5407 {
5408 VectorMA( pos, orientation.origin[i], model->axis[i], pos );
5409 }
5410
5411 if ( axis )
5412 {
5413 MatrixMultiply( orientation.axis, model->axis, axis );
5414 }
5415 }
5416
5417 static qboolean calcedMp = qfalse;
5418
5419 /*
5420 -------------------------
5421 CG_GetPlayerLightLevel
5422 -------------------------
5423 */
5424
CG_GetPlayerLightLevel(centity_t * cent)5425 static void CG_GetPlayerLightLevel( centity_t *cent )
5426 {
5427 vec3_t ambient={0}, directed, lightDir;
5428
5429 //Poll the renderer for the light level
5430 if ( cent->currentState.clientNum == cg.snap->ps.clientNum )
5431 {//hAX0R
5432 ambient[0] = 666;
5433 }
5434 cgi_R_GetLighting( cent->lerpOrigin, ambient, directed, lightDir );
5435
5436 //Get the maximum value for the player
5437 cent->gent->lightLevel = directed[0];
5438
5439 if ( directed[1] > cent->gent->lightLevel )
5440 cent->gent->lightLevel = directed[1];
5441
5442 if ( directed[2] > cent->gent->lightLevel )
5443 cent->gent->lightLevel = directed[2];
5444
5445 if ( cent->gent->client->ps.weapon == WP_SABER && cent->gent->client->ps.SaberLength() > 0 )
5446 {
5447 cent->gent->lightLevel += (cent->gent->client->ps.SaberLength()/cent->gent->client->ps.SaberLengthMax())*200;
5448 }
5449 }
5450
5451 /*
5452 ===============
5453 CG_StopWeaponSounds
5454
5455 Stops any weapon sounds as needed
5456 ===============
5457 */
CG_StopWeaponSounds(centity_t * cent)5458 static void CG_StopWeaponSounds( centity_t *cent )
5459 {
5460 weaponInfo_t *weapon = &cg_weapons[ cent->currentState.weapon ];
5461
5462 if ( cent->currentState.weapon == WP_SABER )
5463 {
5464 if ( cent->gent && cent->gent->client )
5465 {
5466 if ( !cent->gent->client->ps.SaberActive() )
5467 {//neither saber is on
5468 return;
5469 }
5470 else if ( cent->gent->client->ps.saberInFlight ) //cent->gent->client->ps.saberInFlight )
5471 {//throwing saber
5472 if ( !cent->gent->client->ps.dualSabers || !cent->gent->client->ps.saber[1].Active() )
5473 {//don't have a second saber or it's not on
5474 return;
5475 }
5476 }
5477 }
5478
5479 cgi_S_AddLoopingSound( cent->currentState.number,
5480 cent->lerpOrigin,
5481 vec3_origin,
5482 cgs.sound_precache[g_entities[cent->currentState.clientNum].client->ps.saber[0].soundLoop] );
5483 return;
5484 }
5485
5486 if ( cent->currentState.weapon == WP_STUN_BATON || cent->currentState.weapon == WP_CONCUSSION )
5487 { //idling sounds
5488 cgi_S_AddLoopingSound( cent->currentState.number,
5489 cent->lerpOrigin,
5490 vec3_origin,
5491 weapon->firingSound );
5492 return;
5493 }
5494
5495 if ( !( cent->currentState.eFlags & EF_FIRING ) )
5496 {
5497 if ( cent->pe.lightningFiring )
5498 {
5499 if ( weapon->stopSound )
5500 {
5501 cgi_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_WEAPON, weapon->stopSound );
5502 }
5503
5504 cent->pe.lightningFiring = qfalse;
5505 }
5506 return;
5507 }
5508
5509 if ( cent->currentState.eFlags & EF_ALT_FIRING )
5510 {
5511 if ( weapon->altFiringSound )
5512 {
5513 cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->altFiringSound );
5514 }
5515 cent->pe.lightningFiring = qtrue;
5516 }
5517 }
5518
5519
5520 //--------------- SABER STUFF --------
5521 extern void CG_Smoke( vec3_t origin, vec3_t dir, float radius, float speed, qhandle_t shader, int flags);
CG_SaberDoWeaponHitMarks(gclient_t * client,gentity_t * saberEnt,gentity_t * hitEnt,int saberNum,int bladeNum,vec3_t hitPos,vec3_t hitDir,vec3_t uaxis,vec3_t splashBackDir,float sizeTimeScale)5522 void CG_SaberDoWeaponHitMarks( gclient_t *client, gentity_t *saberEnt, gentity_t *hitEnt, int saberNum, int bladeNum, vec3_t hitPos, vec3_t hitDir, vec3_t uaxis, vec3_t splashBackDir, float sizeTimeScale )
5523 {
5524 if ( client
5525 && sizeTimeScale > 0.0f
5526 && hitEnt
5527 && hitEnt->client
5528 && hitEnt->ghoul2.size() )
5529 {//burn mark with glow
5530 //FIXME: set the correct angle based on direction of swing
5531 //FIXME: keep a count of these on the ent and don't add too many
5532 int lifeTime = (1.01-(float)(hitEnt->health)/hitEnt->max_health) * (float)Q_irand( 5000, 10000 );
5533 float size = 0.0f;
5534 int weaponMarkShader = 0, markShader = cgs.media.bdecal_saberglowmark;
5535
5536 //First: do mark decal on hitEnt
5537 if ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) )
5538 {
5539 if ( client->ps.saber[saberNum].g2MarksShader2[0] )
5540 {//we have a shader to use instead of the standard mark shader
5541 markShader = cgi_R_RegisterShader( client->ps.saber[saberNum].g2MarksShader2 );
5542 lifeTime = Q_irand( 20000, 30000 );//last longer if overridden
5543 }
5544 }
5545 else
5546 {
5547 if ( client->ps.saber[saberNum].g2MarksShader[0] )
5548 {//we have a shader to use instead of the standard mark shader
5549 markShader = cgi_R_RegisterShader( client->ps.saber[saberNum].g2MarksShader );
5550 lifeTime = Q_irand( 20000, 30000 );//last longer if overridden
5551 }
5552 }
5553
5554 if ( markShader )
5555 {
5556 lifeTime = ceil( (float)lifeTime * sizeTimeScale );
5557 size = Q_flrand( 2.0f, 3.0f ) * sizeTimeScale;
5558 CG_AddGhoul2Mark( markShader, size, hitPos, hitDir, hitEnt->s.number,
5559 hitEnt->client->ps.origin, hitEnt->client->renderInfo.legsYaw, hitEnt->ghoul2, hitEnt->s.modelScale,
5560 lifeTime, 0, uaxis );
5561 }
5562
5563 //now do weaponMarkShader - splashback decal on weapon
5564 if ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) )
5565 {
5566 if ( client->ps.saber[saberNum].g2WeaponMarkShader2[0] )
5567 {//we have a shader to use instead of the standard mark shader
5568 weaponMarkShader = cgi_R_RegisterShader( client->ps.saber[saberNum].g2WeaponMarkShader2 );
5569 lifeTime = Q_irand( 7000, 12000 );//last longer if overridden
5570 }
5571 }
5572 else
5573 {
5574 if ( client->ps.saber[saberNum].g2WeaponMarkShader[0] )
5575 {//we have a shader to use instead of the standard mark shader
5576 weaponMarkShader = cgi_R_RegisterShader( client->ps.saber[saberNum].g2WeaponMarkShader );
5577 lifeTime = Q_irand( 7000, 12000 );//last longer if overridden
5578 }
5579 }
5580
5581 if ( weaponMarkShader )
5582 {
5583 centity_t *splatterOnCent = (saberEnt&&client->ps.saberInFlight?&cg_entities[saberEnt->s.number]:&cg_entities[client->ps.clientNum]);
5584 float yawAngle = 0;
5585 vec3_t backDir;
5586 VectorScale( hitDir, -1, backDir );
5587 if ( !splatterOnCent->gent->client )
5588 {
5589 yawAngle = splatterOnCent->lerpAngles[YAW];
5590 }
5591 else
5592 {
5593 yawAngle = splatterOnCent->gent->client->renderInfo.legsYaw;
5594 }
5595 lifeTime = ceil( (float)lifeTime * sizeTimeScale );
5596 size = Q_flrand( 1.0f, 3.0f ) * sizeTimeScale;
5597 if ( splatterOnCent->gent->ghoul2.size() > saberNum+1 )
5598 {
5599 CG_AddGhoul2Mark( weaponMarkShader, size, hitPos, backDir, splatterOnCent->currentState.number,
5600 splatterOnCent->lerpOrigin, yawAngle, splatterOnCent->gent->ghoul2, splatterOnCent->currentState.modelScale,
5601 lifeTime, saberNum+1, uaxis/*splashBackDir*/ );
5602 }
5603 }
5604 }
5605 }
5606
CG_RGBForSaberColor(saber_colors_t color,vec3_t rgb)5607 static void CG_RGBForSaberColor( saber_colors_t color, vec3_t rgb )
5608 {
5609 switch( color )
5610 {
5611 case SABER_RED:
5612 VectorSet( rgb, 1.0f, 0.2f, 0.2f );
5613 break;
5614 case SABER_ORANGE:
5615 VectorSet( rgb, 1.0f, 0.5f, 0.1f );
5616 break;
5617 case SABER_YELLOW:
5618 VectorSet( rgb, 1.0f, 1.0f, 0.2f );
5619 break;
5620 case SABER_GREEN:
5621 VectorSet( rgb, 0.2f, 1.0f, 0.2f );
5622 break;
5623 case SABER_BLUE:
5624 VectorSet( rgb, 0.2f, 0.4f, 1.0f );
5625 break;
5626 case SABER_PURPLE:
5627 VectorSet( rgb, 0.9f, 0.2f, 1.0f );
5628 break;
5629 }
5630 }
5631
CG_DoSaberLight(saberInfo_t * saber)5632 static void CG_DoSaberLight( saberInfo_t *saber )
5633 {
5634 int firstBlade = 0;
5635 int lastBlade;
5636 //RGB combine all the colors of the sabers you're using into one averaged color!
5637 if ( !saber )
5638 {
5639 return;
5640 }
5641
5642 lastBlade = saber->numBlades - 1;
5643
5644 if ( (saber->saberFlags2&SFL2_NO_DLIGHT) )
5645 {
5646 if ( saber->bladeStyle2Start > 0 )
5647 {
5648 if ( (saber->saberFlags2&SFL2_NO_DLIGHT2) )
5649 {
5650 return;
5651 }
5652 else
5653 {
5654 firstBlade = saber->bladeStyle2Start;
5655 }
5656 }
5657 else
5658 {
5659 return;
5660 }
5661 }
5662 else if ( saber->bladeStyle2Start > 0 )
5663 {
5664 if ( (saber->saberFlags2&SFL2_NO_DLIGHT2) )
5665 {
5666 lastBlade = saber->bladeStyle2Start;
5667 }
5668 }
5669
5670 vec3_t positions[MAX_BLADES*2], mid={0}, rgbs[MAX_BLADES*2], rgb={0};
5671 float lengths[MAX_BLADES*2]={0}, totallength = 0, numpositions = 0, dist, diameter = 0;
5672 int i, j;
5673
5674 if ( saber )
5675 {
5676 for ( i = firstBlade; i <= lastBlade; i++ )
5677 {
5678 if ( saber->blade[i].length >= MIN_SABERBLADE_DRAW_LENGTH )
5679 {
5680 //FIXME: make RGB sabers
5681 CG_RGBForSaberColor( saber->blade[i].color, rgbs[i] );
5682 lengths[i] = saber->blade[i].length;
5683 if ( saber->blade[i].length*2.0f > diameter )
5684 {
5685 diameter = saber->blade[i].length*2.0f;
5686 }
5687 totallength += saber->blade[i].length;
5688 VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length, saber->blade[i].muzzleDir, positions[i] );
5689 if ( !numpositions )
5690 {//first blade, store middle of that as midpoint
5691 VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length*0.5, saber->blade[i].muzzleDir, mid );
5692 VectorCopy( rgbs[i], rgb );
5693 }
5694 numpositions++;
5695 }
5696 }
5697 }
5698
5699 if ( totallength )
5700 {//actually have something to do
5701 if ( numpositions == 1 )
5702 {//only 1 blade, midpoint is already set (halfway between the start and end of that blade), rgb is already set, so it diameter
5703 }
5704 else
5705 {//multiple blades, calc averages
5706 VectorClear( mid );
5707 VectorClear( rgb );
5708 //now go through all the data and get the average RGB and middle position and the radius
5709 for ( i = 0; i < MAX_BLADES*2; i++ )
5710 {
5711 if ( lengths[i] )
5712 {
5713 VectorMA( rgb, lengths[i], rgbs[i], rgb );
5714 VectorAdd( mid, positions[i], mid );
5715 }
5716 }
5717
5718 //get middle rgb
5719 VectorScale( rgb, 1/totallength, rgb );//get the average, normalized RGB
5720 //get mid position
5721 VectorScale( mid, 1/numpositions, mid );
5722 //find the farthest distance between the blade tips, this will be our diameter
5723 for ( i = 0; i < MAX_BLADES*2; i++ )
5724 {
5725 if ( lengths[i] )
5726 {
5727 for ( j = 0; j < MAX_BLADES*2; j++ )
5728 {
5729 if ( lengths[j] )
5730 {
5731 dist = Distance( positions[i], positions[j] );
5732 if ( dist > diameter )
5733 {
5734 diameter = dist;
5735 }
5736 }
5737 }
5738 }
5739 }
5740 }
5741
5742 cgi_R_AddLightToScene( mid, diameter + (Q_flrand(0.0f, 1.0f)*8.0f), rgb[0], rgb[1], rgb[2] );
5743 }
5744 }
5745
CG_DoSaber(vec3_t origin,vec3_t dir,float length,float lengthMax,float radius,saber_colors_t color,int rfx,qboolean doLight)5746 static void CG_DoSaber( vec3_t origin, vec3_t dir, float length, float lengthMax, float radius, saber_colors_t color, int rfx, qboolean doLight )
5747 {
5748 vec3_t mid;
5749 qhandle_t blade = 0, glow = 0;
5750 refEntity_t saber;
5751 float radiusmult;
5752
5753 if ( length < MIN_SABERBLADE_DRAW_LENGTH )
5754 {
5755 // if the thing is so short, just forget even adding me.
5756 return;
5757 }
5758
5759 // Find the midpoint of the saber for lighting purposes
5760 VectorMA( origin, length * 0.5f, dir, mid );
5761
5762 switch( color )
5763 {
5764 case SABER_RED:
5765 glow = cgs.media.redSaberGlowShader;
5766 blade = cgs.media.redSaberCoreShader;
5767 break;
5768 case SABER_ORANGE:
5769 glow = cgs.media.orangeSaberGlowShader;
5770 blade = cgs.media.orangeSaberCoreShader;
5771 break;
5772 case SABER_YELLOW:
5773 glow = cgs.media.yellowSaberGlowShader;
5774 blade = cgs.media.yellowSaberCoreShader;
5775 break;
5776 case SABER_GREEN:
5777 glow = cgs.media.greenSaberGlowShader;
5778 blade = cgs.media.greenSaberCoreShader;
5779 break;
5780 case SABER_BLUE:
5781 glow = cgs.media.blueSaberGlowShader;
5782 blade = cgs.media.blueSaberCoreShader;
5783 break;
5784 case SABER_PURPLE:
5785 glow = cgs.media.purpleSaberGlowShader;
5786 blade = cgs.media.purpleSaberCoreShader;
5787 break;
5788 }
5789
5790 // always add a light because sabers cast a nice glow before they slice you in half!! or something...
5791 if ( doLight )
5792 {//FIXME: RGB combine all the colors of the sabers you're using into one averaged color!
5793 vec3_t rgb={1,1,1};
5794 CG_RGBForSaberColor( color, rgb );
5795 cgi_R_AddLightToScene( mid, (length*1.4f) + (Q_flrand(0.0f, 1.0f)*3.0f), rgb[0], rgb[1], rgb[2] );
5796 }
5797
5798 memset( &saber, 0, sizeof( refEntity_t ));
5799
5800 // Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many
5801 // refEnts to do each glow blob individually
5802 saber.saberLength = length;
5803
5804 // Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed.
5805 // It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat
5806 if (length < lengthMax )
5807 {
5808 radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5.
5809 }
5810 else
5811 {
5812 radiusmult = 1.0;
5813 }
5814
5815 float radiusRange = radius * 0.075f;
5816 float radiusStart = radius-radiusRange;
5817
5818 saber.radius = (radiusStart + Q_flrand(-1.0f, 1.0f) * radiusRange)*radiusmult;
5819 //saber.radius = (2.8f + Q_flrand(-1.0f, 1.0f) * 0.2f)*radiusmult;
5820
5821
5822 VectorCopy( origin, saber.origin );
5823 VectorCopy( dir, saber.axis[0] );
5824 saber.reType = RT_SABER_GLOW;
5825 saber.customShader = glow;
5826 saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff;
5827 saber.renderfx = rfx;
5828
5829 cgi_R_AddRefEntityToScene( &saber );
5830
5831 // Do the hot core
5832 VectorMA( origin, length, dir, saber.origin );
5833 VectorMA( origin, -1, dir, saber.oldorigin );
5834 saber.customShader = blade;
5835 saber.reType = RT_LINE;
5836 radiusStart = radius/3.0f;
5837 saber.radius = (radiusStart + Q_flrand(-1.0f, 1.0f) * radiusRange)*radiusmult;
5838 // saber.radius = (1.0 + Q_flrand(-1.0f, 1.0f) * 0.2f)*radiusmult;
5839
5840 cgi_R_AddRefEntityToScene( &saber );
5841 }
5842
5843 #define MAX_MARK_FRAGMENTS 128
5844 #define MAX_MARK_POINTS 384
5845 extern markPoly_t *CG_AllocMark();
5846
CG_CreateSaberMarks(vec3_t start,vec3_t end,vec3_t normal)5847 static void CG_CreateSaberMarks( vec3_t start, vec3_t end, vec3_t normal )
5848 {
5849 // byte colors[4];
5850 int i, j, numFragments;
5851 vec3_t axis[3], originalPoints[4], mid;
5852 vec3_t markPoints[MAX_MARK_POINTS], projection;
5853 polyVert_t *v, verts[MAX_VERTS_ON_POLY];
5854 markPoly_t *mark;
5855 markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf;
5856
5857 if ( !cg_addMarks.integer ) {
5858 return;
5859 }
5860
5861 float radius = 0.65f;
5862
5863 VectorSubtract( end, start, axis[1] );
5864 VectorNormalizeFast( axis[1] );
5865
5866 // create the texture axis
5867 VectorCopy( normal, axis[0] );
5868 CrossProduct( axis[1], axis[0], axis[2] );
5869
5870 // create the full polygon that we'll project
5871 for ( i = 0 ; i < 3 ; i++ )
5872 {
5873 originalPoints[0][i] = start[i] - radius * axis[1][i] - radius * axis[2][i];
5874 originalPoints[1][i] = end[i] + radius * axis[1][i] - radius * axis[2][i];
5875 originalPoints[2][i] = end[i] + radius * axis[1][i] + radius * axis[2][i];
5876 originalPoints[3][i] = start[i] - radius * axis[1][i] + radius * axis[2][i];
5877 }
5878
5879 VectorScale( normal, -1, projection );
5880
5881 // get the fragments
5882 numFragments = cgi_CM_MarkFragments( 4, (const float (*)[3])originalPoints,
5883 projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments );
5884
5885
5886 for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ )
5887 {
5888 // we have an upper limit on the complexity of polygons that we store persistantly
5889 if ( mf->numPoints > MAX_VERTS_ON_POLY )
5890 {
5891 mf->numPoints = MAX_VERTS_ON_POLY;
5892 }
5893
5894 for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ )
5895 {
5896 vec3_t delta;
5897
5898 // Set up our texture coords, this may need some work
5899 VectorCopy( markPoints[mf->firstPoint + j], v->xyz );
5900 VectorAdd( end, start, mid );
5901 VectorScale( mid, 0.5f, mid );
5902 VectorSubtract( v->xyz, mid, delta );
5903
5904 v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * (0.05f + Q_flrand(0.0f, 1.0f) * 0.03f);
5905 v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * (0.15f + Q_flrand(0.0f, 1.0f) * 0.05f);
5906 }
5907
5908 // save it persistantly, do burn first
5909 mark = CG_AllocMark();
5910 mark->time = cg.time;
5911 mark->alphaFade = qtrue;
5912 mark->markShader = cgs.media.rivetMarkShader;
5913 mark->poly.numVerts = mf->numPoints;
5914 mark->color[0] = mark->color[1] = mark->color[2] = mark->color[3] = 255;
5915 memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) );
5916
5917 // And now do a glow pass
5918 // by moving the start time back, we can hack it to fade out way before the burn does
5919 mark = CG_AllocMark();
5920 mark->time = cg.time - 8500;
5921 mark->alphaFade = qfalse;
5922 mark->markShader = cgi_R_RegisterShader("gfx/effects/saberDamageGlow" );
5923 mark->poly.numVerts = mf->numPoints;
5924 mark->color[0] = 215 + Q_flrand(0.0f, 1.0f) * 40.0f;
5925 mark->color[1] = 96 + Q_flrand(0.0f, 1.0f) * 32.0f;
5926 mark->color[2] = mark->color[3] = Q_flrand(0.0f, 1.0f)*15.0f;
5927 memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) );
5928 }
5929 }
5930
5931 extern void FX_AddPrimitive( CEffect **effect, int killTime );
5932 //-------------------------------------------------------
CG_CheckSaberInWater(centity_t * cent,centity_t * scent,int saberNum,int modelIndex,vec3_t origin,vec3_t angles)5933 void CG_CheckSaberInWater( centity_t *cent, centity_t *scent, int saberNum, int modelIndex, vec3_t origin, vec3_t angles )
5934 {
5935 gclient_t *client = cent->gent->client;
5936 if ( !client )
5937 {
5938 return;
5939 }
5940 if ( !scent ||
5941 modelIndex == -1 ||
5942 scent->gent->ghoul2.size() <= modelIndex ||
5943 scent->gent->ghoul2[modelIndex].mBltlist.size() <= 0 || //using a camera puts away your saber so you have no bolts
5944 scent->gent->ghoul2[modelIndex].mModelindex == -1 )
5945 {
5946 return;
5947 }
5948 if ( cent && cent->gent && cent->gent->client
5949 && (cent->gent->client->ps.saber[saberNum].saberFlags&SFL_ON_IN_WATER) )
5950 {//saber can stay on underwater
5951 return;
5952 }
5953 if (gi.totalMapContents() & (CONTENTS_WATER|CONTENTS_SLIME))
5954 {
5955 vec3_t saberOrg;
5956 mdxaBone_t boltMatrix;
5957
5958 // figure out where the actual model muzzle is
5959 gi.G2API_GetBoltMatrix( scent->gent->ghoul2, modelIndex, 0, &boltMatrix, angles, origin, cg.time, cgs.model_draw, scent->currentState.modelScale );
5960 // work the matrix axis stuff into the original axis and origins used.
5961 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, saberOrg );
5962
5963 const int contents = gi.pointcontents( saberOrg, cent->currentState.clientNum );
5964 if ( contents & (CONTENTS_WATER|CONTENTS_SLIME) )
5965 {//still in water
5966 client->ps.saberEventFlags |= SEF_INWATER;
5967 return;
5968 }
5969 }
5970 //not in water
5971 client->ps.saberEventFlags &= ~SEF_INWATER;
5972 }
5973
CG_AddSaberBladeGo(centity_t * cent,centity_t * scent,refEntity_t * saber,int renderfx,int modelIndex,vec3_t origin,vec3_t angles,int saberNum,int bladeNum)5974 static void CG_AddSaberBladeGo( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, vec3_t origin, vec3_t angles, int saberNum, int bladeNum )
5975 {
5976 vec3_t org_, end,//org_future,
5977 axis_[3] = {{0,0,0}, {0,0,0}, {0,0,0}};//, axis_future[3]={0,0,0, 0,0,0, 0,0,0}; // shut the compiler up
5978 trace_t trace;
5979 float length;
5980 int bolt;
5981 mdxaBone_t boltMatrix;
5982 qboolean tagHack = qfalse;
5983
5984 gclient_t *client = cent->gent->client;
5985
5986 if ( !client )
5987 {
5988 return;
5989 }
5990 /*
5991 Ghoul2 Insert Start
5992 */
5993
5994 // if (scent->gent->ghoul2.size())
5995 if(1)
5996 {
5997 if ( !scent ||
5998 modelIndex == -1 ||
5999 scent->gent->ghoul2.size() <= modelIndex ||
6000 scent->gent->ghoul2[modelIndex].mModelindex == -1 )
6001 {
6002 return;
6003 }
6004
6005 /*
6006 if ( cent->gent->client->ps.saber[saberNum].type == SABER_CLAW )
6007 {//hack - come off the forearm
6008 int fwdAxis = POSITIVE_Y;
6009 int rtAxis = POSITIVE_X;
6010 int upAxis = POSITIVE_Z;
6011 if ( saberNum == 0 )
6012 {
6013 bolt = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], "*r_hand_cap_r_arm" );
6014 if ( bolt == -1 )
6015 {
6016 bolt = cent->gent->handRBolt;
6017 fwdAxis = NEGATIVE_Y;
6018 rtAxis = POSITIVE_Z;
6019 upAxis = NEGATIVE_X;
6020 }
6021 }
6022 else
6023 {
6024 bolt = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], "*l_hand_cap_l_arm" );
6025 if ( bolt == -1 )
6026 {
6027 bolt = cent->gent->handLBolt;
6028 fwdAxis = NEGATIVE_Y;
6029 rtAxis = POSITIVE_Z;
6030 upAxis = POSITIVE_X;
6031 }
6032 }
6033 tagHack = qtrue;//use the hacked switch statement below to position and orient the blades
6034 // figure out where the actual model muzzle is
6035 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt, &boltMatrix, angles, origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
6036 // work the matrix axis stuff into the original axis and origins used.
6037 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, org_);
6038 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, (Eorientations)fwdAxis, axis_[0]);
6039 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, (Eorientations)rtAxis, axis_[1]);//right
6040 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, (Eorientations)upAxis, axis_[2]);//up
6041 }
6042 else
6043 */
6044 {
6045 // figure out where the actual model muzzle is
6046
6047 //old way - only 1 tag ever in a saber:
6048 //gi.G2API_GetBoltMatrix(scent->gent->ghoul2, modelIndex, 0, &boltMatrix, angles, origin, cg.time, cgs.model_draw, scent->currentState.modelScale);
6049
6050 //New way, multiple blade tags:
6051 char *tagName = va( "*blade%d", bladeNum+1 );
6052 bolt = gi.G2API_AddBolt( &scent->gent->ghoul2[modelIndex], tagName );
6053
6054 if ( bolt == -1 )
6055 {
6056 tagHack = qtrue;//use the hacked switch statement below to position and orient the blades
6057 //hmm, just fall back to the most basic tag (this will also make it work with pre-JKA saber models
6058 bolt = gi.G2API_AddBolt( &scent->gent->ghoul2[modelIndex], "*flash" );
6059 if ( bolt == -1 )
6060 {//no tag_flash either?!!
6061 bolt = 0;
6062 }
6063 }
6064
6065 //if there is an effect on this blade, play it
6066 if ( !WP_SaberBladeUseSecondBladeStyle( ¢->gent->client->ps.saber[saberNum], bladeNum )
6067 && cent->gent->client->ps.saber[saberNum].bladeEffect )
6068 {
6069 CG_PlayEffectIDBolted( cent->gent->client->ps.saber[saberNum].bladeEffect, modelIndex, bolt, scent->currentState.clientNum, scent->lerpOrigin, -1, qfalse );
6070 }
6071 else if ( WP_SaberBladeUseSecondBladeStyle( ¢->gent->client->ps.saber[saberNum], bladeNum )
6072 && cent->gent->client->ps.saber[saberNum].bladeEffect2 )
6073 {
6074 CG_PlayEffectIDBolted( cent->gent->client->ps.saber[saberNum].bladeEffect2, modelIndex, bolt, scent->currentState.clientNum, scent->lerpOrigin, -1, qfalse );
6075 }
6076 //get the boltMatrix
6077 gi.G2API_GetBoltMatrix(scent->gent->ghoul2, modelIndex, bolt, &boltMatrix, angles, origin, cg.time, cgs.model_draw, scent->currentState.modelScale);
6078
6079 // work the matrix axis stuff into the original axis and origins used.
6080 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, org_);
6081 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_X, axis_[0]);//front (was NEGATIVE_Y, but the md3->glm exporter screws up this tag somethin' awful)
6082 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_Y, axis_[1]);//right
6083 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, POSITIVE_Z, axis_[2]);//up
6084 }
6085
6086 //Now figure out where this info will be next frame
6087 /*
6088 {
6089 vec3_t futureOrigin, futureAngles, orgDiff, angDiff;
6090 int futuretime;
6091
6092 //futuretime = (int)((cg.time + 99)/50) * 50;
6093 futuretime = cg.time+100;
6094
6095 VectorCopy( angles, futureAngles );
6096 VectorCopy( origin, futureOrigin );
6097
6098 //note: for a thrown saber, this does nothing, really
6099 if ( cent->gent )
6100 {
6101 VectorSubtract( cent->lerpOrigin, cent->gent->lastOrigin, orgDiff );
6102 VectorSubtract( cent->lerpAngles, cent->gent->lastAngles, angDiff );
6103 VectorAdd( futureOrigin, orgDiff, futureOrigin );
6104 for ( int i = 0; i < 3; i++ )
6105 {
6106 futureAngles[i] = AngleNormalize360( futureAngles[i]+angDiff[i] );
6107 }
6108 }
6109
6110 // figure out where the actual model muzzle will be after next server frame.
6111 gi.G2API_GetBoltMatrix(scent->gent->ghoul2, modelIndex, 0, &boltMatrix, futureAngles, futureOrigin, futuretime, cgs.model_draw, scent->currentState.modelScale);
6112 // work the matrix axis stuff into the original axis and origins used.
6113 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, org_future);
6114 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_X, axis_future[0]);//was NEGATIVE_Y, but the md3->glm exporter screws up this tag somethin' awful
6115 }
6116 */
6117 }
6118 else
6119 {
6120 CG_GetTagWorldPosition( saber, "*flash", org_, axis_ );
6121 }
6122
6123 /*
6124 Ghoul2 Insert End
6125 */
6126
6127 //====FIXMEFIXMEFIXMEFIXMEFIXME========================================================
6128 //FIXME: temp hack until we have a tag_flash2 for the second (3rd? 4th?) blade
6129 //FIXME: maybe fall back on this if the saber model has only 1 tag_flash?
6130 // or, better yet, if the saber info doesn't list tagnames for the blades?
6131 if ( tagHack )
6132 {
6133 switch ( cent->gent->client->ps.saber[saberNum].type )
6134 {
6135 case SABER_SINGLE:
6136 case SABER_DAGGER:
6137 case SABER_LANCE:
6138 break;
6139 case SABER_STAFF:
6140 if ( bladeNum == 1 )
6141 {
6142 VectorScale( axis_[0], -1, axis_[0] );
6143 VectorMA( org_, 16, axis_[0], org_ );
6144 }
6145 break;
6146 case SABER_BROAD:
6147 if ( bladeNum == 0 )
6148 {
6149 VectorMA( org_, -1, axis_[1], org_ );
6150 }
6151 else if ( bladeNum == 1 )
6152 {
6153 VectorMA( org_, 1, axis_[1], org_ );
6154 }
6155 break;
6156 case SABER_PRONG:
6157 if ( bladeNum == 0 )
6158 {
6159 VectorMA( org_, -3, axis_[1], org_ );
6160 }
6161 else if ( bladeNum == 1 )
6162 {
6163 VectorMA( org_, 3, axis_[1], org_ );
6164 }
6165 break;
6166 case SABER_ARC:
6167 VectorSubtract( axis_[1], axis_[2], axis_[1] );
6168 VectorNormalizeFast( axis_[1] );
6169 switch ( bladeNum )
6170 {
6171 case 0:
6172 VectorMA( org_, 8, axis_[0], org_ );
6173 VectorScale( axis_[0], 0.75f, axis_[0] );
6174 VectorScale( axis_[1], 0.25f, axis_[1] );
6175 VectorAdd( axis_[0], axis_[1], axis_[0] );
6176 //VectorNormalize( axis_[0] );
6177 break;
6178 case 1:
6179 //VectorMA( org_, 0, axis_[0], org_ );
6180 VectorScale( axis_[0], 0.25f, axis_[0] );
6181 VectorScale( axis_[1], 0.75f, axis_[1] );
6182 VectorAdd( axis_[0], axis_[1], axis_[0] );
6183 //VectorNormalize( axis_[0] );
6184 break;
6185 case 2:
6186 VectorMA( org_, -8, axis_[0], org_ );
6187 VectorScale( axis_[0], -0.25f, axis_[0] );
6188 VectorScale( axis_[1], 0.75f, axis_[1] );
6189 VectorAdd( axis_[0], axis_[1], axis_[0] );
6190 //VectorNormalize( axis_[0] );
6191 break;
6192 case 3:
6193 VectorMA( org_, -16, axis_[0], org_ );
6194 VectorScale( axis_[0], -0.75f, axis_[0] );
6195 VectorScale( axis_[1], 0.25f, axis_[1] );
6196 VectorAdd( axis_[0], axis_[1], axis_[0] );
6197 //VectorNormalize( axis_[0] );
6198 break;
6199 }
6200 break;
6201 case SABER_SAI:
6202 if ( bladeNum == 1 )
6203 {
6204 VectorMA( org_, -3, axis_[1], org_ );
6205 }
6206 else if ( bladeNum == 2 )
6207 {
6208 VectorMA( org_, 3, axis_[1], org_ );
6209 }
6210 break;
6211 case SABER_CLAW:
6212 switch ( bladeNum )
6213 {
6214 case 0:
6215 VectorMA( org_, 2, axis_[0], org_ );
6216 VectorMA( org_, 2, axis_[2], org_ );
6217 break;
6218 case 1:
6219 VectorMA( org_, 2, axis_[0], org_ );
6220 VectorMA( org_, 2, axis_[2], org_ );
6221 VectorMA( org_, 2, axis_[1], org_ );
6222 break;
6223 case 2:
6224 VectorMA( org_, 2, axis_[0], org_ );
6225 VectorMA( org_, 2, axis_[2], org_ );
6226 VectorMA( org_, -2, axis_[1], org_ );
6227 break;
6228 }
6229 /*
6230 if ( bladeNum == 1 )
6231 {
6232 VectorMA( org_, -2, axis_[1], org_ );
6233 }
6234 else if ( bladeNum == 2 )
6235 {
6236 VectorMA( org_, 2, axis_[1], org_ );
6237 }
6238 */
6239 break;
6240 case SABER_STAR:
6241 /*
6242 if ( saber )
6243 {
6244 VectorCopy( saber->origin, org_ );
6245 }
6246 else
6247 {
6248 bolt = cent->gent->handRBolt;
6249 if ( saberNum == 1 )
6250 {
6251 bolt = cent->gent->handLBolt;
6252 }
6253 // figure out where the actual model muzzle is
6254 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt, &boltMatrix, angles, origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
6255 // work the matrix axis stuff into the original axis and origins used.
6256 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, org_);
6257 }
6258 */
6259 /*
6260 if ( cent->currentState.clientNum && cent->gent->client->ps.saberInFlight )
6261 {//WTF? For some reason, sabers thrown by NPCs are 90 degrees off on roll
6262 VectorCopy( axis_[1], axis_[2] );
6263 }
6264 */
6265 switch ( bladeNum )
6266 {
6267 case 0:
6268 VectorMA( org_, 8, axis_[0], org_ );
6269 break;
6270 case 1:
6271 VectorScale( axis_[0], 0.33f, axis_[0] );
6272 VectorScale( axis_[2], 0.67f, axis_[2] );
6273 VectorAdd( axis_[0], axis_[2], axis_[0] );
6274 //VectorNormalize( axis_[0] );
6275 VectorMA( org_, 8, axis_[0], org_ );
6276 break;
6277 case 2:
6278 VectorScale( axis_[0], -0.33f, axis_[0] );
6279 VectorScale( axis_[2], 0.67f, axis_[2] );
6280 VectorAdd( axis_[0], axis_[2], axis_[0] );
6281 //VectorNormalize( axis_[0] );
6282 VectorMA( org_, 8, axis_[0], org_ );
6283 break;
6284 case 3:
6285 VectorScale( axis_[0], -1, axis_[0] );
6286 VectorMA( org_, 8, axis_[0], org_ );
6287 break;
6288 case 4:
6289 VectorScale( axis_[0], -0.33f, axis_[0] );
6290 VectorScale( axis_[2], -0.67f, axis_[2] );
6291 VectorAdd( axis_[0], axis_[2], axis_[0] );
6292 //VectorNormalize( axis_[0] );
6293 VectorMA( org_, 8, axis_[0], org_ );
6294 break;
6295 case 5:
6296 VectorScale( axis_[0], 0.33f, axis_[0] );
6297 VectorScale( axis_[2], -0.67f, axis_[2] );
6298 VectorAdd( axis_[0], axis_[2], axis_[0] );
6299 //VectorNormalize( axis_[0] );
6300 VectorMA( org_, 8, axis_[0], org_ );
6301 break;
6302 }
6303 break;
6304 case SABER_TRIDENT:
6305 switch ( bladeNum )
6306 {
6307 case 0:
6308 VectorMA( org_, 24, axis_[0], org_ );
6309 break;
6310 case 1:
6311 VectorMA( org_, -6, axis_[1], org_ );
6312 VectorMA( org_, 24, axis_[0], org_ );
6313 break;
6314 case 2:
6315 VectorMA( org_, 6, axis_[1], org_ );
6316 VectorMA( org_, 24, axis_[0], org_ );
6317 break;
6318 case 3:
6319 VectorMA( org_, -32, axis_[0], org_ );
6320 VectorScale( axis_[0], -1, axis_[0] );
6321 break;
6322 }
6323 break;
6324 case SABER_SITH_SWORD:
6325 //no blade
6326 break;
6327 default:
6328 break;
6329 }
6330 }
6331 //====FIXMEFIXMEFIXMEFIXMEFIXME========================================================
6332
6333 //store where saber is this frame
6334 VectorCopy( org_, cent->gent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint );
6335 VectorCopy( axis_[0], cent->gent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir );
6336 if ( saberNum == 0 && bladeNum == 0 )
6337 {
6338 VectorCopy( org_, cent->gent->client->renderInfo.muzzlePoint );
6339 VectorCopy( axis_[0], cent->gent->client->renderInfo.muzzleDir );
6340 cent->gent->client->renderInfo.mPCalcTime = cg.time;
6341 }
6342 //length for purposes of rendering and marks trace will be longer than blade so we don't damage past a wall's surface
6343 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length < cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax )
6344 {
6345 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length < cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax - 8 )
6346 {
6347 length = cent->gent->client->ps.saber[saberNum].blade[bladeNum].length + 8;
6348 }
6349 else
6350 {
6351 length = cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax;
6352 }
6353 }
6354 else
6355 {
6356 length = cent->gent->client->ps.saber[saberNum].blade[bladeNum].length;
6357 }
6358 VectorMA( org_, length, axis_[0], end );
6359
6360 // Now store where the saber will be after next frame.
6361 //VectorCopy(org_future, cent->gent->client->renderInfo.muzzlePointNext);
6362 //VectorCopy(axis_future[0], cent->gent->client->renderInfo.muzzleDirNext);
6363
6364 VectorAdd( end, axis_[0], end );
6365
6366 // If the saber is in flight we shouldn't trace from the player to the muzzle point
6367 if ( cent->gent->client->ps.saberInFlight && saberNum == 0 )
6368 {
6369 trace.fraction = 1.0f;
6370 }
6371 else
6372 {
6373 vec3_t rootOrigin;
6374 if (cent->gent->rootBone>=0 && cent->gent->ghoul2.IsValid() && cent->gent->ghoul2[0].animModelIndexOffset)//If it has an animOffset it's a cinematic anim
6375 { //i might be running out of my bounding box, so get my root origin
6376 mdxaBone_t boltMatrix;
6377 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->rootBone,
6378 &boltMatrix, angles, cent->lerpOrigin,
6379 cg.time, cgs.model_draw, cent->currentState.modelScale);
6380 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, rootOrigin );
6381 }
6382 else
6383 {
6384 VectorCopy( cent->lerpOrigin, rootOrigin );
6385 }
6386 gi.trace( &trace, rootOrigin, NULL, NULL, cent->gent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, cent->currentState.number, CONTENTS_SOLID, (EG2_Collision)0, 0 );
6387 }
6388
6389 if ( trace.fraction < 1.0f )
6390 {
6391 // Saber is on the other side of a wall
6392 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length = 0.1f;
6393 cent->gent->client->ps.saberEventFlags &= ~SEF_INWATER;
6394 }
6395 else
6396 {
6397 extern vmCvar_t cg_saberEntMarks;
6398 int traceMask = MASK_SOLID;
6399 qboolean noMarks = qfalse;
6400
6401 if ( (!WP_SaberBladeUseSecondBladeStyle( ¢->gent->client->ps.saber[saberNum], bladeNum )
6402 && (cent->gent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
6403 || ( WP_SaberBladeUseSecondBladeStyle( ¢->gent->client->ps.saber[saberNum], bladeNum )
6404 && (cent->gent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
6405 )
6406 {//do no effects when idle
6407 if ( !cent->gent->client->ps.saberInFlight
6408 && !PM_SaberInAttack( cent->gent->client->ps.saberMove )
6409 && !PM_SaberInTransitionAny( cent->gent->client->ps.saberMove )
6410 && !PM_SaberInSpecialAttack( cent->gent->client->ps.torsoAnim ) )
6411 {//idle, do no marks
6412 noMarks = qtrue;
6413 }
6414 }
6415 if ( cg_saberEntMarks.integer )
6416 {
6417 if ( cent->gent->client->ps.saberInFlight
6418 || PM_SaberInAttack( cent->gent->client->ps.saberMove )
6419 //|| PM_SaberInTransitionAny( cent->gent->client->ps.saberMove )
6420 || PM_SaberInSpecialAttack( cent->gent->client->ps.torsoAnim ) )
6421 {
6422 traceMask |= (CONTENTS_BODY|CONTENTS_CORPSE);
6423 }
6424 }
6425
6426 for ( int i = 0; i < 1; i++ )//was 2 because it would go through architecture and leave saber trails on either side of the brush - but still looks bad if we hit a corner, blade is still 8 longer than hit
6427 {
6428 if ( i )
6429 {//tracing from end to base
6430 gi.trace( &trace, end, NULL, NULL, org_, cent->currentState.clientNum, traceMask, (EG2_Collision)0, 0 );
6431 }
6432 else
6433 {//tracing from base to end
6434 gi.trace( &trace, org_, NULL, NULL, end, cent->currentState.clientNum, traceMask|CONTENTS_WATER|CONTENTS_SLIME, (EG2_Collision)0, 0 );
6435 }
6436
6437 if ( trace.fraction < 1.0f )
6438 {
6439 if ( (trace.contents&CONTENTS_WATER) || (trace.contents&CONTENTS_SLIME) )
6440 {
6441 if ( !noMarks )
6442 {
6443 /*
6444 if ( !(cent->gent->client->ps.saberEventFlags&SEF_INWATER) )
6445 {
6446 }
6447 */
6448 if ( !Q_irand( 0, 10 ) )
6449 {//FIXME: don't do this this way.... :)
6450 vec3_t spot;
6451 VectorCopy( trace.endpos, spot );
6452 spot[2] += 4;
6453 if ( Q_irand( 1, client->ps.saber[saberNum].numBlades ) == 1 )
6454 {
6455 theFxScheduler.PlayEffect( "saber/boil", spot );
6456 cgi_S_StartSound ( spot, -1, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/saber/hitwater.wav" ) );
6457 }
6458 }
6459 //cent->gent->client->ps.saberEventFlags |= SEF_INWATER;
6460 //don't do other trace
6461 }
6462 i = 1;
6463 }
6464 else
6465 {
6466 if ( !noMarks )
6467 {
6468 if ( ( !WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && !(client->ps.saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS) )
6469 || ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && !(client->ps.saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS2) ) )
6470 {
6471 if ( !(trace.surfaceFlags & SURF_NOIMPACT) // never spark on sky
6472 && (trace.entityNum == ENTITYNUM_WORLD || cg_entities[trace.entityNum].currentState.solid == SOLID_BMODEL)
6473 && Q_irand( 1, client->ps.saber[saberNum].numBlades ) == 1 )
6474 {
6475 //was "sparks/spark"
6476 theFxScheduler.PlayEffect( "sparks/spark_nosnd", trace.endpos, trace.plane.normal );
6477 }
6478 }
6479 // All I need is a bool to mark whether I have a previous point to work with.
6480 //....come up with something better..
6481 if ( client->ps.saber[saberNum].blade[bladeNum].trail.haveOldPos[i] )
6482 {
6483 if ( trace.entityNum == ENTITYNUM_WORLD || (cg_entities[trace.entityNum].currentState.eFlags & EF_PERMANENT) || cg_entities[trace.entityNum].currentState.eType == ET_TERRAIN )
6484 {//only put marks on architecture
6485 if ( (!WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && !(client->ps.saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS))
6486 || (WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && !(client->ps.saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS2)) )
6487 {
6488 // Let's do some cool burn/glowing mark bits!!!
6489 CG_CreateSaberMarks( client->ps.saber[saberNum].blade[bladeNum].trail.oldPos[i], trace.endpos, trace.plane.normal );
6490
6491 if ( Q_irand( 1, client->ps.saber[saberNum].numBlades ) == 1 )
6492 {
6493 //make a sound
6494 if ( cg.time - cent->gent->client->ps.saberHitWallSoundDebounceTime >= 100 )
6495 {//ugh, need to have a real sound debouncer... or do this game-side
6496 cent->gent->client->ps.saberHitWallSoundDebounceTime = cg.time;
6497 cgi_S_StartSound ( cent->lerpOrigin, cent->currentState.clientNum, CHAN_ITEM, cgi_S_RegisterSound( va ( "sound/weapons/saber/saberhitwall%d.wav", Q_irand( 1, 3 ) ) ) );
6498 }
6499 }
6500 }
6501 }
6502 else if ( !i )
6503 {//can put marks on G2 clients (but only on base to tip trace)
6504 gentity_t *hitEnt = &g_entities[trace.entityNum];
6505 vec3_t uaxis, splashBackDir;
6506 VectorSubtract(client->ps.saber[saberNum].blade[bladeNum].trail.oldPos[i], trace.endpos, uaxis);
6507 VectorScale( axis_[0], -1, splashBackDir );
6508 //FIXME: if not hitting the first model on the enemy, don't do this!
6509 CG_SaberDoWeaponHitMarks( client, (scent!=NULL?scent->gent:NULL), hitEnt, saberNum, bladeNum, trace.endpos, axis_[0], uaxis, splashBackDir, 0.25f );
6510 }
6511 }
6512 else
6513 {
6514 // if we impact next frame, we'll mark a slash mark
6515 client->ps.saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qtrue;
6516 }
6517 }
6518 }
6519
6520 // stash point so we can connect-the-dots later
6521 VectorCopy( trace.endpos, client->ps.saber[saberNum].blade[bladeNum].trail.oldPos[i] );
6522 VectorCopy( trace.plane.normal, client->ps.saber[saberNum].blade[bladeNum].trail.oldNormal[i] );
6523
6524 if ( !i && trace.contents&(CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_SHOTCLIP) )
6525 { //Now that we don't let the blade go through walls, we need to shorten the blade when it hits one
6526 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length = cent->gent->client->ps.saber[saberNum].blade[bladeNum].length * trace.fraction;//this will stop damage from going through walls
6527 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length <= 0.1f )
6528 {//SIGH... hack so it doesn't play the saber turn-on sound that plays when you first turn the saber on (assumed when saber is active but length is zero)
6529 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length = 0.1f;//FIXME: may go through walls still??
6530 }
6531 //FIXME: should probably re-extend instantly, not use the "turning-on" growth rate
6532 }
6533 }
6534 else
6535 {
6536 cent->gent->client->ps.saberEventFlags &= ~SEF_INWATER;
6537 if ( client->ps.saber[saberNum].blade[bladeNum].trail.haveOldPos[i] )
6538 {
6539 if ( !noMarks )
6540 {
6541 if ( (!WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && !(client->ps.saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS))
6542 || (WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && !(client->ps.saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS2)) )
6543 {
6544 // Hmmm, no impact this frame, but we have an old point
6545 // Let's put the mark there, we should use an endcap mark to close the line, but we
6546 // can probably just get away with a round mark
6547 //CG_ImpactMark( cgs.media.rivetMarkShader, client->ps.saber[saberNum].blade[bladeNum].trail.oldPos[i], client->ps.saber[saberNum].blade[bladeNum].trail.oldNormal[i],
6548 // 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse );
6549 }
6550 }
6551 }
6552
6553 // we aren't impacting, so turn off our mark tracking mechanism
6554 client->ps.saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qfalse;
6555 }
6556 }
6557 }
6558
6559 // Added 10/02/02 by Aurelio Reis.
6560 // If the Blade is not active, leave here; we do not Render it!
6561 /* if ( cent->gent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
6562 {//draws no blade or trail
6563 //FIXME: draw some sort of energy halo and motion trail!
6564 return;
6565 }
6566 */
6567 if ( !client->ps.saber[saberNum].blade[bladeNum].active && client->ps.saber[saberNum].blade[bladeNum].length <= 0 )
6568 {
6569 return;
6570 }
6571
6572 if ( (!WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && client->ps.saber[saberNum].trailStyle < 2 )
6573 || ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && client->ps.saber[saberNum].trailStyle2 < 2 ) )
6574 {//okay to draw the trail
6575 saberTrail_t *saberTrail = &client->ps.saber[saberNum].blade[bladeNum].trail;
6576
6577 #define SABER_TRAIL_TIME 40.0f
6578
6579 // if we happen to be timescaled or running in a high framerate situation, we don't want to flood
6580 // the system with very small trail slices...but perhaps doing it by distance would yield better results?
6581 if ( saberTrail->lastTime > cg.time )
6582 {//after a pause, cg.time jumps ahead in time for one frame
6583 //and lastTime gets set to that and will freak out, so, since
6584 //it's never valid for saberTrail->lastTime to be > cg.time,
6585 //cap it to cg.time here
6586 saberTrail->lastTime = cg.time;
6587 }
6588 if ( cg.time > saberTrail->lastTime + 2 && saberTrail->inAction ) // 2ms
6589 {
6590 if ( saberTrail->inAction && cg.time < saberTrail->lastTime + 300 ) // if we have a stale segment, don't draw until we have a fresh one
6591 {
6592 vec3_t rgb1={255,255,255};
6593
6594 if ( cent->gent->client->ps.saber[saberNum].type != SABER_SITH_SWORD
6595 && ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) || client->ps.saber[saberNum].trailStyle != 1 )
6596 && ( !WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) || client->ps.saber[saberNum].trailStyle2 != 1 )
6597 )
6598 {
6599 switch( client->ps.saber[saberNum].blade[bladeNum].color )
6600 {
6601 case SABER_RED:
6602 VectorSet( rgb1, 255.0f, 0.0f, 0.0f );
6603 break;
6604 case SABER_ORANGE:
6605 VectorSet( rgb1, 255.0f, 64.0f, 0.0f );
6606 break;
6607 case SABER_YELLOW:
6608 VectorSet( rgb1, 255.0f, 255.0f, 0.0f );
6609 break;
6610 case SABER_GREEN:
6611 VectorSet( rgb1, 0.0f, 255.0f, 0.0f );
6612 break;
6613 case SABER_BLUE:
6614 VectorSet( rgb1, 0.0f, 64.0f, 255.0f );
6615 break;
6616 case SABER_PURPLE:
6617 VectorSet( rgb1, 220.0f, 0.0f, 255.0f );
6618 break;
6619 }
6620 }
6621
6622 float diff = cg.time - saberTrail->lastTime;
6623
6624 // I'm not sure that clipping this is really the best idea
6625 if ( diff <= SABER_TRAIL_TIME * 2 )
6626 {
6627 // build a quad
6628 CTrail *fx = new CTrail;
6629
6630 float duration;
6631
6632 if ( cent->gent->client->ps.saber[saberNum].type == SABER_SITH_SWORD
6633 || (!WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && client->ps.saber[saberNum].trailStyle == 1 )
6634 || ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && client->ps.saber[saberNum].trailStyle2 == 1 )
6635 )
6636 {
6637 fx->mShader = cgs.media.swordTrailShader;
6638 duration = saberTrail->duration/2.0f; // stay around twice as long
6639 VectorSet( rgb1, 32.0f, 32.0f, 32.0f ); // make the sith sword trail pretty faint
6640 }
6641 else
6642 {
6643 fx->mShader = cgs.media.saberBlurShader;
6644 duration = saberTrail->duration/5.0f;
6645 }
6646
6647 float oldAlpha = 1.0f - ( diff / duration );
6648
6649 // Go from new muzzle to new end...then to old end...back down to old muzzle...finally
6650 // connect back to the new muzzle...this is our trail quad
6651 VectorCopy( org_, fx->mVerts[0].origin );
6652 VectorMA( end, 3.0f, axis_[0], fx->mVerts[1].origin );
6653
6654 VectorCopy( saberTrail->tip, fx->mVerts[2].origin );
6655 VectorCopy( saberTrail->base, fx->mVerts[3].origin );
6656
6657 // New muzzle
6658 VectorCopy( rgb1, fx->mVerts[0].rgb );
6659 fx->mVerts[0].alpha = 255.0f;
6660
6661 fx->mVerts[0].ST[0] = 0.0f;
6662 fx->mVerts[0].ST[1] = 0.99f;
6663 fx->mVerts[0].destST[0] = 0.99f;
6664 fx->mVerts[0].destST[1] = 0.99f;
6665
6666 // new tip
6667 VectorCopy( rgb1, fx->mVerts[1].rgb );
6668 fx->mVerts[1].alpha = 255.0f;
6669
6670 fx->mVerts[1].ST[0] = 0.0f;
6671 fx->mVerts[1].ST[1] = 0.0f;
6672 fx->mVerts[1].destST[0] = 0.99f;
6673 fx->mVerts[1].destST[1] = 0.0f;
6674
6675 // old tip
6676 VectorCopy( rgb1, fx->mVerts[2].rgb );
6677 fx->mVerts[2].alpha = 255.0f;
6678
6679 fx->mVerts[2].ST[0] = 0.99f - oldAlpha; // NOTE: this just happens to contain the value I want
6680 fx->mVerts[2].ST[1] = 0.0f;
6681 fx->mVerts[2].destST[0] = 0.99f + fx->mVerts[2].ST[0];
6682 fx->mVerts[2].destST[1] = 0.0f;
6683
6684 // old muzzle
6685 VectorCopy( rgb1, fx->mVerts[3].rgb );
6686 fx->mVerts[3].alpha = 255.0f;
6687
6688 fx->mVerts[3].ST[0] = 0.99f - oldAlpha; // NOTE: this just happens to contain the value I want
6689 fx->mVerts[3].ST[1] = 0.99f;
6690 fx->mVerts[3].destST[0] = 0.99f + fx->mVerts[2].ST[0];
6691 fx->mVerts[3].destST[1] = 0.99f;
6692
6693 // fx->SetFlags( FX_USE_ALPHA );
6694 FX_AddPrimitive( (CEffect**)&fx, duration );//SABER_TRAIL_TIME );
6695 }
6696 }
6697
6698 // we must always do this, even if we aren't active..otherwise we won't know where to pick up from
6699 VectorCopy( org_, saberTrail->base );
6700 VectorMA( end, 3.0f, axis_[0], saberTrail->tip );
6701 saberTrail->lastTime = cg.time;
6702 }
6703 }
6704
6705 if ( cent->gent->client->ps.saber[saberNum].type == SABER_SITH_SWORD)
6706 {
6707 // don't need to do nuthin else
6708 return;
6709 }
6710
6711 qboolean noDlight = qfalse;
6712 if ( client->ps.saber[saberNum].numBlades >= 3
6713 || (!WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && (client->ps.saber[saberNum].saberFlags2&SFL2_NO_DLIGHT) )
6714 || ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && (client->ps.saber[saberNum].saberFlags2&SFL2_NO_DLIGHT2) )
6715 )
6716 {
6717 noDlight = qtrue;
6718 }
6719
6720 if ( (!WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && (client->ps.saber[saberNum].saberFlags2&SFL2_NO_BLADE) )
6721 || ( WP_SaberBladeUseSecondBladeStyle( &client->ps.saber[saberNum], bladeNum ) && (client->ps.saber[saberNum].saberFlags2&SFL2_NO_BLADE2) ) )
6722 {//don't draw a blade
6723 if ( !noDlight )
6724 {//but still do dlight
6725 CG_DoSaberLight( &client->ps.saber[saberNum] );
6726 }
6727 return;
6728 }
6729 // Pass in the renderfx flags attached to the saber weapon model...this is done so that saber glows
6730 // will get rendered properly in a mirror...not sure if this is necessary??
6731 CG_DoSaber(
6732 org_, axis_[0], length,
6733 client->ps.saber[saberNum].blade[bladeNum].lengthMax,
6734 client->ps.saber[saberNum].blade[bladeNum].radius,
6735 client->ps.saber[saberNum].blade[bladeNum].color,
6736 renderfx, (qboolean)!noDlight );
6737 }
6738
CG_AddSaberBlade(centity_t * cent,centity_t * scent,refEntity_t * saber,int renderfx,int modelIndex,vec3_t origin,vec3_t angles)6739 void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, vec3_t origin, vec3_t angles )
6740 {
6741 //FIXME: if this is a dropped saber, it could be possible that it's the second saber?
6742 if ( cent->gent->client )
6743 {
6744 for ( int i = 0; i < cent->gent->client->ps.saber[0].numBlades;i++ )
6745 {
6746 CG_AddSaberBladeGo( cent, scent, saber, renderfx, modelIndex, origin, angles, 0, i );
6747 }
6748 if ( cent->gent->client->ps.saber[0].numBlades > 2 )
6749 {// add blended light
6750 CG_DoSaberLight( ¢->gent->client->ps.saber[0] );
6751 }
6752 }
6753 }
6754
6755 /*
6756 static void CG_AddSaberBlades( centity_t *cent, int renderfx, vec3_t origin, vec3_t angles, int saberNum )
6757 {
6758 if ( cent->gent->client )
6759 {
6760 for ( int i = 0; i < cent->gent->client->ps.saber[saberNum].numBlades; i++ )
6761 {
6762 CG_AddSaberBladeGo( cent, cent, NULL, renderfx, cent->gent->weaponModel[saberNum], origin, angles, saberNum, i );
6763 }
6764 }
6765 }
6766 */
6767 //--------------- END SABER STUFF --------
6768
6769 /*
6770 ===============
6771 CG_Player
6772
6773 FIXME: Extend this to be a render func for all multiobject entities in the game such that:
6774
6775 You can have and stack multiple animated pieces (not just legs and torso)
6776 You can attach "bolt-ons" that either animate or don't (weapons, heads, borg pieces)
6777 You can attach any object to any tag on any object (weapon on the head, etc.)
6778
6779 Basically, keep a list of objects:
6780 Root object (in this case, the legs) with this info:
6781 model
6782 skin
6783 effects
6784 scale
6785 if animated, frame or anim number
6786 drawn at origin and angle of master entity
6787 Animated objects, with this info:
6788 model
6789 skin
6790 effects
6791 scale
6792 frame or anim number
6793 object it's attached to
6794 tag to attach it's tag_parent to
6795 angle offset to attach it with
6796 Non-animated objects, with this info:
6797 model
6798 skin
6799 effects
6800 scale
6801 object it's attached to
6802 tag to attach it's tag_parent to
6803 angle offset to attach it with
6804
6805 ALSO:
6806 Move the auto angle setting back up to the game
6807 Implement 3-axis scaling
6808 Implement alpha
6809 Implement tint
6810 Implement other effects (generic call effect at org and dir (or tag), with parms?)
6811
6812 ===============
6813 */
6814 extern qboolean G_GetRootSurfNameWithVariant( gentity_t *ent, const char *rootSurfName, char *returnSurfName, int returnSize );
6815 extern qboolean G_ControlledByPlayer( gentity_t *self );
6816 extern qboolean G_RagDoll(gentity_t *ent, vec3_t forcedAngles);
6817 int cg_saberOnSoundTime[MAX_GENTITIES] = {0};
6818
CG_Player(centity_t * cent)6819 void CG_Player( centity_t *cent ) {
6820 clientInfo_t *ci;
6821 qboolean shadow, staticScale = qfalse;
6822 float shadowPlane;
6823 const weaponData_t *wData = NULL;
6824
6825 if ( cent->currentState.eFlags & EF_NODRAW )
6826 {
6827 return;
6828 }
6829
6830 //make sure this thing has a gent and client
6831 if(!cent->gent)
6832 {
6833 return;
6834 }
6835
6836 if(!cent->gent->client)
6837 {
6838 return;
6839 }
6840
6841 if( cent->gent->s.number == 0 && cg.weaponSelect == WP_NONE && cg.zoomMode == 1 )
6842 {
6843 // HACK
6844 return;
6845 }
6846
6847 calcedMp = qfalse;
6848
6849 //Get the player's light level for stealth calculations
6850 CG_GetPlayerLightLevel( cent );
6851
6852 if ((in_camera) && cent->currentState.clientNum == 0 ) // If player in camera then no need for shadow
6853 {
6854 return;
6855 }
6856
6857 if(cent->currentState.number == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
6858 {
6859 calcedMp = qtrue;
6860 }
6861
6862 ci = ¢->gent->client->clientInfo;
6863
6864 if ( !ci->infoValid )
6865 {
6866 return;
6867 }
6868
6869 G_RagDoll(cent->gent, cent->lerpAngles);
6870
6871 if ( cent->currentState.weapon )
6872 {
6873 wData = &weaponData[cent->currentState.weapon];
6874 }
6875 /*
6876 Ghoul2 Insert Start
6877 */
6878 if (cent->gent->ghoul2.size()) //do we have ghoul models attached?
6879 {
6880 refEntity_t ent;
6881 vec3_t tempAngles;
6882 memset (&ent, 0, sizeof(ent));
6883
6884 //FIXME: if at all possible, do all our sets before our gets to do only *1* G2 skeleton transform per render frame
6885 CG_SetGhoul2Info(&ent, cent);
6886
6887 // Weapon sounds may need to be stopped, so check now
6888 CG_StopWeaponSounds( cent );
6889
6890 // add powerups floating behind the player
6891 CG_PlayerPowerups( cent );
6892
6893 // add the shadow
6894 //FIXME: funcs that modify our origin below will cause the shadow to be in the wrong spot
6895 shadow = CG_PlayerShadow( cent, &shadowPlane );
6896
6897 // add a water splash if partially in and out of water
6898 CG_PlayerSplash( cent );
6899
6900 // get the player model information
6901 ent.renderfx = 0;
6902 if ( !cg.renderingThirdPerson || cg.zoomMode )
6903 {//in first person or zoomed in
6904 if ( cg.snap->ps.viewEntity <= 0 || cg.snap->ps.viewEntity >= ENTITYNUM_WORLD)
6905 {//no viewentity
6906 if ( cent->currentState.number == cg.snap->ps.clientNum )
6907 {//I am the player
6908 if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
6909 {//not using saber or fists
6910 ent.renderfx = RF_THIRD_PERSON; // only draw in mirrors
6911 }
6912 }
6913 }
6914 else if ( cent->currentState.number == cg.snap->ps.viewEntity )
6915 {//I am the view entity
6916 if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
6917 {//not using first person saber test or, if so, not using saber
6918 ent.renderfx = RF_THIRD_PERSON; // only draw in mirrors
6919 }
6920 }
6921 }
6922
6923 if ( cent->gent->client->ps.powerups[PW_DISINT_2] > cg.time )
6924 {//ghost!
6925 ent.renderfx = RF_THIRD_PERSON; // only draw in mirrors
6926 }
6927 else if (cg_shadows.integer == 2 && (ent.renderfx & RF_THIRD_PERSON))
6928 { //show stencil shadow in first person now because we can -rww
6929 ent.renderfx |= RF_SHADOW_ONLY;
6930 }
6931
6932 if ( (cg_shadows.integer == 2 && !in_camera) || (cg_shadows.integer == 3 && shadow) )
6933 {
6934 ent.renderfx |= RF_SHADOW_PLANE;
6935 }
6936 ent.shadowPlane = shadowPlane;
6937 ent.renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
6938 if ( cent->gent->NPC && cent->gent->NPC->scriptFlags & SCF_MORELIGHT )
6939 {
6940 ent.renderfx |= RF_MORELIGHT; //bigger than normal min light
6941 }
6942
6943 CG_RegisterWeapon( cent->currentState.weapon );
6944
6945 //---------------
6946 Vehicle_t *pVeh;
6947
6948 if ( cent->currentState.eFlags & EF_LOCKED_TO_WEAPON && cent->gent && cent->gent->health > 0 && cent->gent->owner )
6949 {
6950 centity_t *chair = &cg_entities[cent->gent->owner->s.number];
6951 if ( chair && chair->gent )
6952 {
6953 vec3_t temp;
6954 mdxaBone_t boltMatrix;
6955
6956 //NOTE: call this so it updates on the server and client
6957 if ( chair->gent->bounceCount )
6958 {//EWeb
6959 // We'll set the turret angles directly
6960 VectorClear( temp );
6961 VectorClear( chair->gent->pos1 );
6962
6963 temp[PITCH] = cent->lerpAngles[PITCH];
6964 chair->gent->pos1[YAW] = AngleSubtract( cent->lerpAngles[YAW], chair->gent->s.angles[YAW] );//remember which dir our turret is facing for later
6965 cent->lerpAngles[ROLL] = 0;
6966
6967 BG_G2SetBoneAngles( chair, chair->gent, chair->gent->lowerLumbarBone, chair->gent->pos1, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, cgs.model_draw );
6968 BG_G2SetBoneAngles( chair, chair->gent, chair->gent->upperLumbarBone, temp, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, cgs.model_draw );
6969 }
6970 else
6971 {
6972 // We'll set the turret yaw directly
6973 VectorClear( chair->gent->s.apos.trBase );
6974 VectorClear( temp );
6975
6976 chair->gent->s.apos.trBase[YAW] = cent->lerpAngles[YAW];
6977 temp[PITCH] = -cent->lerpAngles[PITCH];
6978 cent->lerpAngles[ROLL] = 0;
6979 BG_G2SetBoneAngles( chair, chair->gent, chair->gent->lowerLumbarBone, temp, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, cgs.model_draw );
6980 }
6981 //gi.G2API_SetBoneAngles( &chair->gent->ghoul2[0], "swivel_bone", temp, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, cgs.model_draw );
6982 VectorCopy( temp, chair->gent->lastAngles );
6983
6984 gi.G2API_StopBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->hipsBone );
6985
6986 // Getting the seat bolt here
6987 gi.G2API_GetBoltMatrix( chair->gent->ghoul2, chair->gent->playerModel, chair->gent->headBolt,
6988 &boltMatrix, chair->gent->s.apos.trBase, chair->gent->currentOrigin, cg.time,
6989 cgs.model_draw, chair->currentState.modelScale );
6990
6991 if ( chair->gent->bounceCount )
6992 {//put behind it, not in chair
6993 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent.origin );
6994 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, chair->gent->pos3 );
6995 chair->gent->pos3[2] = 0;
6996 VectorNormalizeFast( chair->gent->pos3 );
6997 VectorMA( ent.origin, -44.0f, chair->gent->pos3, ent.origin );
6998 ent.origin[2] = cent->lerpOrigin[2];
6999 cent->lerpAngles[YAW] = vectoyaw( chair->gent->pos3 );
7000 cent->lerpAngles[ROLL] = 0;
7001 CG_G2PlayerAngles( cent, ent.axis, tempAngles);
7002 calcedMp = qtrue;
7003 }
7004 else
7005 {//sitting in it
7006 // Storing ent position, bolt position, and bolt axis
7007 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent.origin );
7008 VectorCopy( ent.origin, chair->gent->pos2 );
7009 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, chair->gent->pos3 );
7010 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, chair->gent->pos4 );
7011
7012 AnglesToAxis( cent->lerpAngles, ent.axis );
7013 VectorCopy( cent->lerpAngles, tempAngles);//tempAngles is needed a lot below
7014 }
7015
7016 VectorCopy( ent.origin, ent.oldorigin );
7017 VectorCopy( ent.origin, ent.lightingOrigin );
7018
7019 // FIXME: Mike claims that hacking the eyepoint will make them shoot at me.......so,
7020 // we move up from the seat bolt and store off that point.
7021 // VectorMA( ent.origin, -20, chair->gent->pos3, cent->gent->client->renderInfo.eyePoint );
7022 // VectorMA( cent->gent->client->renderInfo.eyePoint, 40, chair->gent->pos4, cent->gent->client->renderInfo.eyePoint );
7023 }
7024 }
7025 else if ( ( pVeh = G_IsRidingVehicle( cent->gent ) ) != NULL )
7026 {//rider
7027 CG_G2PlayerAngles( cent, ent.axis, tempAngles);
7028 //Deal with facial expressions
7029 CG_G2PlayerHeadAnims( cent );
7030
7031 centity_t *vehEnt = &cg_entities[cent->gent->owner->s.number];
7032 CG_CalcEntityLerpPositions( vehEnt );
7033 // Get the driver tag.
7034 mdxaBone_t boltMatrix;
7035 gi.G2API_GetBoltMatrix( vehEnt->gent->ghoul2, vehEnt->gent->playerModel, vehEnt->gent->crotchBolt,
7036 &boltMatrix, vehEnt->lerpAngles, vehEnt->lerpOrigin, (cg.time?cg.time:level.time), NULL, vehEnt->currentState.modelScale );
7037 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent.origin );
7038
7039 float savPitch = cent->lerpAngles[PITCH];
7040 VectorCopy( vehEnt->lerpAngles, cent->lerpAngles );
7041 AnglesToAxis( cent->lerpAngles, ent.axis );
7042
7043 VectorCopy( ent.origin, ent.oldorigin );
7044 VectorCopy( ent.origin, ent.lightingOrigin );
7045
7046 VectorCopy( cent->lerpAngles, tempAngles );//tempAngles is needed a lot below
7047 VectorCopy( ent.origin, cent->lerpOrigin );
7048 VectorCopy( ent.origin, cent->gent->client->ps.origin );
7049 //bah, keep our pitch!
7050 cent->lerpAngles[PITCH] = savPitch;
7051 }
7052 else if ( ( (cent->gent->client->ps.eFlags&EF_HELD_BY_RANCOR)||(cent->gent->client->ps.eFlags&EF_HELD_BY_WAMPA) )
7053 && cent->gent && cent->gent->activator )
7054 {
7055 centity_t *monster = &cg_entities[cent->gent->activator->s.number];
7056 if ( monster && monster->gent && monster->gent->inuse && monster->gent->health > 0 )
7057 {
7058 mdxaBone_t boltMatrix;
7059 // Getting the bolt here
7060 //in mouth
7061 int boltIndex = monster->gent->gutBolt;
7062 if ( monster->gent->count == 1 )
7063 {//in hand
7064 boltIndex = monster->gent->handRBolt;
7065 }
7066 vec3_t rancAngles = {0};
7067 rancAngles[YAW] = monster->lerpAngles[YAW];
7068 gi.G2API_GetBoltMatrix( monster->gent->ghoul2, monster->gent->playerModel, boltIndex,
7069 &boltMatrix, rancAngles, monster->lerpOrigin, cg.time,
7070 cgs.model_draw, monster->currentState.modelScale );
7071 // Storing ent position, bolt position, and bolt axis
7072 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent.origin );
7073 if ( (cent->gent->client->ps.eFlags&EF_HELD_BY_WAMPA) )
7074 {
7075 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent.axis[0] );
7076 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_X, ent.axis[1] );
7077 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Z, ent.axis[2] );
7078 }
7079 else if ( monster->gent->count == 1 )
7080 {
7081 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent.axis[0] );
7082 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_X, ent.axis[1] );
7083 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Z, ent.axis[2] );
7084 }
7085 else
7086 {
7087 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Z, ent.axis[0] );
7088 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent.axis[1] );
7089 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, ent.axis[2] );
7090 }
7091 //FIXME: this is messing up our axis and turning us inside-out
7092 if ( cent->gent->client->isRagging )
7093 {//hack, ragdoll has you way at bottom of bounding box
7094 VectorMA( ent.origin, 32, ent.axis[2], ent.origin );
7095 }
7096 VectorCopy( ent.origin, ent.oldorigin );
7097 VectorCopy( ent.origin, ent.lightingOrigin );
7098
7099 vectoangles( ent.axis[0], cent->lerpAngles );
7100 vec3_t temp;
7101 vectoangles( ent.axis[2], temp );
7102 cent->lerpAngles[ROLL] = -temp[PITCH];
7103
7104 VectorCopy( cent->lerpAngles, tempAngles );//tempAngles is needed a lot below
7105 VectorCopy( ent.origin, cent->lerpOrigin );
7106 VectorCopy( ent.origin, cent->gent->client->ps.origin );
7107 // if ( (cent->gent->client->ps.eFlags&EF_HELD_BY_WAMPA) )
7108 // {
7109 vectoangles( ent.axis[0], cent->lerpAngles );
7110 VectorCopy( cent->lerpAngles, tempAngles );//tempAngles is needed a lot below
7111 // }
7112 // else
7113 // {
7114 // //cent->gent->client->ps.viewangles[YAW] = cent->lerpAngles[YAW];
7115 // }
7116 }
7117 else
7118 {//wtf happened to the guy holding me? Better get out
7119 cent->gent->activator = NULL;
7120 cent->gent->client->ps.eFlags &= ~(EF_HELD_BY_WAMPA|EF_HELD_BY_RANCOR);
7121 }
7122 }
7123 else if ( (cent->gent->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
7124 && cent->gent
7125 && cent->gent->activator )
7126 {
7127 centity_t *sand_creature = &cg_entities[cent->gent->activator->s.number];
7128 if ( sand_creature && sand_creature->gent )
7129 {
7130 mdxaBone_t boltMatrix;
7131 // Getting the bolt here
7132 //in hand
7133 vec3_t scAngles = {0};
7134 scAngles[YAW] = sand_creature->lerpAngles[YAW];
7135 gi.G2API_GetBoltMatrix( sand_creature->gent->ghoul2, sand_creature->gent->playerModel, sand_creature->gent->gutBolt,
7136 &boltMatrix, scAngles, sand_creature->lerpOrigin, cg.time,
7137 cgs.model_draw, sand_creature->currentState.modelScale );
7138 // Storing ent position, bolt position, and bolt axis
7139 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent.origin );
7140 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent.axis[0] );
7141 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, ent.axis[1] );
7142 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Z, ent.axis[2] );
7143 //FIXME: this is messing up our axis and turning us inside-out
7144 if ( cent->gent->client->isRagging )
7145 {//hack, ragdoll has you way at bottom of bounding box
7146 VectorMA( ent.origin, 32, ent.axis[2], ent.origin );
7147 }
7148 VectorCopy( ent.origin, ent.oldorigin );
7149 VectorCopy( ent.origin, ent.lightingOrigin );
7150
7151 vectoangles( ent.axis[0], cent->lerpAngles );
7152 vec3_t temp;
7153 vectoangles( ent.axis[2], temp );
7154 cent->lerpAngles[ROLL] = -temp[PITCH];
7155
7156 VectorCopy( cent->lerpAngles, tempAngles );//tempAngles is needed a lot below
7157 VectorCopy( ent.origin, cent->lerpOrigin );
7158 VectorCopy( ent.origin, cent->gent->client->ps.origin );
7159 cent->gent->client->ps.viewangles[YAW] = cent->lerpAngles[YAW];
7160 }
7161 }
7162 else
7163 {
7164 //---------------
7165 CG_G2PlayerAngles( cent, ent.axis, tempAngles);
7166 //Deal with facial expressions
7167 CG_G2PlayerHeadAnims( cent );
7168
7169 /*
7170 if ( cent->gent->client->ps.eFlags & EF_FORCE_DRAINED
7171 && !VectorCompare( cent->gent->client->ps.forceDrainOrg, vec3_origin ) )
7172 {//HACKHACKHACK!!!! being drained
7173 VectorCopy( cent->gent->client->ps.forceDrainOrg, ent.origin);
7174 }
7175 else
7176 */
7177 {
7178 VectorCopy( cent->lerpOrigin, ent.origin);
7179 }
7180
7181 if (ent.modelScale[2] && ent.modelScale[2] != 1.0f)
7182 {
7183 ent.origin[2] += 24 * (ent.modelScale[2] - 1);
7184 }
7185 VectorCopy( ent.origin, ent.oldorigin);
7186 VectorCopy( ent.origin, ent.lightingOrigin );
7187 }
7188
7189 if ( cent->gent && cent->gent->client )
7190 {
7191 cent->gent->client->ps.legsYaw = tempAngles[YAW];
7192 }
7193 ScaleModelAxis(&ent);
7194
7195 //HACK - add swoop model
7196 /*
7197 if ( cent->currentState.vehicleIndex != VEHICLE_NONE
7198 && g_vehicleInfo[cent->currentState.vehicleIndex].type == VH_SPEEDER )
7199 {//add it at my origin
7200 //FIXME: should be a G2 model
7201 refEntity_t swoopEnt;
7202
7203 memset (&swoopEnt, 0, sizeof(swoopEnt));
7204
7205 VectorCopy( cent->lerpOrigin, swoopEnt.origin );
7206 VectorMA( swoopEnt.origin, -32, ent.axis[2], swoopEnt.origin );
7207 VectorCopy( swoopEnt.origin, swoopEnt.oldorigin );
7208 AnglesToAxis( cent->currentState.vehicleAngles, swoopEnt.axis );
7209 swoopEnt.hModel = cgs.model_draw[g_vehicleInfo[cent->currentState.vehicleIndex].modelIndex];
7210 cgi_R_AddRefEntityToScene( &swoopEnt );
7211 }
7212 */
7213 //HACK - add swoop model
7214
7215 extern vmCvar_t cg_thirdPersonAlpha;
7216
7217 if ( (cent->gent->s.number == 0 || G_ControlledByPlayer( cent->gent )) )
7218 {
7219 float alpha = 1.0f;
7220 if ( (cg.overrides.active&CG_OVERRIDE_3RD_PERSON_APH) )
7221 {
7222 alpha = cg.overrides.thirdPersonAlpha;
7223 }
7224 else
7225 {
7226 alpha = cg_thirdPersonAlpha.value;
7227 }
7228
7229 if ( alpha < 1.0f )
7230 {
7231 ent.renderfx |= RF_ALPHA_FADE;
7232 ent.shaderRGBA[3] = (unsigned char)(alpha * 255.0f);
7233 }
7234 }
7235
7236 if ( cg_debugHealthBars.integer )
7237 {
7238 if ( cent->gent && cent->gent->health > 0 && cent->gent->max_health > 0 )
7239 {//draw a health bar over them
7240 CG_AddHealthBarEnt( cent->currentState.clientNum );
7241 }
7242 }
7243 CG_AddRefEntityWithPowerups( &ent, cent->currentState.powerups, cent );
7244 VectorCopy( tempAngles, cent->renderAngles );
7245
7246 //Initialize all these to *some* valid data
7247 VectorCopy( ent.origin, cent->gent->client->renderInfo.headPoint );
7248 VectorCopy( ent.origin, cent->gent->client->renderInfo.handRPoint );
7249 VectorCopy( ent.origin, cent->gent->client->renderInfo.handLPoint );
7250 VectorCopy( ent.origin, cent->gent->client->renderInfo.footRPoint );
7251 VectorCopy( ent.origin, cent->gent->client->renderInfo.footLPoint );
7252 VectorCopy( ent.origin, cent->gent->client->renderInfo.torsoPoint );
7253 VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.torsoAngles );
7254 VectorCopy( ent.origin, cent->gent->client->renderInfo.crotchPoint );
7255 if ( cent->currentState.number != 0
7256 || cg.renderingThirdPerson
7257 || cg.snap->ps.stats[STAT_HEALTH] <= 0
7258 || ( !cg.renderingThirdPerson && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE) )//First person saber
7259 )
7260 {//in some third person mode or NPC
7261 //we don't override thes in pure 1st person because they will be set before this func
7262 VectorCopy( ent.origin, cent->gent->client->renderInfo.eyePoint );
7263 VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.eyeAngles );
7264 if ( !cent->gent->client->ps.saberInFlight )
7265 {
7266 VectorCopy( ent.origin, cent->gent->client->renderInfo.muzzlePoint );
7267 VectorCopy( ent.axis[0], cent->gent->client->renderInfo.muzzleDir );
7268 }
7269 }
7270 //now try to get the right data
7271
7272 mdxaBone_t boltMatrix;
7273 vec3_t tempAxis, G2Angles = {0, tempAngles[YAW], 0};
7274
7275 if ( cent->gent->handRBolt != -1 )
7276 {
7277 //Get handRPoint
7278 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handRBolt,
7279 &boltMatrix, G2Angles, ent.origin, cg.time,
7280 cgs.model_draw, cent->currentState.modelScale );
7281 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.handRPoint );
7282 }
7283 if ( cent->gent->handLBolt != -1 )
7284 {
7285 //always get handLPoint too...?
7286 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->handLBolt,
7287 &boltMatrix, G2Angles, ent.origin, cg.time,
7288 cgs.model_draw, cent->currentState.modelScale );
7289 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.handLPoint );
7290 }
7291 if ( cent->gent->footLBolt != -1 )
7292 {
7293 //get the feet
7294 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footLBolt,
7295 &boltMatrix, G2Angles, ent.origin, cg.time,
7296 cgs.model_draw, cent->currentState.modelScale );
7297 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.footLPoint );
7298 }
7299
7300 if ( cent->gent->footRBolt != -1 )
7301 {
7302 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->footRBolt,
7303 &boltMatrix, G2Angles, ent.origin, cg.time,
7304 cgs.model_draw, cent->currentState.modelScale );
7305 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.footRPoint );
7306 }
7307
7308 //Handle saber
7309 if ( cent->gent
7310 && cent->gent->client
7311 && ( cent->currentState.weapon == WP_SABER || cent->gent->client->ps.saberInFlight )
7312 && cent->gent->client->NPC_class != CLASS_ATST )
7313 {//FIXME: somehow saberactive is getting lost over the network
7314 //loop this and do for both sabers
7315 int numSabers = 1;
7316 if ( cent->gent->client->ps.dualSabers )
7317 {
7318 numSabers = 2;
7319 }
7320 for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
7321 {
7322 if ( cent->gent->client->ps.saberEventFlags&SEF_INWATER )
7323 {
7324 cent->gent->client->ps.saber[saberNum].Deactivate();
7325 }
7326 //loop this and do for both blades
7327 for ( int bladeNum = 0; bladeNum < cent->gent->client->ps.saber[saberNum].numBlades; bladeNum++ )
7328 {
7329 if ( !cent->gent->client->ps.saber[saberNum].blade[bladeNum].active ||
7330 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length > cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax )//hack around network lag for now
7331 {//saber blade is off
7332 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length > 0 )
7333 {
7334 if ( cent->gent->client->ps.stats[STAT_HEALTH] <= 0 )
7335 {//dead, didn't actively turn it off
7336 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length -= cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax/10 * cg.frametime/100;
7337 }
7338 else
7339 {//actively turned it off, shrink faster
7340 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length -= cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax/10 * cg.frametime/100;
7341 }
7342 }
7343 }
7344 else
7345 {//saber blade is on
7346 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length < cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax )
7347 {
7348 if ( !cent->gent->client->ps.saber[saberNum].blade[bladeNum].length )
7349 {
7350 qhandle_t saberOnSound = cgs.sound_precache[g_entities[cent->currentState.clientNum].client->ps.saber[saberNum].soundOn];
7351 if ( !cent->gent->client->ps.weaponTime
7352 && !saberNum//first saber only
7353 && !bladeNum )//first blade only
7354 {//make us play the turn on anim
7355 cent->gent->client->ps.weaponstate = WEAPON_RAISING;
7356 cent->gent->client->ps.weaponTime = 250;
7357 }
7358 if ( cent->gent->client->ps.saberInFlight && saberNum == 0 )
7359 {//play it on the saber
7360 if ( cg_saberOnSoundTime[cent->currentState.number] < cg.time )
7361 {
7362 cgi_S_UpdateEntityPosition( cent->gent->client->ps.saberEntityNum, g_entities[cent->gent->client->ps.saberEntityNum].currentOrigin );
7363 cgi_S_StartSound (NULL, cent->gent->client->ps.saberEntityNum, CHAN_AUTO, saberOnSound );
7364 cg_saberOnSoundTime[cent->currentState.number] = cg.time;//so we don't play multiple on sounds at one time
7365 }
7366 }
7367 else
7368 {
7369 if ( cg_saberOnSoundTime[cent->currentState.number] < cg.time )
7370 {
7371 cgi_S_StartSound (NULL, cent->currentState.number, CHAN_AUTO, saberOnSound );
7372 cg_saberOnSoundTime[cent->currentState.number] = cg.time;//so we don't play multiple on sounds at one time
7373 }
7374 }
7375 }
7376 if ( cg.frametime > 0 )
7377 {
7378 if ( PM_SuperBreakWinAnim( cent->gent->client->ps.torsoAnim ) )
7379 {//just keep it full length!
7380 //NOTE: does this mean it will go through walls now...?
7381 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length = cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax;
7382 }
7383 else
7384 {
7385 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length += cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax/10 * cg.frametime/100;
7386 }
7387 }
7388 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length > cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax )
7389 {
7390 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length = cent->gent->client->ps.saber[saberNum].blade[bladeNum].lengthMax;
7391 }
7392 }
7393 }
7394
7395 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length > 0 )
7396 {
7397 if ( !cent->gent->client->ps.saberInFlight || saberNum != 0 )//&& cent->gent->client->ps.saberActive)
7398 {//holding the saber in-hand
7399 // CGhoul2Info *currentModel = ¢->gent->ghoul2[1];
7400 // CGhoul2Info *nextModel = ¢->gent->ghoul2[1];
7401 //FIXME: need a version of this that *doesn't* need the mFileName in the ghoul2
7402 //FIXME: use an actual surfaceIndex?
7403 char handName[MAX_QPATH];
7404 if ( saberNum == 0 )
7405 {
7406 //this returns qfalse if it doesn't exist or isn't being rendered
7407 if ( G_GetRootSurfNameWithVariant( cent->gent, "r_hand", handName, sizeof(handName) ) ) //!gi.G2API_GetSurfaceRenderStatus( ¢->gent->ghoul2[cent->gent->playerModel], "r_hand" ) )//surf is still on
7408 {
7409 CG_AddSaberBladeGo( cent, cent, NULL, ent.renderfx, cent->gent->weaponModel[saberNum], ent.origin, tempAngles, saberNum, bladeNum );
7410 //CG_AddSaberBlades( cent, ent.renderfx, ent.origin, tempAngles, saberNum );
7411 }//else, the limb will draw the blade itself
7412 }
7413 else if ( saberNum == 1 )
7414 {
7415 //this returns qfalse if it doesn't exist or isn't being rendered
7416 if ( G_GetRootSurfNameWithVariant( cent->gent, "l_hand", handName, sizeof(handName) ) ) //!gi.G2API_GetSurfaceRenderStatus( ¢->gent->ghoul2[cent->gent->playerModel], "l_hand" ) )//surf is still on
7417 {
7418 CG_AddSaberBladeGo( cent, cent, NULL, ent.renderfx, cent->gent->weaponModel[saberNum], ent.origin, tempAngles, saberNum, bladeNum );
7419 //CG_AddSaberBlades( cent, ent.renderfx, ent.origin, tempAngles, saberNum );
7420 }//else, the limb will draw the blade itself
7421 }
7422 }//in-flight saber draws it's own blade
7423 }
7424 else
7425 {
7426 if ( cent->gent->client->ps.saber[saberNum].blade[bladeNum].length < 0 )
7427 {
7428 cent->gent->client->ps.saber[saberNum].blade[bladeNum].length = 0;
7429 }
7430 //if ( cent->gent->client->ps.saberEventFlags&SEF_INWATER )
7431 {
7432 CG_CheckSaberInWater( cent, cent, saberNum, cent->gent->weaponModel[saberNum], ent.origin, tempAngles );
7433 }
7434 }
7435 if ( cent->currentState.weapon == WP_SABER
7436 && (cent->gent->client->ps.saber[saberNum].blade[bladeNum].length > 0 || cent->gent->client->ps.saberInFlight) )
7437 {
7438 calcedMp = qtrue;
7439 }
7440 }
7441 }
7442 //add the light
7443 if ( cent->gent->client->ps.dualSabers )
7444 {
7445 if ( cent->gent->client->ps.saber[0].Length() > 0.0f
7446 && !cent->gent->client->ps.saberInFlight )
7447 {
7448 if ( cent->gent->client->ps.saber[0].numBlades > 2 )
7449 {// add blended light
7450 CG_DoSaberLight( ¢->gent->client->ps.saber[0] );
7451 }
7452 }
7453 if ( cent->gent->client->ps.saber[1].Length() > 0.0f )
7454 {
7455 if ( cent->gent->client->ps.saber[1].numBlades > 2 )
7456 {// add blended light
7457 CG_DoSaberLight( ¢->gent->client->ps.saber[1] );
7458 }
7459 }
7460 }
7461 else if ( cent->gent->client->ps.saber[0].Length() > 0.0f
7462 && !cent->gent->client->ps.saberInFlight )
7463 {
7464 if ( cent->gent->client->ps.saber[0].numBlades > 2 )
7465 {// add blended light
7466 CG_DoSaberLight( ¢->gent->client->ps.saber[0] );
7467 }
7468 }
7469 }
7470
7471 if ( cent->currentState.number != 0
7472 || cg.renderingThirdPerson
7473 || cg.snap->ps.stats[STAT_HEALTH] <= 0
7474 || ( !cg.renderingThirdPerson && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE) )//First person saber
7475 )
7476 {//if NPC, third person, or dead, unless using saber
7477 //Get eyePoint & eyeAngles
7478 /*
7479 if ( cg.snap->ps.viewEntity > 0
7480 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD
7481 && cg.snap->ps.viewEntity == cent->currentState.clientNum )
7482 {//player is in an entity camera view, ME
7483 VectorCopy( ent.origin, cent->gent->client->renderInfo.eyePoint );
7484 VectorCopy( tempAngles, cent->gent->client->renderInfo.eyeAngles );
7485 VectorCopy( ent.origin, cent->gent->client->renderInfo.headPoint );
7486 }
7487 else
7488 */if ( cent->gent->headBolt == -1 )
7489 {//no headBolt
7490 VectorCopy( ent.origin, cent->gent->client->renderInfo.eyePoint );
7491 VectorCopy( tempAngles, cent->gent->client->renderInfo.eyeAngles );
7492 VectorCopy( ent.origin, cent->gent->client->renderInfo.headPoint );
7493 }
7494 else
7495 {
7496 //FIXME: if head is missing, we should let the dismembered head set our eyePoint...
7497 gi.G2API_GetBoltMatrix(cent->gent->ghoul2, cent->gent->playerModel, cent->gent->headBolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7498 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, cent->gent->client->renderInfo.eyePoint);
7499 if ( cent->gent->client->NPC_class == CLASS_RANCOR )
7500 {//temp hack
7501 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, POSITIVE_X, tempAxis);
7502 }
7503 else
7504 {
7505 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_Y, tempAxis);
7506 }
7507 vectoangles( tempAxis, cent->gent->client->renderInfo.eyeAngles );
7508 //estimate where the neck would be...
7509 gi.G2API_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_Z, tempAxis);//go down to find neck
7510 VectorMA( cent->gent->client->renderInfo.eyePoint, 8, tempAxis, cent->gent->client->renderInfo.headPoint );
7511
7512 // Play the breath puffs (or not).
7513 CG_BreathPuffs( cent, tempAngles, ent.origin );
7514 }
7515 //Get torsoPoint & torsoAngles
7516 if (cent->gent->chestBolt>=0)
7517 {
7518 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->chestBolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7519 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.torsoPoint );
7520 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, tempAxis );
7521 vectoangles( tempAxis, cent->gent->client->renderInfo.torsoAngles );
7522 }
7523 else
7524 {
7525 VectorCopy( ent.origin, cent->gent->client->renderInfo.torsoPoint);
7526 VectorClear(cent->gent->client->renderInfo.torsoAngles);
7527 }
7528 //get crotchPoint
7529 if (cent->gent->crotchBolt>=0)
7530 {
7531 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->crotchBolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7532 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.crotchPoint );
7533 }
7534 else
7535 {
7536 VectorCopy( ent.origin, cent->gent->client->renderInfo.crotchPoint);
7537 }
7538 //NOTE: these are used for any case where an NPC fires and the next shot needs to come out
7539 // of a new barrel/point. That way the muzzleflash will draw on the old barrel/point correctly
7540 //NOTE: I'm only doing this for the saboteur right now - AT-STs might need this... others?
7541 vec3_t oldMP = {0,0,0};
7542 vec3_t oldMD = {0,0,0};
7543
7544 if( !calcedMp )
7545 {
7546 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_ATST)
7547 {//FIXME: different for the three different weapon positions
7548 mdxaBone_t boltMatrix;
7549 int bolt;
7550 entityState_t *es;
7551
7552 es = ¢->currentState;
7553
7554 // figure out where the actual model muzzle is
7555 if (es->weapon == WP_ATST_MAIN)
7556 {
7557 if ( !es->number )
7558 {//player, just use left one, I guess
7559 if ( cent->gent->alt_fire )
7560 {
7561 bolt = cent->gent->handRBolt;
7562 }
7563 else
7564 {
7565 bolt = cent->gent->handLBolt;
7566 }
7567 }
7568 else if (cent->gent->count > 0)
7569 {
7570 cent->gent->count = 0;
7571 bolt = cent->gent->handLBolt;
7572 }
7573 else
7574 {
7575 cent->gent->count = 1;
7576 bolt = cent->gent->handRBolt;
7577 }
7578 }
7579 else // ATST SIDE weapons
7580 {
7581 if ( cent->gent->alt_fire)
7582 {
7583 bolt = cent->gent->genericBolt2;
7584 }
7585 else
7586 {
7587 bolt = cent->gent->genericBolt1;
7588 }
7589 }
7590
7591 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7592
7593 // work the matrix axis stuff into the original axis and origins used.
7594 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
7595 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
7596 }
7597 else if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_GALAKMECH )
7598 {
7599 int bolt = -1;
7600 if ( cent->gent->lockCount )
7601 {//using the big laser beam
7602 bolt = cent->gent->handLBolt;
7603 }
7604 else//repeater
7605 {
7606 if ( cent->gent->alt_fire )
7607 {//fire from the lower barrel (not that anyone will ever notice this, but...)
7608 bolt = cent->gent->genericBolt3;
7609 }
7610 else
7611 {
7612 bolt = cent->gent->handRBolt;
7613 }
7614 }
7615
7616 if ( bolt == -1 )
7617 {
7618 VectorCopy( ent.origin, cent->gent->client->renderInfo.muzzlePoint );
7619 AngleVectors( tempAngles, cent->gent->client->renderInfo.muzzleDir, NULL, NULL );
7620 }
7621 else
7622 {
7623 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, bolt, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7624
7625 // work the matrix axis stuff into the original axis and origins used.
7626 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
7627 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
7628 }
7629 }
7630 // Set the Vehicle Muzzle Point and Direction.
7631 else if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_VEHICLE )
7632 {
7633 // Get the Position and Direction of the Tag and use that as our Muzzles Properties.
7634 mdxaBone_t boltMatrix;
7635 vec3_t velocity;
7636 VectorCopy(cent->gent->client->ps.velocity, velocity);
7637 velocity[2] = 0;
7638 for ( int i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
7639 {
7640 if ( cent->gent->m_pVehicle->m_iMuzzleTag[i] != -1 )
7641 {
7642 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->playerModel, cent->gent->m_pVehicle->m_iMuzzleTag[i], &boltMatrix, cent->lerpAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7643 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->m_pVehicle->m_Muzzles[i].m_vMuzzlePos );
7644 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->m_pVehicle->m_Muzzles[i].m_vMuzzleDir );
7645 VectorMA(cent->gent->m_pVehicle->m_Muzzles[i].m_vMuzzlePos, 0.075f, velocity, cent->gent->m_pVehicle->m_Muzzles[i].m_vMuzzlePos);
7646 }
7647 else
7648 {
7649 break;
7650 }
7651 }
7652 }
7653 else if ( cent->gent->client && cent->gent->NPC//client NPC
7654 /*
7655 && cent->gent->client->NPC_class == CLASS_REBORN//cultist
7656 && cent->gent->NPC->rank >= RANK_LT_COMM//commando
7657 */
7658 && cent->gent->s.weapon == WP_BLASTER_PISTOL//using blaster pistol
7659 && cent->gent->weaponModel[1] )//one in each hand
7660 {
7661 qboolean getBoth = qfalse;
7662 int oldOne = 0;
7663 if ( cent->muzzleFlashTime > 0 && wData && !(cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
7664 {//we need to get both muzzles since we're toggling and we fired recently
7665 getBoth = qtrue;
7666 oldOne = (cent->gent->count)?0:1;
7667 }
7668 if ( ( cent->gent->weaponModel[cent->gent->count] != -1)
7669 && ( cent->gent->ghoul2.size() > cent->gent->weaponModel[cent->gent->count] )
7670 && ( cent->gent->ghoul2[cent->gent->weaponModel[cent->gent->count]].mModelindex != -1) )
7671 {//get whichever one we're using now
7672 mdxaBone_t boltMatrix;
7673 // figure out where the actual model muzzle is
7674 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->weaponModel[cent->gent->count], 0, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7675 // work the matrix axis stuff into the original axis and origins used.
7676 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
7677 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
7678 }
7679 //get the old one too, if needbe, and store it in muzzle2
7680 if ( getBoth
7681 && ( cent->gent->weaponModel[oldOne] != -1) //have a second weapon
7682 && ( cent->gent->ghoul2.size() > cent->gent->weaponModel[oldOne] ) //have a valid ghoul model index
7683 && ( cent->gent->ghoul2[cent->gent->weaponModel[oldOne]].mModelindex != -1) )//model exists and was loaded
7684 {//saboteur commando, toggle the muzzle point back and forth between the two pistols each time he fires
7685 mdxaBone_t boltMatrix;
7686 // figure out where the actual model muzzle is
7687 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->weaponModel[oldOne], 0, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7688 // work the matrix axis stuff into the original axis and origins used.
7689 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, oldMP );
7690 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, oldMD );
7691 }
7692 }
7693 else if (( cent->gent->weaponModel[0] != -1) &&
7694 ( cent->gent->ghoul2.size() > cent->gent->weaponModel[0] ) &&
7695 ( cent->gent->ghoul2[cent->gent->weaponModel[0]].mModelindex != -1))
7696 {
7697 mdxaBone_t boltMatrix;
7698 // figure out where the actual model muzzle is
7699 gi.G2API_GetBoltMatrix( cent->gent->ghoul2, cent->gent->weaponModel[0], 0, &boltMatrix, tempAngles, ent.origin, cg.time, cgs.model_draw, cent->currentState.modelScale );
7700 // work the matrix axis stuff into the original axis and origins used.
7701 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cent->gent->client->renderInfo.muzzlePoint );
7702 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, cent->gent->client->renderInfo.muzzleDir );
7703 }
7704 else
7705 {
7706 VectorCopy( cent->gent->client->renderInfo.eyePoint, cent->gent->client->renderInfo.muzzlePoint );
7707 AngleVectors( cent->gent->client->renderInfo.eyeAngles, cent->gent->client->renderInfo.muzzleDir, NULL, NULL );
7708 }
7709 cent->gent->client->renderInfo.mPCalcTime = cg.time;
7710 }
7711
7712 // Draw Vehicle Muzzle Flashs.
7713 if ( cent->gent && cent->gent->client && cent->gent->client->NPC_class == CLASS_VEHICLE )
7714 {
7715 for ( int i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
7716 {
7717 /*if ( cent->gent->m_pVehicle->m_pVehicleInfo->weap1ID == cent->gent->m_pVehicle->m_pVehicleInfo->weapMuzzle[i] )
7718 {
7719 iDelay = cent->gent->m_pVehicle->m_pVehicleInfo->weap1Delay;
7720 }
7721 else if ( cent->gent->m_pVehicle->m_pVehicleInfo->weap2ID == cent->gent->m_pVehicle->m_pVehicleInfo->weapMuzzle[i] )
7722 {
7723 iDelay = cent->gent->m_pVehicle->m_pVehicleInfo->weap2Delay;
7724 }
7725
7726 if ( cent->gent->m_pVehicle->m_Muzzles[i].m_iMuzzleWait - cg.time > ( iDelay - 500 ) )*/
7727
7728 if ( cent->gent->m_pVehicle->m_Muzzles[i].m_bFired )
7729 {
7730 const char *effect = &weaponData[ cent->gent->m_pVehicle->m_pVehicleInfo->weapMuzzle[i] ].mMuzzleEffect[0];
7731 if ( effect )
7732 {
7733 theFxScheduler.PlayEffect( effect, cent->gent->m_pVehicle->m_Muzzles[i].m_vMuzzlePos, cent->gent->m_pVehicle->m_Muzzles[i].m_vMuzzleDir );
7734 }
7735 cent->gent->m_pVehicle->m_Muzzles[i].m_bFired = false;
7736 }
7737 }
7738 }
7739 // Pick the right effect for the type of weapon we are, defaults to no effect unless explicitly specified
7740 else if ( cent->muzzleFlashTime > 0 && wData && !(cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
7741 {
7742 const char *effect = NULL;
7743
7744 cent->muzzleFlashTime = 0;
7745
7746 // Try and get a default muzzle so we have one to fall back on
7747 if ( wData->mMuzzleEffect[0] )
7748 {
7749 effect = &wData->mMuzzleEffect[0];
7750 }
7751
7752 if ( cent->altFire )
7753 {
7754 // We're alt-firing, so see if we need to override with a custom alt-fire effect
7755 if ( wData->mAltMuzzleEffect[0] )
7756 {
7757 effect = &wData->mAltMuzzleEffect[0];
7758 }
7759 }
7760
7761 if (/*( cent->currentState.eFlags & EF_FIRING || cent->currentState.eFlags & EF_ALT_FIRING ) &&*/ effect )
7762 {
7763 if ( cent->gent && cent->gent->NPC )
7764 {
7765 if ( !VectorCompare( oldMP, vec3_origin )
7766 && !VectorCompare( oldMD, vec3_origin ) )
7767 {//we have an old muzzlePoint we want to use
7768 theFxScheduler.PlayEffect( effect, oldMP, oldMD );
7769 }
7770 else
7771 {//use the current one
7772 theFxScheduler.PlayEffect( effect, cent->gent->client->renderInfo.muzzlePoint,
7773 cent->gent->client->renderInfo.muzzleDir );
7774 }
7775 }
7776 else
7777 {
7778 // We got an effect and we're firing, so let 'er rip.
7779 theFxScheduler.PlayEffect( effect, cent->currentState.clientNum );
7780 }
7781 }
7782 }
7783
7784 //play special force effects
7785 /*
7786 if ( cent->gent->NPC && ( cent->gent->NPC->confusionTime > cg.time || cent->gent->NPC->charmedTime > cg.time || cent->gent->NPC->controlledTime > cg.time) )
7787 {// we are currently confused, so play an effect at the headBolt position
7788 if ( TIMER_Done( cent->gent, "confusionEffectDebounce" ) )
7789 {//ARGH!!!
7790 theFxScheduler.PlayEffect( cgs.effects.forceConfusion, cent->gent->client->renderInfo.eyePoint );
7791 TIMER_Set( cent->gent, "confusionEffectDebounce", 1000 );
7792 }
7793 }
7794 */
7795
7796 if ( cent->gent->client && cent->gent->forcePushTime > cg.time )
7797 {//being pushed
7798 CG_ForcePushBodyBlur( cent, ent.origin, tempAngles );
7799 }
7800
7801 //This is now being done via an effect and the animevents.cfg
7802 //if ( cent->gent->client->ps.powerups[PW_FORCE_PUSH] > cg.time ||
7803 if ( (cent->gent->client->ps.forcePowersActive & (1<<FP_GRIP)) )
7804 {//doing the gripping
7805 //FIXME: effect?
7806 CG_ForcePushBlur( cent->gent->client->renderInfo.handLPoint, qtrue );
7807 }
7808
7809 if ( cent->gent->client->ps.eFlags & EF_FORCE_GRIPPED )
7810 {//being gripped
7811 CG_ForcePushBlur( cent->gent->client->renderInfo.headPoint, qtrue );
7812 }
7813
7814 if ( cent->gent->client && cent->gent->client->ps.powerups[PW_SHOCKED] > cg.time )
7815 {//being electrocuted
7816 CG_ForceElectrocution( cent, ent.origin, tempAngles, cgs.media.boltShader );
7817 }
7818
7819 if ( cent->gent->client->ps.eFlags & EF_FORCE_DRAINED
7820 || (cent->currentState.powerups&(1<<PW_DRAINED)) )
7821 {//being drained
7822 //do red electricity lines off them and red drain shell on them
7823 CG_ForceElectrocution( cent, ent.origin, tempAngles, cgs.media.drainShader, qtrue );
7824 }
7825
7826 if ( cent->gent->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
7827 {//doing the electrocuting
7828 //FIXME: if the target is absorbing or blocking lightning w/saber, draw a beam from my hand to his (hand?chest?saber?)
7829 vec3_t tAng, fxDir;
7830 VectorCopy( cent->lerpAngles, tAng );
7831 if ( cent->gent->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
7832 {//arc
7833 vec3_t fxAxis[3];
7834 AnglesToAxis( tAng, fxAxis );
7835 theFxScheduler.PlayEffect( cgs.effects.forceLightningWide, cent->gent->client->renderInfo.handLPoint, fxAxis );
7836 if ( cent->gent->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING
7837 || cent->gent->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START
7838 || cent->gent->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
7839 || cent->gent->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE )
7840 {//jackin' 'em up, Palpatine-style
7841 theFxScheduler.PlayEffect( cgs.effects.forceLightningWide, cent->gent->client->renderInfo.handRPoint, fxAxis );
7842 }
7843 }
7844 else
7845 {//line
7846 AngleVectors( tAng, fxDir, NULL, NULL );
7847 theFxScheduler.PlayEffect( cgs.effects.forceLightning, cent->gent->client->renderInfo.handLPoint, fxDir );
7848 }
7849 }
7850
7851 if ( (cent->gent->client->ps.eFlags&EF_POWERING_ROSH) )
7852 {
7853 vec3_t tAng, fxDir;
7854 VectorCopy( cent->lerpAngles, tAng );
7855 AngleVectors( tAng, fxDir, NULL, NULL );
7856 theFxScheduler.PlayEffect( cgs.effects.forceDrain, cent->gent->client->renderInfo.handLPoint, fxDir );//theFxScheduler.RegisterEffect( "force/dr1" )
7857 }
7858
7859 if ( cent->gent->client->ps.forcePowersActive&(1<<FP_DRAIN)
7860 && cent->gent->client->ps.forceDrainEntityNum >= ENTITYNUM_WORLD )
7861 {//doing the draining and not on a single person
7862 vec3_t tAng, fxDir;
7863 VectorCopy( cent->lerpAngles, tAng );
7864 if ( cent->gent->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
7865 {//arc
7866 vec3_t fxAxis[3];
7867 AnglesToAxis( tAng, fxAxis );
7868 theFxScheduler.PlayEffect( cgs.effects.forceDrainWide, cent->gent->client->renderInfo.handLPoint, fxAxis );
7869 }
7870 else
7871 {//line
7872 AngleVectors( tAng, fxDir, NULL, NULL );
7873 theFxScheduler.PlayEffect( cgs.effects.forceDrain, cent->gent->client->renderInfo.handLPoint, fxDir );
7874 }
7875 }
7876 //spotlight?
7877 if ( (cent->currentState.eFlags&EF_SPOTLIGHT) )
7878 {//FIXME: player's view should glare/flare if look at this... maybe build into the effect?
7879 // hack for the spotlight
7880 vec3_t org, eyeFwd;
7881
7882 AngleVectors( cent->gent->client->renderInfo.eyeAngles, eyeFwd, NULL, NULL );
7883 theFxScheduler.PlayEffect( "rockettrooper/light_cone", cent->gent->client->renderInfo.eyePoint, eyeFwd );
7884 // stay a bit back from the server-side's trace impact point...this may not be enough?
7885 VectorMA( cent->gent->client->renderInfo.eyePoint, cent->gent->speed - 5, eyeFwd, org );
7886 float radius = cent->gent->speed;
7887 if ( radius < 128.0f )
7888 {
7889 radius = 128.0f;
7890 }
7891 else if ( radius > 1024.0f )
7892 {
7893 radius = 1024.0f;
7894 }
7895 cgi_R_AddLightToScene( org, radius, 1.0f, 1.0f, 1.0f );
7896 }
7897 }
7898 //"refraction" effect -rww
7899 if ( cent->gent->client->ps.powerups[PW_FORCE_PUSH] > cg.time )
7900 {
7901 CG_ForcePushRefraction(cent->gent->client->renderInfo.handLPoint, cent);
7902 }
7903 else if ( cent->gent->client->ps.powerups[PW_FORCE_PUSH_RHAND] > cg.time )
7904 {
7905 CG_ForcePushRefraction(cent->gent->client->renderInfo.handRPoint, cent);
7906 }
7907 else
7908 {
7909 cent->gent->client->ps.forcePowersActive &= ~( 1 << FP_PULL );
7910 }
7911
7912 //bolted effects
7913 CG_BoltedEffects( cent, ent.origin, tempAngles );
7914 //As good a place as any, I suppose, to do this keyframed sound thing
7915 CGG2_AnimEvents( cent );
7916 //setup old system for gun to look at
7917 //CG_RunLerpFrame( ci, ¢->pe.torso, cent->gent->client->ps.torsoAnim, cent->gent->client->renderInfo.torsoFpsMod, cent->gent->s.number );
7918 if ( cent->gent && cent->gent->client && cent->gent->client->ps.weapon == WP_SABER )
7919 {
7920 extern qboolean PM_KickingAnim( int anim );
7921 if ( !PM_KickingAnim( cent->gent->client->ps.torsoAnim )
7922 || cent->gent->client->ps.torsoAnim == BOTH_A7_KICK_S )
7923 {//not kicking (unless it's the spinning kick)
7924 if ( cg_timescale.value < 1.0f && (cent->gent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
7925 {
7926 int wait = floor( (float)FRAMETIME/2.0f );
7927 //sanity check
7928 if ( cent->gent->client->ps.saberDamageDebounceTime - cg.time > wait )
7929 {//when you unpause the game with force speed on, the time gets *really* wiggy...
7930 cent->gent->client->ps.saberDamageDebounceTime = cg.time + wait;
7931 }
7932 if ( cent->gent->client->ps.saberDamageDebounceTime <= cg.time )
7933 {
7934 extern void WP_SabersDamageTrace( gentity_t *ent, qboolean noEffects );
7935 extern void WP_SaberUpdateOldBladeData( gentity_t *ent );
7936 //FIXME: this causes an ASSLOAD of effects
7937 WP_SabersDamageTrace( cent->gent, qtrue );
7938 WP_SaberUpdateOldBladeData( cent->gent );
7939 cent->gent->client->ps.saberDamageDebounceTime = cg.time + floor((float)wait*cg_timescale.value);
7940 }
7941 }
7942 }
7943 }
7944 }
7945 else
7946 {
7947 refEntity_t legs;
7948 refEntity_t torso;
7949 refEntity_t head;
7950 refEntity_t gun;
7951 refEntity_t flash;
7952 refEntity_t flashlight;
7953 int renderfx, i;
7954 const weaponInfo_t *weapon;
7955
7956
7957 /*
7958 Ghoul2 Insert End
7959 */
7960
7961 memset( &legs, 0, sizeof(legs) );
7962 memset( &torso, 0, sizeof(torso) );
7963 memset( &head, 0, sizeof(head) );
7964 memset( &gun, 0, sizeof(gun) );
7965 memset( &flash, 0, sizeof(flash) );
7966 memset( &flashlight, 0, sizeof(flashlight) );
7967
7968 // Weapon sounds may need to be stopped, so check now
7969 CG_StopWeaponSounds( cent );
7970
7971 //FIXME: pass in the axis/angles offset between the tag_torso and the tag_head?
7972 // get the rotation information
7973 CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis );
7974 if ( cent->gent && cent->gent->client )
7975 {
7976 cent->gent->client->ps.legsYaw = cent->lerpAngles[YAW];
7977 }
7978
7979 // get the animation state (after rotation, to allow feet shuffle)
7980 // NB: Also plays keyframed animSounds (Bob- hope you dont mind, I was here late and at about 5:30 Am i needed to do something to keep me awake and i figured you wouldn't mind- you might want to check it, though, to make sure I wasn't smoking crack and missed something important, it is pretty late and I'm getting pretty close to being up for 24 hours here, so i wouldn't doubt if I might have messed something up, but i tested it and it looked right.... noticed in old code base i was doing it wrong too, whic h explains why I was getting so many damn sounds all the time! I had to lower the probabilities because it seemed like i was getting way too many sounds, and that was the problem! Well, should be fixed now I think...)
7981 CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp,
7982 &torso.oldframe, &torso.frame, &torso.backlerp );
7983
7984 cent->gent->client->renderInfo.legsFrame = cent->pe.legs.frame;
7985 cent->gent->client->renderInfo.torsoFrame = cent->pe.torso.frame;
7986
7987 // add powerups floating behind the player
7988 CG_PlayerPowerups( cent );
7989
7990 // add the shadow
7991 shadow = CG_PlayerShadow( cent, &shadowPlane );
7992
7993 // add a water splash if partially in and out of water
7994 CG_PlayerSplash( cent );
7995
7996 // get the player model information
7997 renderfx = 0;
7998 if ( !cg.renderingThirdPerson || cg.zoomMode )
7999 {
8000 if ( cg.snap->ps.viewEntity <= 0 || cg.snap->ps.viewEntity >= ENTITYNUM_WORLD)
8001 {//no viewentity
8002 if ( cent->currentState.number == cg.snap->ps.clientNum )
8003 {//I am the player
8004 if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
8005 {//not using saber or fists
8006 renderfx = RF_THIRD_PERSON; // only draw in mirrors
8007 }
8008 }
8009 }
8010 else if ( cent->currentState.number == cg.snap->ps.viewEntity )
8011 {//I am the view entity
8012 if ( cg.snap->ps.weapon != WP_SABER && cg.snap->ps.weapon != WP_MELEE )
8013 {//not using saber or fists
8014 renderfx = RF_THIRD_PERSON; // only draw in mirrors
8015 }
8016 }
8017 }
8018
8019 if ( (cg_shadows.integer == 2) || (cg_shadows.integer == 3 && shadow) )
8020 {
8021 renderfx |= RF_SHADOW_PLANE;
8022 }
8023 renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
8024 if ( cent->gent->NPC && cent->gent->NPC->scriptFlags & SCF_MORELIGHT )
8025 {
8026 renderfx |= RF_MORELIGHT; //bigger than normal min light
8027 }
8028
8029 if ( cent->gent && cent->gent->client )
8030 {
8031 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.headPoint );
8032 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.handRPoint );
8033 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.handLPoint );
8034 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.footRPoint );
8035 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.footLPoint );
8036 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.torsoPoint );
8037 VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.torsoAngles );
8038 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.crotchPoint );
8039 }
8040 if ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD && cg.snap->ps.viewEntity == cent->currentState.clientNum )
8041 {//player is in an entity camera view, ME
8042 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.eyePoint );
8043 VectorCopy( cent->lerpAngles, cent->gent->client->renderInfo.eyeAngles );
8044 VectorCopy( cent->lerpOrigin, cent->gent->client->renderInfo.headPoint );
8045 }
8046 //
8047 // add the legs
8048 //
8049 legs.hModel = ci->legsModel;
8050 legs.customSkin = ci->legsSkin;
8051
8052 VectorCopy( cent->lerpOrigin, legs.origin );
8053
8054 //Scale applied to a refEnt will apply to any models attached to it...
8055 //This seems to copy the scale to every piece attached, kinda cool, but doesn't
8056 //allow the body to be scaled up without scaling a bolt on or whatnot...
8057 //Only apply scale if it's not 100% scale...
8058 if(cent->currentState.modelScale[0] != 0.0f)
8059 {
8060 VectorScale( legs.axis[0], cent->currentState.modelScale[0], legs.axis[0] );
8061 legs.nonNormalizedAxes = qtrue;
8062 }
8063
8064 if(cent->currentState.modelScale[1] != 0.0f)
8065 {
8066 VectorScale( legs.axis[1], cent->currentState.modelScale[1], legs.axis[1] );
8067 legs.nonNormalizedAxes = qtrue;
8068 }
8069
8070 if(cent->currentState.modelScale[2] != 0.0f)
8071 {
8072 VectorScale( legs.axis[2], cent->currentState.modelScale[2], legs.axis[2] );
8073 legs.nonNormalizedAxes = qtrue;
8074 if ( !staticScale )
8075 {
8076 //FIXME:? need to know actual height of leg model bottom to origin, not hardcoded
8077 legs.origin[2] += 24 * (cent->currentState.modelScale[2] - 1);
8078 }
8079 }
8080
8081 VectorCopy( legs.origin, legs.lightingOrigin );
8082 legs.shadowPlane = shadowPlane;
8083 legs.renderfx = renderfx;
8084 VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all
8085
8086 CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, cent );
8087
8088 // if the model failed, allow the default nullmodel to be displayed
8089 if (!legs.hModel)
8090 {
8091 return;
8092 }
8093
8094 //
8095 // add the torso
8096 //
8097 torso.hModel = ci->torsoModel;
8098 if (torso.hModel)
8099 {
8100 orientation_t tag_torso;
8101
8102 torso.customSkin = ci->torsoSkin;
8103
8104 VectorCopy( cent->lerpOrigin, torso.lightingOrigin );
8105
8106 CG_PositionRotatedEntityOnTag( &torso, &legs, legs.hModel, "tag_torso", &tag_torso );
8107 VectorCopy( torso.origin, cent->gent->client->renderInfo.torsoPoint );
8108 vectoangles( tag_torso.axis[0], cent->gent->client->renderInfo.torsoAngles );
8109
8110 torso.shadowPlane = shadowPlane;
8111 torso.renderfx = renderfx;
8112
8113 CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, cent );
8114
8115 //
8116 // add the head
8117 //
8118 head.hModel = ci->headModel;
8119 if (head.hModel)
8120 {
8121 orientation_t tag_head;
8122
8123 //Deal with facial expressions
8124 //CG_PlayerHeadExtension( cent, &head );
8125
8126 VectorCopy( cent->lerpOrigin, head.lightingOrigin );
8127
8128 CG_PositionRotatedEntityOnTag( &head, &torso, torso.hModel, "tag_head", &tag_head );
8129 VectorCopy( head.origin, cent->gent->client->renderInfo.headPoint );
8130 vectoangles( tag_head.axis[0], cent->gent->client->renderInfo.headAngles );
8131
8132 head.shadowPlane = shadowPlane;
8133 head.renderfx = renderfx;
8134
8135 CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, cent );
8136
8137 if ( cent->gent && cent->gent->NPC && ( cent->gent->NPC->confusionTime > cg.time || cent->gent->NPC->charmedTime > cg.time || cent->gent->NPC->controlledTime > cg.time) )
8138 {
8139 // we are currently confused, so play an effect
8140 if ( TIMER_Done( cent->gent, "confusionEffectDebounce" ) )
8141 {//ARGH!!!
8142 theFxScheduler.PlayEffect( cgs.effects.forceConfusion, head.origin );
8143 TIMER_Set( cent->gent, "confusionEffectDebounce", 1000 );
8144 }
8145 }
8146
8147 if ( !calcedMp )
8148 {//First person player's eyePoint and eyeAngles should be copies from cg.refdef...
8149 //Calc this client's eyepoint
8150 VectorCopy( head.origin, cent->gent->client->renderInfo.eyePoint );
8151 // race is gone, eyepoint should refer to the tag/bolt on the model... if this breaks something let me know - dmv
8152 // VectorMA( cent->gent->client->renderInfo.eyePoint, CG_EyePointOfsForRace[cent->gent->client->race][1]*scaleFactor[2], head.axis[2], cent->gent->client->renderInfo.eyePoint );//up
8153 // VectorMA( cent->gent->client->renderInfo.eyePoint, CG_EyePointOfsForRace[cent->gent->client->race][0]*scaleFactor[0], head.axis[0], cent->gent->client->renderInfo.eyePoint );//forward
8154 //Calc this client's eyeAngles
8155 vectoangles( head.axis[0], cent->gent->client->renderInfo.eyeAngles );
8156 }
8157 }
8158 else
8159 {
8160 VectorCopy( torso.origin, cent->gent->client->renderInfo.eyePoint );
8161 cent->gent->client->renderInfo.eyePoint[2] += cent->gent->maxs[2] - 4;
8162 vectoangles( torso.axis[0], cent->gent->client->renderInfo.eyeAngles );
8163 }
8164
8165 //
8166 // add the gun
8167 //
8168 CG_RegisterWeapon( cent->currentState.weapon );
8169 weapon = &cg_weapons[cent->currentState.weapon];
8170
8171 gun.hModel = weapon->weaponWorldModel;
8172 if (gun.hModel)
8173 {
8174 qboolean drawGun = qtrue;
8175 //FIXME: allow scale, animation and angle offsets
8176 VectorCopy( cent->lerpOrigin, gun.lightingOrigin );
8177
8178 //FIXME: allow it to be put anywhere and move this out of if(torso.hModel)
8179 //Will have to call CG_PositionRotatedEntityOnTag
8180
8181 CG_PositionEntityOnTag( &gun, &torso, torso.hModel, "tag_weapon");
8182
8183 //--------------------- start saber hacks
8184 /*
8185 if ( cent->gent && cent->gent->client && ( cent->currentState.weapon == WP_SABER || cent->gent->client->ps.saberInFlight ) )
8186 {
8187 int numSabers = 1;
8188 if ( cent->gent->client->ps.dualSabers )
8189 {
8190 numSabers = 2;
8191 }
8192 for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
8193 {
8194 if ( !cent->gent->client->ps.saber[saberNum].active )//!cent->gent->client->ps.saberActive )
8195 {//saber is off
8196 if ( cent->gent->client->ps.saber[saberNum].length > 0 )
8197 {
8198 if ( cent->gent->client->ps.stats[STAT_HEALTH] <= 0 )
8199 {//dead, didn't actively turn it off
8200 cent->gent->client->ps.saber[saberNum].length -= cent->gent->client->ps.saber[saberNum].lengthMax/10 * cg.frametime/100;
8201 }
8202 else
8203 {//actively turned it off, shrink faster
8204 cent->gent->client->ps.saber[saberNum].length -= cent->gent->client->ps.saber[saberNum].lengthMax/3 * cg.frametime/100;
8205 }
8206 }
8207 if ( cent->gent->client->ps.saber[saberNum].length < 0 )
8208 {
8209 cent->gent->client->ps.saber[saberNum].length = 0;
8210 }
8211 }
8212 else if ( cent->gent->client->ps.saber[saberNum].length < cent->gent->client->ps.saber[saberNum].lengthMax )
8213 {//saber is on
8214 if ( !cent->gent->client->ps.saber[saberNum].length )
8215 {
8216 if ( cent->gent->client->ps.saberInFlight )
8217 {//play it on the saber
8218 cgi_S_UpdateEntityPosition( cent->gent->client->ps.saberEntityNum, g_entities[cent->gent->client->ps.saberEntityNum].currentOrigin );
8219 cgi_S_StartSound (NULL, cent->gent->client->ps.saberEntityNum, CHAN_AUTO, cgs.sound_precache[cent->gent->client->ps.saber[0].soundOn] );
8220 }
8221 else
8222 {
8223 cgi_S_StartSound (NULL, cent->currentState.number, CHAN_AUTO, cgs.sound_precache[cent->gent->client->ps.saber[0].soundOn] );
8224 #ifdef _IMMERSION
8225 cgi_FF_Start( cgi_FF_Register( "fffx/weapons/saber/saberon", FF_CHANNEL_WEAPON ), cent->currentState.number );
8226 #endif // _IMMERSION
8227 }
8228 }
8229 cent->gent->client->ps.saber[saberNum].length += cent->gent->client->ps.saber[saberNum].lengthMax/6 * cg.frametime/100;//= saber[saberNum].lengthMax;
8230 if ( cent->gent->client->ps.saber[saberNum].length > cent->gent->client->ps.saber[saberNum].lengthMax )
8231 {
8232 cent->gent->client->ps.saber[saberNum].length = cent->gent->client->ps.saber[saberNum].lengthMax;
8233 }
8234 }
8235
8236 if ( cent->gent->client->ps.saberInFlight )
8237 {//not holding the saber in-hand
8238 drawGun = qfalse;
8239 }
8240 if ( cent->gent->client->ps.saber[saberNum].length > 0 )
8241 {
8242 if ( !cent->gent->client->ps.saberInFlight )
8243 {//holding the saber in-hand
8244 CG_AddSaberBlade( cent, cent, &gun, renderfx, 0, NULL, NULL );
8245 calcedMp = qtrue;
8246 }
8247 }
8248 else
8249 {
8250 //if ( cent->gent->client->ps.saberEventFlags&SEF_INWATER )
8251 {
8252 CG_CheckSaberInWater( cent, cent, 0, 0, NULL, NULL );
8253 }
8254 }
8255 }
8256 }
8257
8258 */
8259 //--------------------- end saber hacks
8260
8261 gun.shadowPlane = shadowPlane;
8262 gun.renderfx = renderfx;
8263
8264 if ( drawGun )
8265 {
8266 CG_AddRefEntityWithPowerups( &gun,
8267 (cent->currentState.powerups & ((1<<PW_CLOAKED)|(1<<PW_BATTLESUIT)) ),
8268 cent );
8269 }
8270
8271 //
8272 // add the flash (even if invisible)
8273 //
8274
8275 // impulse flash
8276 if ( cent->muzzleFlashTime > 0 && wData && !(cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
8277 {
8278 int effect = 0;
8279
8280 cent->muzzleFlashTime = 0;
8281
8282 CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash");
8283
8284 // Try and get a default muzzle so we have one to fall back on
8285 if ( wData->mMuzzleEffectID )
8286 {
8287 effect = wData->mMuzzleEffectID;
8288 }
8289
8290 if ( cent->currentState.eFlags & EF_ALT_FIRING )
8291 {
8292 // We're alt-firing, so see if we need to override with a custom alt-fire effect
8293 if ( wData->mAltMuzzleEffectID )
8294 {
8295 effect = wData->mAltMuzzleEffectID;
8296 }
8297 }
8298
8299 if (( cent->currentState.eFlags & EF_FIRING || cent->currentState.eFlags & EF_ALT_FIRING ) && effect )
8300 {
8301 vec3_t up={0,0,1}, ax[3];
8302
8303 VectorCopy( flash.axis[0], ax[0] );
8304
8305 CrossProduct( up, ax[0], ax[1] );
8306 CrossProduct( ax[0], ax[1], ax[2] );
8307
8308 if (( cent->gent && cent->gent->NPC ) || cg.renderingThirdPerson )
8309 {
8310 theFxScheduler.PlayEffect( effect, flash.origin, ax );
8311 }
8312 else
8313 {
8314 // We got an effect and we're firing, so let 'er rip.
8315 theFxScheduler.PlayEffect( effect, flash.origin, ax );
8316 }
8317 }
8318 }
8319
8320 if ( !calcedMp && !(cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
8321 {// Set the muzzle point
8322 orientation_t orientation;
8323
8324 cgi_R_LerpTag( &orientation, weapon->weaponModel, gun.oldframe, gun.frame,
8325 1.0f - gun.backlerp, "tag_flash" );
8326
8327 // FIXME: allow origin offsets along tag?
8328 VectorCopy( gun.origin, cent->gent->client->renderInfo.muzzlePoint );
8329 for ( i = 0 ; i < 3 ; i++ )
8330 {
8331 VectorMA( cent->gent->client->renderInfo.muzzlePoint, orientation.origin[i], gun.axis[i], cent->gent->client->renderInfo.muzzlePoint );
8332 }
8333 // VectorCopy( gun.axis[0], cent->gent->client->renderInfo.muzzleDir );
8334 // VectorAdd( gun.axis[0], orientation.axis[0], cent->gent->client->renderInfo.muzzleDir );
8335 // VectorNormalize( cent->gent->client->renderInfo.muzzleDir );
8336
8337
8338 cent->gent->client->renderInfo.mPCalcTime = cg.time;
8339 // Weapon wasn't firing anymore, so ditch any weapon associated looping sounds.
8340 //cent->gent->s.loopSound = 0;
8341 }
8342 }
8343 }
8344 else
8345 {
8346 VectorCopy( legs.origin, cent->gent->client->renderInfo.eyePoint );
8347 cent->gent->client->renderInfo.eyePoint[2] += cent->gent->maxs[2] - 4;
8348 vectoangles( legs.axis[0], cent->gent->client->renderInfo.eyeAngles );
8349 }
8350
8351 }
8352
8353 //FIXME: for debug, allow to draw a cone of the NPC's FOV...
8354 if ( cent->currentState.number == 0 && cg.renderingThirdPerson )
8355 {
8356 playerState_t *ps = &cg.predicted_player_state;
8357
8358 if (( ps->weaponstate == WEAPON_CHARGING_ALT && ps->weapon == WP_BRYAR_PISTOL )
8359 || ( ps->weaponstate == WEAPON_CHARGING_ALT && ps->weapon == WP_BLASTER_PISTOL )
8360 || ( ps->weapon == WP_BOWCASTER && ps->weaponstate == WEAPON_CHARGING )
8361 || ( ps->weapon == WP_DEMP2 && ps->weaponstate == WEAPON_CHARGING_ALT ))
8362 {
8363 int shader = 0;
8364 float val = 0.0f, scale = 1.0f;
8365 vec3_t WHITE = {1.0f,1.0f,1.0f};
8366
8367 if ( ps->weapon == WP_BRYAR_PISTOL
8368 || ps->weapon == WP_BLASTER_PISTOL )
8369 {
8370 // Hardcoded max charge time of 1 second
8371 val = ( cg.time - ps->weaponChargeTime ) * 0.001f;
8372 shader = cgi_R_RegisterShader( "gfx/effects/bryarFrontFlash" );
8373 }
8374 else if ( ps->weapon == WP_BOWCASTER )
8375 {
8376 // Hardcoded max charge time of 1 second
8377 val = ( cg.time - ps->weaponChargeTime ) * 0.001f;
8378 shader = cgi_R_RegisterShader( "gfx/effects/greenFrontFlash" );
8379 }
8380 else if ( ps->weapon == WP_DEMP2 )
8381 {
8382 // Hardcoded max charge time of 1 second
8383 val = ( cg.time - ps->weaponChargeTime ) * 0.001f;
8384 shader = cgi_R_RegisterShader( "gfx/misc/lightningFlash" );
8385 scale = 1.75f;
8386 }
8387
8388 if ( val < 0.0f )
8389 {
8390 val = 0.0f;
8391 }
8392 else if ( val > 1.0f )
8393 {
8394 val = 1.0f;
8395 CGCam_Shake( 0.1f, 100 );
8396 }
8397 else
8398 {
8399 CGCam_Shake( val * val * 0.3f, 100 );
8400 }
8401
8402 val += Q_flrand(0.0f, 1.0f) * 0.5f;
8403
8404 FX_AddSprite( cent->gent->client->renderInfo.muzzlePoint, NULL, NULL, 3.0f * val * scale, 0.0f, 0.7f, 0.7f, WHITE, WHITE, Q_flrand(0.0f, 1.0f) * 360, 0.0f, 1.0f, shader, FX_USE_ALPHA );
8405 }
8406 }
8407 }
8408
8409 //=====================================================================
8410
8411 /*
8412 ===============
8413 CG_ResetPlayerEntity
8414
8415 A player just came into view or teleported, so reset all animation info
8416
8417 FIXME: We do not need to do this, we can remember the last anim and frame they were
8418 on and coontinue from there.
8419 ===============
8420 */
CG_ResetPlayerEntity(centity_t * cent)8421 void CG_ResetPlayerEntity( centity_t *cent ) {
8422 // cent->errorTime = -99999; // guarantee no error decay added
8423 // cent->extrapolated = qfalse;
8424
8425 if ( cent->gent && cent->gent->ghoul2.size() )
8426 {
8427 if ( cent->currentState.clientNum < MAX_CLIENTS )
8428 {
8429 CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim );
8430 CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim );
8431 }
8432 else if ( cent->gent && cent->gent->client )
8433 {
8434 CG_ClearLerpFrame( ¢->gent->client->clientInfo, ¢->pe.legs, cent->currentState.legsAnim );
8435 CG_ClearLerpFrame( ¢->gent->client->clientInfo, ¢->pe.torso, cent->currentState.torsoAnim );
8436 }
8437 }
8438 //else????
8439
8440 EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
8441 EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
8442
8443 // Removed by BTO (VV) - These values are crap anyway. Also changed below to use lerp instead
8444 // VectorCopy( cent->lerpOrigin, cent->rawOrigin );
8445 // VectorCopy( cent->lerpAngles, cent->rawAngles );
8446
8447 memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) );
8448 cent->pe.legs.yawAngle = cent->lerpAngles[YAW];
8449 cent->pe.legs.yawing = qfalse;
8450 cent->pe.legs.pitchAngle = 0;
8451 cent->pe.legs.pitching = qfalse;
8452
8453 memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) );
8454 cent->pe.torso.yawAngle = cent->lerpAngles[YAW];
8455 cent->pe.torso.yawing = qfalse;
8456 cent->pe.torso.pitchAngle = cent->lerpAngles[PITCH];
8457 cent->pe.torso.pitching = qfalse;
8458 }
8459