2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
7
Quake III Arena source code is free software; you can redistribute it
8
and/or modify it under the terms of the GNU General Public License as
9
published by the Free Software Foundation; either version 2 of the License,
10
or (at your option) any later version.
12
Quake III Arena source code is distributed in the hope that it will be
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with Quake III Arena source code; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
===========================================================================
23
// cg_players.c -- handle the media and animation for player entities
26
char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = {
49
sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) {
53
if ( soundName[0] != '*' ) {
54
return trap_S_RegisterSound( soundName, qfalse );
57
if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
60
ci = &cgs.clientinfo[ clientNum ];
62
for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) {
63
if ( !strcmp( soundName, cg_customSoundNames[i] ) ) {
68
CG_Error( "Unknown custom sound: %s", soundName );
75
=============================================================================
79
=============================================================================
83
======================
86
Read a configuration file containing animation coutns and rates
87
models/players/visor/animation.cfg, etc
88
======================
90
static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) {
99
animation_t *animations;
101
animations = ci->animations;
104
len = trap_FS_FOpenFile( filename, &f, FS_READ );
108
if ( len >= sizeof( text ) - 1 ) {
109
CG_Printf( "File %s too long\n", filename );
110
trap_FS_FCloseFile( f );
113
trap_FS_Read( text, len, f );
115
trap_FS_FCloseFile( f );
119
skip = 0; // quite the compiler warning
121
ci->footsteps = FOOTSTEP_NORMAL;
122
VectorClear( ci->headOffset );
123
ci->gender = GENDER_MALE;
124
ci->fixedlegs = qfalse;
125
ci->fixedtorso = qfalse;
127
// read optional parameters
129
prev = text_p; // so we can unget
130
token = COM_Parse( &text_p );
134
if ( !Q_stricmp( token, "footsteps" ) ) {
135
token = COM_Parse( &text_p );
139
if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) {
140
ci->footsteps = FOOTSTEP_NORMAL;
141
} else if ( !Q_stricmp( token, "boot" ) ) {
142
ci->footsteps = FOOTSTEP_BOOT;
143
} else if ( !Q_stricmp( token, "flesh" ) ) {
144
ci->footsteps = FOOTSTEP_FLESH;
145
} else if ( !Q_stricmp( token, "mech" ) ) {
146
ci->footsteps = FOOTSTEP_MECH;
147
} else if ( !Q_stricmp( token, "energy" ) ) {
148
ci->footsteps = FOOTSTEP_ENERGY;
150
CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token );
153
} else if ( !Q_stricmp( token, "headoffset" ) ) {
154
for ( i = 0 ; i < 3 ; i++ ) {
155
token = COM_Parse( &text_p );
159
ci->headOffset[i] = atof( token );
162
} else if ( !Q_stricmp( token, "sex" ) ) {
163
token = COM_Parse( &text_p );
167
if ( token[0] == 'f' || token[0] == 'F' ) {
168
ci->gender = GENDER_FEMALE;
169
} else if ( token[0] == 'n' || token[0] == 'N' ) {
170
ci->gender = GENDER_NEUTER;
172
ci->gender = GENDER_MALE;
175
} else if ( !Q_stricmp( token, "fixedlegs" ) ) {
176
ci->fixedlegs = qtrue;
178
} else if ( !Q_stricmp( token, "fixedtorso" ) ) {
179
ci->fixedtorso = qtrue;
183
// if it is a number, start parsing animations
184
if ( token[0] >= '0' && token[0] <= '9' ) {
185
text_p = prev; // unget the token
188
Com_Printf( "unknown token '%s' is %s\n", token, filename );
191
// read information for each frame
192
for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) {
194
token = COM_Parse( &text_p );
196
if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) {
197
animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame;
198
animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp;
199
animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp;
200
animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames;
201
animations[i].numFrames = animations[TORSO_GESTURE].numFrames;
202
animations[i].reversed = qfalse;
203
animations[i].flipflop = qfalse;
208
animations[i].firstFrame = atoi( token );
209
// leg only frames are adjusted to not count the upper body only frames
210
if ( i == LEGS_WALKCR ) {
211
skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
213
if ( i >= LEGS_WALKCR && i<TORSO_GETFLAG) {
214
animations[i].firstFrame -= skip;
217
token = COM_Parse( &text_p );
221
animations[i].numFrames = atoi( token );
223
animations[i].reversed = qfalse;
224
animations[i].flipflop = qfalse;
225
// if numFrames is negative the animation is reversed
226
if (animations[i].numFrames < 0) {
227
animations[i].numFrames = -animations[i].numFrames;
228
animations[i].reversed = qtrue;
231
token = COM_Parse( &text_p );
235
animations[i].loopFrames = atoi( token );
237
token = COM_Parse( &text_p );
245
animations[i].frameLerp = 1000 / fps;
246
animations[i].initialLerp = 1000 / fps;
249
if ( i != MAX_ANIMATIONS ) {
250
CG_Printf( "Error parsing animation file: %s", filename );
254
// crouch backward animation
255
memcpy(&animations[LEGS_BACKCR], &animations[LEGS_WALKCR], sizeof(animation_t));
256
animations[LEGS_BACKCR].reversed = qtrue;
257
// walk backward animation
258
memcpy(&animations[LEGS_BACKWALK], &animations[LEGS_WALK], sizeof(animation_t));
259
animations[LEGS_BACKWALK].reversed = qtrue;
261
animations[FLAG_RUN].firstFrame = 0;
262
animations[FLAG_RUN].numFrames = 16;
263
animations[FLAG_RUN].loopFrames = 16;
264
animations[FLAG_RUN].frameLerp = 1000 / 15;
265
animations[FLAG_RUN].initialLerp = 1000 / 15;
266
animations[FLAG_RUN].reversed = qfalse;
267
// flag not moving or moving slowly
268
animations[FLAG_STAND].firstFrame = 16;
269
animations[FLAG_STAND].numFrames = 5;
270
animations[FLAG_STAND].loopFrames = 0;
271
animations[FLAG_STAND].frameLerp = 1000 / 20;
272
animations[FLAG_STAND].initialLerp = 1000 / 20;
273
animations[FLAG_STAND].reversed = qfalse;
275
animations[FLAG_STAND2RUN].firstFrame = 16;
276
animations[FLAG_STAND2RUN].numFrames = 5;
277
animations[FLAG_STAND2RUN].loopFrames = 1;
278
animations[FLAG_STAND2RUN].frameLerp = 1000 / 15;
279
animations[FLAG_STAND2RUN].initialLerp = 1000 / 15;
280
animations[FLAG_STAND2RUN].reversed = qtrue;
284
// animations[TORSO_GETFLAG].flipflop = qtrue;
285
// animations[TORSO_GUARDBASE].flipflop = qtrue;
286
// animations[TORSO_PATROL].flipflop = qtrue;
287
// animations[TORSO_AFFIRMATIVE].flipflop = qtrue;
288
// animations[TORSO_NEGATIVE].flipflop = qtrue;
294
==========================
296
==========================
298
static qboolean CG_FileExists(const char *filename) {
301
len = trap_FS_FOpenFile( filename, NULL, FS_READ );
309
==========================
310
CG_FindClientModelFile
311
==========================
313
static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) {
314
char *team, *charactersFolder;
317
if ( cgs.gametype >= GT_TEAM ) {
318
switch ( ci->team ) {
332
charactersFolder = "";
334
for ( i = 0; i < 2; i++ ) {
335
if ( i == 0 && teamName && *teamName ) {
336
// "models/players/characters/james/stroggs/lower_lily_red.skin"
337
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext );
340
// "models/players/characters/james/lower_lily_red.skin"
341
Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext );
343
if ( CG_FileExists( filename ) ) {
346
if ( cgs.gametype >= GT_TEAM ) {
347
if ( i == 0 && teamName && *teamName ) {
348
// "models/players/characters/james/stroggs/lower_red.skin"
349
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext );
352
// "models/players/characters/james/lower_red.skin"
353
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext );
357
if ( i == 0 && teamName && *teamName ) {
358
// "models/players/characters/james/stroggs/lower_lily.skin"
359
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext );
362
// "models/players/characters/james/lower_lily.skin"
363
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext );
366
if ( CG_FileExists( filename ) ) {
369
if ( !teamName || !*teamName ) {
373
// if tried the heads folder first
374
if ( charactersFolder[0] ) {
377
charactersFolder = "characters/";
384
==========================
385
CG_FindClientHeadFile
386
==========================
388
static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) {
389
char *team, *headsFolder;
392
if ( cgs.gametype >= GT_TEAM ) {
393
switch ( ci->team ) {
408
if ( headModelName[0] == '*' ) {
409
headsFolder = "heads/";
416
for ( i = 0; i < 2; i++ ) {
417
if ( i == 0 && teamName && *teamName ) {
418
Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext );
421
Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext );
423
if ( CG_FileExists( filename ) ) {
426
if ( cgs.gametype >= GT_TEAM ) {
427
if ( i == 0 && teamName && *teamName ) {
428
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext );
431
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext );
435
if ( i == 0 && teamName && *teamName ) {
436
Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext );
439
Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext );
442
if ( CG_FileExists( filename ) ) {
445
if ( !teamName || !*teamName ) {
449
// if tried the heads folder first
450
if ( headsFolder[0] ) {
453
headsFolder = "heads/";
460
==========================
461
CG_RegisterClientSkin
462
==========================
464
static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) {
465
char filename[MAX_QPATH];
468
Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName );
469
ci->legsSkin = trap_R_RegisterSkin( filename );
471
Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName );
472
ci->legsSkin = trap_R_RegisterSkin( filename );
474
Com_Printf( "Leg skin load failure: %s\n", filename );
479
Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName );
480
ci->torsoSkin = trap_R_RegisterSkin( filename );
481
if (!ci->torsoSkin) {
482
Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName );
483
ci->torsoSkin = trap_R_RegisterSkin( filename );
484
if (!ci->torsoSkin) {
485
Com_Printf( "Torso skin load failure: %s\n", filename );
489
if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) {
490
ci->legsSkin = trap_R_RegisterSkin( filename );
493
Com_Printf( "Leg skin load failure: %s\n", filename );
496
if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) {
497
ci->torsoSkin = trap_R_RegisterSkin( filename );
499
if (!ci->torsoSkin) {
500
Com_Printf( "Torso skin load failure: %s\n", filename );
503
if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) {
504
ci->headSkin = trap_R_RegisterSkin( filename );
507
Com_Printf( "Head skin load failure: %s\n", filename );
510
// if any skins failed to load
511
if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) {
518
==========================
519
CG_RegisterClientModelname
520
==========================
522
static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) {
523
char filename[MAX_QPATH*2];
524
const char *headName;
525
char newTeamName[MAX_QPATH*2];
527
if ( headModelName[0] == '\0' ) {
528
headName = modelName;
531
headName = headModelName;
533
Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName );
534
ci->legsModel = trap_R_RegisterModel( filename );
535
if ( !ci->legsModel ) {
536
Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName );
537
ci->legsModel = trap_R_RegisterModel( filename );
538
if ( !ci->legsModel ) {
539
Com_Printf( "Failed to load model file %s\n", filename );
544
Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName );
545
ci->torsoModel = trap_R_RegisterModel( filename );
546
if ( !ci->torsoModel ) {
547
Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName );
548
ci->torsoModel = trap_R_RegisterModel( filename );
549
if ( !ci->torsoModel ) {
550
Com_Printf( "Failed to load model file %s\n", filename );
555
if( headName[0] == '*' ) {
556
Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] );
559
Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headName );
561
ci->headModel = trap_R_RegisterModel( filename );
562
// if the head model could not be found and we didn't load from the heads folder try to load from there
563
if ( !ci->headModel && headName[0] != '*' ) {
564
Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName );
565
ci->headModel = trap_R_RegisterModel( filename );
567
if ( !ci->headModel ) {
568
Com_Printf( "Failed to load model file %s\n", filename );
572
// if any skins failed to load, return failure
573
if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) {
574
if ( teamName && *teamName) {
575
Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName );
576
if( ci->team == TEAM_BLUE ) {
577
Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME);
580
Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME);
582
if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) {
583
Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName );
587
Com_Printf( "Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName );
592
// load the animations
593
Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
594
if ( !CG_ParseAnimationFile( filename, ci ) ) {
595
Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName );
596
if ( !CG_ParseAnimationFile( filename, ci ) ) {
597
Com_Printf( "Failed to load animation file %s\n", filename );
602
if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) {
603
ci->modelIcon = trap_R_RegisterShaderNoMip( filename );
605
else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) {
606
ci->modelIcon = trap_R_RegisterShaderNoMip( filename );
609
if ( !ci->modelIcon ) {
621
static void CG_ColorFromString( const char *v, vec3_t color ) {
624
VectorClear( color );
628
if ( val < 1 || val > 7 ) {
629
VectorSet( color, 1, 1, 1 );
648
Load it now, taking the disk hits.
649
This will usually be deferred to a safe time
652
static void CG_LoadClientInfo( int clientNum, clientInfo_t *ci ) {
653
const char *dir, *fallback;
656
char teamname[MAX_QPATH];
660
if( cgs.gametype >= GT_TEAM) {
661
if( ci->team == TEAM_BLUE ) {
662
Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) );
664
Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) );
668
strcat( teamname, "/" );
672
if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) {
673
if ( cg_buildScript.integer ) {
674
CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname );
677
// fall back to default team name
678
if( cgs.gametype >= GT_TEAM) {
680
if( ci->team == TEAM_BLUE ) {
681
Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) );
683
Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) );
685
if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) {
686
CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName );
689
if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) {
690
CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL );
693
modelloaded = qfalse;
696
ci->newAnims = qfalse;
697
if ( ci->torsoModel ) {
699
// if the torso model has the "tag_flag"
700
if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) {
701
ci->newAnims = qtrue;
707
fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL;
709
for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) {
710
s = cg_customSoundNames[i];
715
// if the model didn't load use the sounds of the default model
717
ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse );
719
if ( !ci->sounds[i] ) {
720
ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse );
724
ci->deferred = qfalse;
726
// reset any existing players and bodies, because they might be in bad
727
// frames for this new model
728
for ( i = 0 ; i < MAX_GENTITIES ; i++ ) {
729
if ( cg_entities[i].currentState.clientNum == clientNum
730
&& cg_entities[i].currentState.eType == ET_PLAYER ) {
731
CG_ResetPlayerEntity( &cg_entities[i] );
737
======================
738
CG_CopyClientInfoModel
739
======================
741
static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) {
742
VectorCopy( from->headOffset, to->headOffset );
743
to->footsteps = from->footsteps;
744
to->gender = from->gender;
746
to->legsModel = from->legsModel;
747
to->legsSkin = from->legsSkin;
748
to->torsoModel = from->torsoModel;
749
to->torsoSkin = from->torsoSkin;
750
to->headModel = from->headModel;
751
to->headSkin = from->headSkin;
752
to->modelIcon = from->modelIcon;
754
to->newAnims = from->newAnims;
756
memcpy( to->animations, from->animations, sizeof( to->animations ) );
757
memcpy( to->sounds, from->sounds, sizeof( to->sounds ) );
761
======================
762
CG_ScanForExistingClientInfo
763
======================
765
static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) {
769
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
770
match = &cgs.clientinfo[ i ];
771
if ( !match->infoValid ) {
774
if ( match->deferred ) {
777
if ( !Q_stricmp( ci->modelName, match->modelName )
778
&& !Q_stricmp( ci->skinName, match->skinName )
779
&& !Q_stricmp( ci->headModelName, match->headModelName )
780
&& !Q_stricmp( ci->headSkinName, match->headSkinName )
781
&& !Q_stricmp( ci->blueTeam, match->blueTeam )
782
&& !Q_stricmp( ci->redTeam, match->redTeam )
783
&& (cgs.gametype < GT_TEAM || ci->team == match->team) ) {
784
// this clientinfo is identical, so use it's handles
786
ci->deferred = qfalse;
788
CG_CopyClientInfoModel( match, ci );
794
// nothing matches, so defer the load
799
======================
800
CG_SetDeferredClientInfo
802
We aren't going to load it now, so grab some other
803
client's info to use until we have some spare time.
804
======================
806
static void CG_SetDeferredClientInfo( int clientNum, clientInfo_t *ci ) {
810
// if someone else is already the same models and skins we
811
// can just load the client info
812
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
813
match = &cgs.clientinfo[ i ];
814
if ( !match->infoValid || match->deferred ) {
817
if ( Q_stricmp( ci->skinName, match->skinName ) ||
818
Q_stricmp( ci->modelName, match->modelName ) ||
819
// Q_stricmp( ci->headModelName, match->headModelName ) ||
820
// Q_stricmp( ci->headSkinName, match->headSkinName ) ||
821
(cgs.gametype >= GT_TEAM && ci->team != match->team) ) {
824
// just load the real info cause it uses the same models and skins
825
CG_LoadClientInfo( clientNum, ci );
829
// if we are in teamplay, only grab a model if the skin is correct
830
if ( cgs.gametype >= GT_TEAM ) {
831
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
832
match = &cgs.clientinfo[ i ];
833
if ( !match->infoValid || match->deferred ) {
836
if ( Q_stricmp( ci->skinName, match->skinName ) ||
837
(cgs.gametype >= GT_TEAM && ci->team != match->team) ) {
840
ci->deferred = qtrue;
841
CG_CopyClientInfoModel( match, ci );
844
// load the full model, because we don't ever want to show
845
// an improper team skin. This will cause a hitch for the first
846
// player, when the second enters. Combat shouldn't be going on
847
// yet, so it shouldn't matter
848
CG_LoadClientInfo( clientNum, ci );
852
// find the first valid clientinfo and grab its stuff
853
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
854
match = &cgs.clientinfo[ i ];
855
if ( !match->infoValid ) {
859
ci->deferred = qtrue;
860
CG_CopyClientInfoModel( match, ci );
864
// we should never get here...
865
CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" );
867
CG_LoadClientInfo( clientNum, ci );
872
======================
874
======================
876
void CG_NewClientInfo( int clientNum ) {
878
clientInfo_t newInfo;
879
const char *configstring;
883
ci = &cgs.clientinfo[clientNum];
885
configstring = CG_ConfigString( clientNum + CS_PLAYERS );
886
if ( !configstring[0] ) {
887
memset( ci, 0, sizeof( *ci ) );
888
return; // player just left
891
// build into a temp buffer so the defer checks can use
893
memset( &newInfo, 0, sizeof( newInfo ) );
895
// isolate the player's name
896
v = Info_ValueForKey(configstring, "n");
897
Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) );
900
v = Info_ValueForKey( configstring, "c1" );
901
CG_ColorFromString( v, newInfo.color1 );
903
v = Info_ValueForKey( configstring, "c2" );
904
CG_ColorFromString( v, newInfo.color2 );
907
v = Info_ValueForKey( configstring, "skill" );
908
newInfo.botSkill = atoi( v );
911
v = Info_ValueForKey( configstring, "hc" );
912
newInfo.handicap = atoi( v );
915
v = Info_ValueForKey( configstring, "w" );
916
newInfo.wins = atoi( v );
919
v = Info_ValueForKey( configstring, "l" );
920
newInfo.losses = atoi( v );
923
v = Info_ValueForKey( configstring, "t" );
924
newInfo.team = atoi( v );
927
v = Info_ValueForKey( configstring, "tt" );
928
newInfo.teamTask = atoi(v);
931
v = Info_ValueForKey( configstring, "tl" );
932
newInfo.teamLeader = atoi(v);
934
v = Info_ValueForKey( configstring, "g_redteam" );
935
Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME);
937
v = Info_ValueForKey( configstring, "g_blueteam" );
938
Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME);
941
v = Info_ValueForKey( configstring, "model" );
942
if ( cg_forceModel.integer ) {
943
// forcemodel makes everyone use a single model
944
// to prevent load hitches
945
char modelStr[MAX_QPATH];
948
if( cgs.gametype >= GT_TEAM ) {
949
Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) );
950
Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
952
trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) );
953
if ( ( skin = strchr( modelStr, '/' ) ) == NULL) {
959
Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) );
960
Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) );
963
if ( cgs.gametype >= GT_TEAM ) {
965
slash = strchr( v, '/' );
967
Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
971
Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) );
973
slash = strchr( newInfo.modelName, '/' );
975
// modelName didn not include a skin name
976
Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
978
Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
979
// truncate modelName
985
v = Info_ValueForKey( configstring, "hmodel" );
986
if ( cg_forceModel.integer ) {
987
// forcemodel makes everyone use a single model
988
// to prevent load hitches
989
char modelStr[MAX_QPATH];
992
if( cgs.gametype >= GT_TEAM ) {
993
Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) );
994
Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) );
996
trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) );
997
if ( ( skin = strchr( modelStr, '/' ) ) == NULL) {
1003
Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) );
1004
Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) );
1007
if ( cgs.gametype >= GT_TEAM ) {
1009
slash = strchr( v, '/' );
1011
Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) );
1015
Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) );
1017
slash = strchr( newInfo.headModelName, '/' );
1019
// modelName didn not include a skin name
1020
Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) );
1022
Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) );
1023
// truncate modelName
1028
// scan for an existing clientinfo that matches this modelname
1029
// so we can avoid loading checks if possible
1030
if ( !CG_ScanForExistingClientInfo( &newInfo ) ) {
1031
qboolean forceDefer;
1033
forceDefer = trap_MemoryRemaining() < 4000000;
1035
// if we are defering loads, just have it pick the first valid
1036
if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) {
1037
// keep whatever they had if it won't violate team skins
1038
CG_SetDeferredClientInfo( clientNum, &newInfo );
1039
// if we are low on memory, leave them with this model
1041
CG_Printf( "Memory is low. Using deferred model.\n" );
1042
newInfo.deferred = qfalse;
1045
CG_LoadClientInfo( clientNum, &newInfo );
1049
// replace whatever was there with the new one
1050
newInfo.infoValid = qtrue;
1057
======================
1058
CG_LoadDeferredPlayers
1060
Called each frame when a player is dead
1061
and the scoreboard is up
1062
so deferred players can be loaded
1063
======================
1065
void CG_LoadDeferredPlayers( void ) {
1069
// scan for a deferred player to load
1070
for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) {
1071
if ( ci->infoValid && ci->deferred ) {
1072
// if we are low on memory, leave it deferred
1073
if ( trap_MemoryRemaining() < 4000000 ) {
1074
CG_Printf( "Memory is low. Using deferred model.\n" );
1075
ci->deferred = qfalse;
1078
CG_LoadClientInfo( i, ci );
1085
=============================================================================
1089
=============================================================================
1095
CG_SetLerpFrameAnimation
1097
may include ANIM_TOGGLEBIT
1100
static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
1103
lf->animationNumber = newAnimation;
1104
newAnimation &= ~ANIM_TOGGLEBIT;
1106
if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) {
1107
CG_Error( "Bad animation number: %i", newAnimation );
1110
anim = &ci->animations[ newAnimation ];
1112
lf->animation = anim;
1113
lf->animationTime = lf->frameTime + anim->initialLerp;
1115
if ( cg_debugAnim.integer ) {
1116
CG_Printf( "Anim: %i\n", newAnimation );
1124
Sets cg.snap, cg.oldFrame, and cg.backlerp
1125
cg.time should be between oldFrameTime and frameTime after exit
1128
static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) {
1132
// debugging tool to get no animations
1133
if ( cg_animSpeed.integer == 0 ) {
1134
lf->oldFrame = lf->frame = lf->backlerp = 0;
1138
// see if the animation sequence is switching
1139
if ( newAnimation != lf->animationNumber || !lf->animation ) {
1140
CG_SetLerpFrameAnimation( ci, lf, newAnimation );
1143
// if we have passed the current frame, move it to
1144
// oldFrame and calculate a new frame
1145
if ( cg.time >= lf->frameTime ) {
1146
lf->oldFrame = lf->frame;
1147
lf->oldFrameTime = lf->frameTime;
1149
// get the next frame based on the animation
1150
anim = lf->animation;
1151
if ( !anim->frameLerp ) {
1152
return; // shouldn't happen
1154
if ( cg.time < lf->animationTime ) {
1155
lf->frameTime = lf->animationTime; // initial lerp
1157
lf->frameTime = lf->oldFrameTime + anim->frameLerp;
1159
f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
1160
f *= speedScale; // adjust for haste, etc
1162
numFrames = anim->numFrames;
1163
if (anim->flipflop) {
1166
if ( f >= numFrames ) {
1168
if ( anim->loopFrames ) {
1169
f %= anim->loopFrames;
1170
f += anim->numFrames - anim->loopFrames;
1173
// the animation is stuck at the end, so it
1174
// can immediately transition to another sequence
1175
lf->frameTime = cg.time;
1178
if ( anim->reversed ) {
1179
lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
1181
else if (anim->flipflop && f>=anim->numFrames) {
1182
lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames);
1185
lf->frame = anim->firstFrame + f;
1187
if ( cg.time > lf->frameTime ) {
1188
lf->frameTime = cg.time;
1189
if ( cg_debugAnim.integer ) {
1190
CG_Printf( "Clamp lf->frameTime\n");
1195
if ( lf->frameTime > cg.time + 200 ) {
1196
lf->frameTime = cg.time;
1199
if ( lf->oldFrameTime > cg.time ) {
1200
lf->oldFrameTime = cg.time;
1202
// calculate current lerp value
1203
if ( lf->frameTime == lf->oldFrameTime ) {
1206
lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
1216
static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) {
1217
lf->frameTime = lf->oldFrameTime = cg.time;
1218
CG_SetLerpFrameAnimation( ci, lf, animationNumber );
1219
lf->oldFrame = lf->frame = lf->animation->firstFrame;
1228
static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp,
1229
int *torsoOld, int *torso, float *torsoBackLerp ) {
1234
clientNum = cent->currentState.clientNum;
1236
if ( cg_noPlayerAnims.integer ) {
1237
*legsOld = *legs = *torsoOld = *torso = 0;
1241
if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) {
1247
ci = &cgs.clientinfo[ clientNum ];
1249
// do the shuffle turn frames locally
1250
if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) {
1251
CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale );
1253
CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale );
1256
*legsOld = cent->pe.legs.oldFrame;
1257
*legs = cent->pe.legs.frame;
1258
*legsBackLerp = cent->pe.legs.backlerp;
1260
CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale );
1262
*torsoOld = cent->pe.torso.oldFrame;
1263
*torso = cent->pe.torso.frame;
1264
*torsoBackLerp = cent->pe.torso.backlerp;
1268
=============================================================================
1272
=============================================================================
1280
static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance,
1281
float speed, float *angle, qboolean *swinging ) {
1287
// see if a swing should be started
1288
swing = AngleSubtract( *angle, destination );
1289
if ( swing > swingTolerance || swing < -swingTolerance ) {
1298
// modify the speed depending on the delta
1299
// so it doesn't seem so linear
1300
swing = AngleSubtract( destination, *angle );
1301
scale = fabs( swing );
1302
if ( scale < swingTolerance * 0.5 ) {
1304
} else if ( scale < swingTolerance ) {
1310
// swing towards the destination angle
1312
move = cg.frametime * scale * speed;
1313
if ( move >= swing ) {
1317
*angle = AngleMod( *angle + move );
1318
} else if ( swing < 0 ) {
1319
move = cg.frametime * scale * -speed;
1320
if ( move <= swing ) {
1324
*angle = AngleMod( *angle + move );
1327
// clamp to no more than tolerance
1328
swing = AngleSubtract( destination, *angle );
1329
if ( swing > clampTolerance ) {
1330
*angle = AngleMod( destination - (clampTolerance - 1) );
1331
} else if ( swing < -clampTolerance ) {
1332
*angle = AngleMod( destination + (clampTolerance - 1) );
1341
static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) {
1345
t = cg.time - cent->pe.painTime;
1346
if ( t >= PAIN_TWITCH_TIME ) {
1350
f = 1.0 - (float)t / PAIN_TWITCH_TIME;
1352
if ( cent->pe.painDirection ) {
1353
torsoAngles[ROLL] += 20 * f;
1355
torsoAngles[ROLL] -= 20 * f;
1364
Handles seperate torso motion
1366
legs pivot based on direction of movement
1368
head always looks exactly at cent->lerpAngles
1370
if motion < 20 degrees, show in head only
1371
if < 45 degrees, also show in torso
1374
static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
1375
vec3_t legsAngles, torsoAngles, headAngles;
1377
static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 };
1383
VectorCopy( cent->lerpAngles, headAngles );
1384
headAngles[YAW] = AngleMod( headAngles[YAW] );
1385
VectorClear( legsAngles );
1386
VectorClear( torsoAngles );
1388
// --------- yaw -------------
1390
// allow yaw to drift a bit
1391
if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE
1392
|| ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) {
1393
// if not standing still, always point all in the same direction
1394
cent->pe.torso.yawing = qtrue; // always center
1395
cent->pe.torso.pitching = qtrue; // always center
1396
cent->pe.legs.yawing = qtrue; // always center
1399
// adjust legs for movement dir
1400
if ( cent->currentState.eFlags & EF_DEAD ) {
1401
// don't let dead bodies twitch
1404
dir = cent->currentState.angles2[YAW];
1405
if ( dir < 0 || dir > 7 ) {
1406
CG_Error( "Bad player movement angle" );
1409
legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ];
1410
torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ];
1413
CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing );
1414
CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing );
1416
torsoAngles[YAW] = cent->pe.torso.yawAngle;
1417
legsAngles[YAW] = cent->pe.legs.yawAngle;
1420
// --------- pitch -------------
1422
// only show a fraction of the pitch angle in the torso
1423
if ( headAngles[PITCH] > 180 ) {
1424
dest = (-360 + headAngles[PITCH]) * 0.75f;
1426
dest = headAngles[PITCH] * 0.75f;
1428
CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching );
1429
torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
1432
clientNum = cent->currentState.clientNum;
1433
if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) {
1434
ci = &cgs.clientinfo[ clientNum ];
1435
if ( ci->fixedtorso ) {
1436
torsoAngles[PITCH] = 0.0f;
1440
// --------- roll -------------
1443
// lean towards the direction of travel
1444
VectorCopy( cent->currentState.pos.trDelta, velocity );
1445
speed = VectorNormalize( velocity );
1452
AnglesToAxis( legsAngles, axis );
1453
side = speed * DotProduct( velocity, axis[1] );
1454
legsAngles[ROLL] -= side;
1456
side = speed * DotProduct( velocity, axis[0] );
1457
legsAngles[PITCH] += side;
1461
clientNum = cent->currentState.clientNum;
1462
if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) {
1463
ci = &cgs.clientinfo[ clientNum ];
1464
if ( ci->fixedlegs ) {
1465
legsAngles[YAW] = torsoAngles[YAW];
1466
legsAngles[PITCH] = 0.0f;
1467
legsAngles[ROLL] = 0.0f;
1472
CG_AddPainTwitch( cent, torsoAngles );
1474
// pull the angles back out of the hierarchial chain
1475
AnglesSubtract( headAngles, torsoAngles, headAngles );
1476
AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
1477
AnglesToAxis( legsAngles, legs );
1478
AnglesToAxis( torsoAngles, torso );
1479
AnglesToAxis( headAngles, head );
1483
//==========================================================================
1490
static void CG_HasteTrail( centity_t *cent ) {
1491
localEntity_t *smoke;
1495
if ( cent->trailTime > cg.time ) {
1498
anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT;
1499
if ( anim != LEGS_RUN && anim != LEGS_BACK ) {
1503
cent->trailTime += 100;
1504
if ( cent->trailTime < cg.time ) {
1505
cent->trailTime = cg.time;
1508
VectorCopy( cent->lerpOrigin, origin );
1511
smoke = CG_SmokePuff( origin, vec3_origin,
1518
cgs.media.hastePuffShader );
1520
// use the optimized local entity add
1521
smoke->leType = LE_SCALE_FADE;
1530
static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) {
1535
ci = &cgs.clientinfo[ cent->currentState.number ];
1537
if (!cg_enableBreath.integer) {
1540
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) {
1543
if ( cent->currentState.eFlags & EF_DEAD ) {
1546
contents = trap_CM_PointContents( head->origin, 0 );
1547
if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
1550
if ( ci->breathPuffTime > cg.time ) {
1554
VectorSet( up, 0, 0, 8 );
1555
VectorMA(head->origin, 8, head->axis[0], origin);
1556
VectorMA(origin, -4, head->axis[2], origin);
1557
CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader );
1558
ci->breathPuffTime = cg.time + 2000;
1566
static void CG_DustTrail( centity_t *cent ) {
1568
localEntity_t *dust;
1572
if (!cg_enableDust.integer)
1575
if ( cent->dustTrailTime > cg.time ) {
1579
anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT;
1580
if ( anim != LEGS_LANDB && anim != LEGS_LAND ) {
1584
cent->dustTrailTime += 40;
1585
if ( cent->dustTrailTime < cg.time ) {
1586
cent->dustTrailTime = cg.time;
1589
VectorCopy(cent->currentState.pos.trBase, end);
1591
CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID );
1593
if ( !(tr.surfaceFlags & SURF_DUST) )
1596
VectorCopy( cent->currentState.pos.trBase, end );
1599
VectorSet(vel, 0, 0, -30);
1600
dust = CG_SmokePuff( end, vel,
1602
.8f, .8f, 0.7f, 0.33f,
1607
cgs.media.dustPuffShader );
1617
static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) {
1622
VectorCopy( cent->lerpAngles, angles );
1625
AnglesToAxis( angles, axis );
1627
memset( &ent, 0, sizeof( ent ) );
1628
VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin );
1629
ent.origin[2] += 16;
1631
AnglesToAxis( angles, ent.axis );
1633
ent.hModel = hModel;
1634
trap_R_AddRefEntityToScene( &ent );
1643
static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) {
1648
int legsAnim, flagAnim, updateangles;
1651
// show the flag pole model
1652
memset( &pole, 0, sizeof(pole) );
1653
pole.hModel = cgs.media.flagPoleModel;
1654
VectorCopy( torso->lightingOrigin, pole.lightingOrigin );
1655
pole.shadowPlane = torso->shadowPlane;
1656
pole.renderfx = torso->renderfx;
1657
CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" );
1658
trap_R_AddRefEntityToScene( &pole );
1660
// show the flag model
1661
memset( &flag, 0, sizeof(flag) );
1662
flag.hModel = cgs.media.flagFlapModel;
1663
flag.customSkin = hSkin;
1664
VectorCopy( torso->lightingOrigin, flag.lightingOrigin );
1665
flag.shadowPlane = torso->shadowPlane;
1666
flag.renderfx = torso->renderfx;
1668
VectorClear(angles);
1670
updateangles = qfalse;
1671
legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
1672
if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) {
1673
flagAnim = FLAG_STAND;
1674
} else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) {
1675
flagAnim = FLAG_STAND;
1676
updateangles = qtrue;
1678
flagAnim = FLAG_RUN;
1679
updateangles = qtrue;
1682
if ( updateangles ) {
1684
VectorCopy( cent->currentState.pos.trDelta, dir );
1687
VectorNormalize( dir );
1688
d = DotProduct(pole.axis[2], dir);
1689
// if there is anough movement orthogonal to the flag pole
1690
if (fabs(d) < 0.9) {
1692
d = DotProduct(pole.axis[0], dir);
1696
else if (d < -1.0f) {
1701
d = DotProduct(pole.axis[1], dir);
1703
angles[YAW] = 360 - angle * 180 / M_PI;
1706
angles[YAW] = angle * 180 / M_PI;
1708
if (angles[YAW] < 0)
1710
if (angles[YAW] > 360)
1713
//vectoangles( cent->currentState.pos.trDelta, tmpangles );
1714
//angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle;
1715
// change the yaw angle
1716
CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing );
1720
d = DotProduct(pole.axis[2], dir);
1723
d = DotProduct(pole.axis[1], dir);
1725
angle = 360 - angle * 180 / M_PI;
1728
angle = angle * 180 / M_PI;
1730
if (angle > 340 && angle < 20) {
1731
flagAnim = FLAG_RUNUP;
1733
if (angle > 160 && angle < 200) {
1734
flagAnim = FLAG_RUNDOWN;
1739
// set the yaw angle
1740
angles[YAW] = cent->pe.flag.yawAngle;
1741
// lerp the flag animation frames
1742
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
1743
CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 );
1744
flag.oldframe = cent->pe.flag.oldFrame;
1745
flag.frame = cent->pe.flag.frame;
1746
flag.backlerp = cent->pe.flag.backlerp;
1748
AnglesToAxis( angles, flag.axis );
1749
CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" );
1751
trap_R_AddRefEntityToScene( &flag );
1755
#ifdef MISSIONPACK // bk001204
1761
static void CG_PlayerTokens( centity_t *cent, int renderfx ) {
1766
skulltrail_t *trail;
1767
trail = &cg.skulltrails[cent->currentState.number];
1768
tokens = cent->currentState.generic1;
1770
trail->numpositions = 0;
1774
if ( tokens > MAX_SKULLTRAIL ) {
1775
tokens = MAX_SKULLTRAIL;
1778
// add skulls if there are more than last time
1779
for (i = 0; i < tokens - trail->numpositions; i++) {
1780
for (j = trail->numpositions; j > 0; j--) {
1781
VectorCopy(trail->positions[j-1], trail->positions[j]);
1783
VectorCopy(cent->lerpOrigin, trail->positions[0]);
1785
trail->numpositions = tokens;
1787
// move all the skulls along the trail
1788
VectorCopy(cent->lerpOrigin, origin);
1789
for (i = 0; i < trail->numpositions; i++) {
1790
VectorSubtract(trail->positions[i], origin, dir);
1791
if (VectorNormalize(dir) > 30) {
1792
VectorMA(origin, 30, dir, trail->positions[i]);
1794
VectorCopy(trail->positions[i], origin);
1797
memset( &ent, 0, sizeof( ent ) );
1798
if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) {
1799
ent.hModel = cgs.media.redCubeModel;
1801
ent.hModel = cgs.media.blueCubeModel;
1803
ent.renderfx = renderfx;
1805
VectorCopy(cent->lerpOrigin, origin);
1806
for (i = 0; i < trail->numpositions; i++) {
1807
VectorSubtract(origin, trail->positions[i], ent.axis[0]);
1809
VectorNormalize(ent.axis[0]);
1810
VectorSet(ent.axis[2], 0, 0, 1);
1811
CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]);
1813
VectorCopy(trail->positions[i], ent.origin);
1814
angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255;
1815
ent.origin[2] += sin(angle) * 10;
1816
trap_R_AddRefEntityToScene( &ent );
1817
VectorCopy(trail->positions[i], origin);
1828
static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) {
1832
powerups = cent->currentState.powerups;
1837
// quad gives a dlight
1838
if ( powerups & ( 1 << PW_QUAD ) ) {
1839
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 );
1842
// flight plays a looped sound
1843
if ( powerups & ( 1 << PW_FLIGHT ) ) {
1844
trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound );
1847
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
1849
if ( powerups & ( 1 << PW_REDFLAG ) ) {
1851
CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso );
1854
CG_TrailItem( cent, cgs.media.redFlagModel );
1856
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f );
1860
if ( powerups & ( 1 << PW_BLUEFLAG ) ) {
1862
CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso );
1865
CG_TrailItem( cent, cgs.media.blueFlagModel );
1867
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 );
1871
if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) {
1873
CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso );
1876
CG_TrailItem( cent, cgs.media.neutralFlagModel );
1878
trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 );
1881
// haste leaves smoke trails
1882
if ( powerups & ( 1 << PW_HASTE ) ) {
1883
CG_HasteTrail( cent );
1890
CG_PlayerFloatSprite
1892
Float a sprite over the player's head
1895
static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) {
1899
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) {
1900
rf = RF_THIRD_PERSON; // only show in mirrors
1905
memset( &ent, 0, sizeof( ent ) );
1906
VectorCopy( cent->lerpOrigin, ent.origin );
1907
ent.origin[2] += 48;
1908
ent.reType = RT_SPRITE;
1909
ent.customShader = shader;
1912
ent.shaderRGBA[0] = 255;
1913
ent.shaderRGBA[1] = 255;
1914
ent.shaderRGBA[2] = 255;
1915
ent.shaderRGBA[3] = 255;
1916
trap_R_AddRefEntityToScene( &ent );
1925
Float sprites over the player's head
1928
static void CG_PlayerSprites( centity_t *cent ) {
1931
if ( cent->currentState.eFlags & EF_CONNECTION ) {
1932
CG_PlayerFloatSprite( cent, cgs.media.connectionShader );
1936
if ( cent->currentState.eFlags & EF_TALK ) {
1937
CG_PlayerFloatSprite( cent, cgs.media.balloonShader );
1941
if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) {
1942
CG_PlayerFloatSprite( cent, cgs.media.medalImpressive );
1946
if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) {
1947
CG_PlayerFloatSprite( cent, cgs.media.medalExcellent );
1951
if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) {
1952
CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet );
1956
if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) {
1957
CG_PlayerFloatSprite( cent, cgs.media.medalDefend );
1961
if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) {
1962
CG_PlayerFloatSprite( cent, cgs.media.medalAssist );
1966
if ( cent->currentState.eFlags & EF_AWARD_CAP ) {
1967
CG_PlayerFloatSprite( cent, cgs.media.medalCapture );
1971
team = cgs.clientinfo[ cent->currentState.clientNum ].team;
1972
if ( !(cent->currentState.eFlags & EF_DEAD) &&
1973
cg.snap->ps.persistant[PERS_TEAM] == team &&
1974
cgs.gametype >= GT_TEAM) {
1975
if (cg_drawFriend.integer) {
1976
CG_PlayerFloatSprite( cent, cgs.media.friendShader );
1986
Returns the Z component of the surface being shadowed
1988
should it return a full plane instead of a Z?
1991
#define SHADOW_DISTANCE 128
1992
static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) {
1993
vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2};
1999
if ( cg_shadows.integer == 0 ) {
2003
// no shadows when invisible
2004
if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) {
2008
// send a trace down from the player to the ground
2009
VectorCopy( cent->lerpOrigin, end );
2010
end[2] -= SHADOW_DISTANCE;
2012
trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
2014
// no shadow if too high
2015
if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) {
2019
*shadowPlane = trace.endpos[2] + 1;
2021
if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows
2025
// fade the shadow out with height
2026
alpha = 1.0 - trace.fraction;
2028
// bk0101022 - hack / FPE - bogus planes?
2029
//assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f )
2031
// add the mark as a temporary, so it goes directly to the renderer
2032
// without taking a spot in the cg_marks array
2033
CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal,
2034
cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue );
2044
Draw a mark at the water surface
2047
static void CG_PlayerSplash( centity_t *cent ) {
2051
polyVert_t verts[4];
2053
if ( !cg_shadows.integer ) {
2057
VectorCopy( cent->lerpOrigin, end );
2060
// if the feet aren't in liquid, don't make a mark
2061
// this won't handle moving water brushes, but they wouldn't draw right anyway...
2062
contents = trap_CM_PointContents( end, 0 );
2063
if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) {
2067
VectorCopy( cent->lerpOrigin, start );
2070
// if the head isn't out of liquid, don't make a mark
2071
contents = trap_CM_PointContents( start, 0 );
2072
if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
2076
// trace down to find the surface
2077
trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) );
2079
if ( trace.fraction == 1.0 ) {
2083
// create a mark polygon
2084
VectorCopy( trace.endpos, verts[0].xyz );
2085
verts[0].xyz[0] -= 32;
2086
verts[0].xyz[1] -= 32;
2089
verts[0].modulate[0] = 255;
2090
verts[0].modulate[1] = 255;
2091
verts[0].modulate[2] = 255;
2092
verts[0].modulate[3] = 255;
2094
VectorCopy( trace.endpos, verts[1].xyz );
2095
verts[1].xyz[0] -= 32;
2096
verts[1].xyz[1] += 32;
2099
verts[1].modulate[0] = 255;
2100
verts[1].modulate[1] = 255;
2101
verts[1].modulate[2] = 255;
2102
verts[1].modulate[3] = 255;
2104
VectorCopy( trace.endpos, verts[2].xyz );
2105
verts[2].xyz[0] += 32;
2106
verts[2].xyz[1] += 32;
2109
verts[2].modulate[0] = 255;
2110
verts[2].modulate[1] = 255;
2111
verts[2].modulate[2] = 255;
2112
verts[2].modulate[3] = 255;
2114
VectorCopy( trace.endpos, verts[3].xyz );
2115
verts[3].xyz[0] += 32;
2116
verts[3].xyz[1] -= 32;
2119
verts[3].modulate[0] = 255;
2120
verts[3].modulate[1] = 255;
2121
verts[3].modulate[2] = 255;
2122
verts[3].modulate[3] = 255;
2124
trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts );
2131
CG_AddRefEntityWithPowerups
2133
Adds a piece with modifications or duplications for powerups
2134
Also called by CG_Missile for quad rockets, but nobody can tell...
2137
void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) {
2139
if ( state->powerups & ( 1 << PW_INVIS ) ) {
2140
ent->customShader = cgs.media.invisShader;
2141
trap_R_AddRefEntityToScene( ent );
2144
if ( state->eFlags & EF_KAMIKAZE ) {
2145
if (team == TEAM_BLUE)
2146
ent->customShader = cgs.media.blueKamikazeShader;
2148
ent->customShader = cgs.media.redKamikazeShader;
2149
trap_R_AddRefEntityToScene( ent );
2152
trap_R_AddRefEntityToScene( ent );
2155
if ( state->powerups & ( 1 << PW_QUAD ) )
2157
if (team == TEAM_RED)
2158
ent->customShader = cgs.media.redQuadShader;
2160
ent->customShader = cgs.media.quadShader;
2161
trap_R_AddRefEntityToScene( ent );
2163
if ( state->powerups & ( 1 << PW_REGEN ) ) {
2164
if ( ( ( cg.time / 100 ) % 10 ) == 1 ) {
2165
ent->customShader = cgs.media.regenShader;
2166
trap_R_AddRefEntityToScene( ent );
2169
if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) {
2170
ent->customShader = cgs.media.battleSuitShader;
2171
trap_R_AddRefEntityToScene( ent );
2181
int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts )
2185
vec3_t ambientLight;
2187
vec3_t directedLight;
2189
trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir );
2191
for (i = 0; i < numVerts; i++) {
2192
incoming = DotProduct (normal, lightDir);
2193
if ( incoming <= 0 ) {
2194
verts[i].modulate[0] = ambientLight[0];
2195
verts[i].modulate[1] = ambientLight[1];
2196
verts[i].modulate[2] = ambientLight[2];
2197
verts[i].modulate[3] = 255;
2200
j = ( ambientLight[0] + incoming * directedLight[0] );
2204
verts[i].modulate[0] = j;
2206
j = ( ambientLight[1] + incoming * directedLight[1] );
2210
verts[i].modulate[1] = j;
2212
j = ( ambientLight[2] + incoming * directedLight[2] );
2216
verts[i].modulate[2] = j;
2218
verts[i].modulate[3] = 255;
2228
void CG_Player( centity_t *cent ) {
2239
refEntity_t powerup;
2246
// the client number is stored in clientNum. It can't be derived
2247
// from the entity number, because a single client may have
2248
// multiple corpses on the level using the same clientinfo
2249
clientNum = cent->currentState.clientNum;
2250
if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
2251
CG_Error( "Bad clientNum on player entity");
2253
ci = &cgs.clientinfo[ clientNum ];
2255
// it is possible to see corpses from disconnected players that may
2256
// not have valid clientinfo
2257
if ( !ci->infoValid ) {
2261
// get the player model information
2263
if ( cent->currentState.number == cg.snap->ps.clientNum) {
2264
if (!cg.renderingThirdPerson) {
2265
renderfx = RF_THIRD_PERSON; // only draw in mirrors
2267
if (cg_cameraMode.integer) {
2274
memset( &legs, 0, sizeof(legs) );
2275
memset( &torso, 0, sizeof(torso) );
2276
memset( &head, 0, sizeof(head) );
2278
// get the rotation information
2279
CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis );
2281
// get the animation state (after rotation, to allow feet shuffle)
2282
CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp,
2283
&torso.oldframe, &torso.frame, &torso.backlerp );
2285
// add the talk baloon or disconnect icon
2286
CG_PlayerSprites( cent );
2289
shadow = CG_PlayerShadow( cent, &shadowPlane );
2291
// add a water splash if partially in and out of water
2292
CG_PlayerSplash( cent );
2294
if ( cg_shadows.integer == 3 && shadow ) {
2295
renderfx |= RF_SHADOW_PLANE;
2297
renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
2299
if( cgs.gametype == GT_HARVESTER ) {
2300
CG_PlayerTokens( cent, renderfx );
2306
legs.hModel = ci->legsModel;
2307
legs.customSkin = ci->legsSkin;
2309
VectorCopy( cent->lerpOrigin, legs.origin );
2311
VectorCopy( cent->lerpOrigin, legs.lightingOrigin );
2312
legs.shadowPlane = shadowPlane;
2313
legs.renderfx = renderfx;
2314
VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all
2316
CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team );
2318
// if the model failed, allow the default nullmodel to be displayed
2326
torso.hModel = ci->torsoModel;
2327
if (!torso.hModel) {
2331
torso.customSkin = ci->torsoSkin;
2333
VectorCopy( cent->lerpOrigin, torso.lightingOrigin );
2335
CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso");
2337
torso.shadowPlane = shadowPlane;
2338
torso.renderfx = renderfx;
2340
CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team );
2343
if ( cent->currentState.eFlags & EF_KAMIKAZE ) {
2345
memset( &skull, 0, sizeof(skull) );
2347
VectorCopy( cent->lerpOrigin, skull.lightingOrigin );
2348
skull.shadowPlane = shadowPlane;
2349
skull.renderfx = renderfx;
2351
if ( cent->currentState.eFlags & EF_DEAD ) {
2352
// one skull bobbing above the dead body
2353
angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255;
2354
if (angle > M_PI * 2)
2355
angle -= (float)M_PI * 2;
2356
dir[0] = sin(angle) * 20;
2357
dir[1] = cos(angle) * 20;
2358
angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255;
2359
dir[2] = 15 + sin(angle) * 8;
2360
VectorAdd(torso.origin, dir, skull.origin);
2363
VectorCopy(dir, skull.axis[1]);
2364
VectorNormalize(skull.axis[1]);
2365
VectorSet(skull.axis[2], 0, 0, 1);
2366
CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);
2368
skull.hModel = cgs.media.kamikazeHeadModel;
2369
trap_R_AddRefEntityToScene( &skull );
2370
skull.hModel = cgs.media.kamikazeHeadTrail;
2371
trap_R_AddRefEntityToScene( &skull );
2374
// three skulls spinning around the player
2375
angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255;
2376
dir[0] = cos(angle) * 20;
2377
dir[1] = sin(angle) * 20;
2378
dir[2] = cos(angle) * 20;
2379
VectorAdd(torso.origin, dir, skull.origin);
2381
angles[0] = sin(angle) * 30;
2382
angles[1] = (angle * 180 / M_PI) + 90;
2383
if (angles[1] > 360)
2386
AnglesToAxis( angles, skull.axis );
2391
VectorCopy(dir, skull.axis[1]);
2392
VectorNormalize(skull.axis[1]);
2393
VectorSet(skull.axis[2], 0, 0, 1);
2394
CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);
2397
skull.hModel = cgs.media.kamikazeHeadModel;
2398
trap_R_AddRefEntityToScene( &skull );
2399
// flip the trail because this skull is spinning in the other direction
2400
VectorInverse(skull.axis[1]);
2401
skull.hModel = cgs.media.kamikazeHeadTrail;
2402
trap_R_AddRefEntityToScene( &skull );
2404
angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI;
2405
if (angle > M_PI * 2)
2406
angle -= (float)M_PI * 2;
2407
dir[0] = sin(angle) * 20;
2408
dir[1] = cos(angle) * 20;
2409
dir[2] = cos(angle) * 20;
2410
VectorAdd(torso.origin, dir, skull.origin);
2412
angles[0] = cos(angle - 0.5 * M_PI) * 30;
2413
angles[1] = 360 - (angle * 180 / M_PI);
2414
if (angles[1] > 360)
2417
AnglesToAxis( angles, skull.axis );
2421
VectorCopy(dir, skull.axis[1]);
2422
VectorNormalize(skull.axis[1]);
2423
VectorSet(skull.axis[2], 0, 0, 1);
2424
CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);
2427
skull.hModel = cgs.media.kamikazeHeadModel;
2428
trap_R_AddRefEntityToScene( &skull );
2429
skull.hModel = cgs.media.kamikazeHeadTrail;
2430
trap_R_AddRefEntityToScene( &skull );
2432
angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI;
2433
if (angle > M_PI * 2)
2434
angle -= (float)M_PI * 2;
2435
dir[0] = sin(angle) * 20;
2436
dir[1] = cos(angle) * 20;
2438
VectorAdd(torso.origin, dir, skull.origin);
2440
VectorCopy(dir, skull.axis[1]);
2441
VectorNormalize(skull.axis[1]);
2442
VectorSet(skull.axis[2], 0, 0, 1);
2443
CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);
2445
skull.hModel = cgs.media.kamikazeHeadModel;
2446
trap_R_AddRefEntityToScene( &skull );
2447
skull.hModel = cgs.media.kamikazeHeadTrail;
2448
trap_R_AddRefEntityToScene( &skull );
2452
if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) {
2453
memcpy(&powerup, &torso, sizeof(torso));
2454
powerup.hModel = cgs.media.guardPowerupModel;
2456
powerup.oldframe = 0;
2457
powerup.customSkin = 0;
2458
trap_R_AddRefEntityToScene( &powerup );
2460
if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) {
2461
memcpy(&powerup, &torso, sizeof(torso));
2462
powerup.hModel = cgs.media.scoutPowerupModel;
2464
powerup.oldframe = 0;
2465
powerup.customSkin = 0;
2466
trap_R_AddRefEntityToScene( &powerup );
2468
if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) {
2469
memcpy(&powerup, &torso, sizeof(torso));
2470
powerup.hModel = cgs.media.doublerPowerupModel;
2472
powerup.oldframe = 0;
2473
powerup.customSkin = 0;
2474
trap_R_AddRefEntityToScene( &powerup );
2476
if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) {
2477
memcpy(&powerup, &torso, sizeof(torso));
2478
powerup.hModel = cgs.media.ammoRegenPowerupModel;
2480
powerup.oldframe = 0;
2481
powerup.customSkin = 0;
2482
trap_R_AddRefEntityToScene( &powerup );
2484
if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) {
2485
if ( !ci->invulnerabilityStartTime ) {
2486
ci->invulnerabilityStartTime = cg.time;
2488
ci->invulnerabilityStopTime = cg.time;
2491
ci->invulnerabilityStartTime = 0;
2493
if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) ||
2494
cg.time - ci->invulnerabilityStopTime < 250 ) {
2496
memcpy(&powerup, &torso, sizeof(torso));
2497
powerup.hModel = cgs.media.invulnerabilityPowerupModel;
2498
powerup.customSkin = 0;
2500
powerup.renderfx &= ~RF_THIRD_PERSON;
2501
VectorCopy(cent->lerpOrigin, powerup.origin);
2503
if ( cg.time - ci->invulnerabilityStartTime < 250 ) {
2504
c = (float) (cg.time - ci->invulnerabilityStartTime) / 250;
2506
else if (cg.time - ci->invulnerabilityStopTime < 250 ) {
2507
c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250;
2512
VectorSet( powerup.axis[0], c, 0, 0 );
2513
VectorSet( powerup.axis[1], 0, c, 0 );
2514
VectorSet( powerup.axis[2], 0, 0, c );
2515
trap_R_AddRefEntityToScene( &powerup );
2518
t = cg.time - ci->medkitUsageTime;
2519
if ( ci->medkitUsageTime && t < 500 ) {
2520
memcpy(&powerup, &torso, sizeof(torso));
2521
powerup.hModel = cgs.media.medkitUsageModel;
2522
powerup.customSkin = 0;
2524
powerup.renderfx &= ~RF_THIRD_PERSON;
2525
VectorClear(angles);
2526
AnglesToAxis(angles, powerup.axis);
2527
VectorCopy(cent->lerpOrigin, powerup.origin);
2528
powerup.origin[2] += -24 + (float) t * 80 / 500;
2530
c = (float) (t - 1000) * 0xff / 100;
2531
powerup.shaderRGBA[0] = 0xff - c;
2532
powerup.shaderRGBA[1] = 0xff - c;
2533
powerup.shaderRGBA[2] = 0xff - c;
2534
powerup.shaderRGBA[3] = 0xff - c;
2537
powerup.shaderRGBA[0] = 0xff;
2538
powerup.shaderRGBA[1] = 0xff;
2539
powerup.shaderRGBA[2] = 0xff;
2540
powerup.shaderRGBA[3] = 0xff;
2542
trap_R_AddRefEntityToScene( &powerup );
2544
#endif // MISSIONPACK
2549
head.hModel = ci->headModel;
2553
head.customSkin = ci->headSkin;
2555
VectorCopy( cent->lerpOrigin, head.lightingOrigin );
2557
CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head");
2559
head.shadowPlane = shadowPlane;
2560
head.renderfx = renderfx;
2562
CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team );
2565
CG_BreathPuffs(cent, &head);
2571
// add the gun / barrel / flash
2573
CG_AddPlayerWeapon( &torso, NULL, cent, ci->team );
2575
// add powerups floating behind the player
2576
CG_PlayerPowerups( cent, &torso );
2580
//=====================================================================
2584
CG_ResetPlayerEntity
2586
A player just came into view or teleported, so reset all animation info
2589
void CG_ResetPlayerEntity( centity_t *cent ) {
2590
cent->errorTime = -99999; // guarantee no error decay added
2591
cent->extrapolated = qfalse;
2593
CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim );
2594
CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim );
2596
BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin );
2597
BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles );
2599
VectorCopy( cent->lerpOrigin, cent->rawOrigin );
2600
VectorCopy( cent->lerpAngles, cent->rawAngles );
2602
memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) );
2603
cent->pe.legs.yawAngle = cent->rawAngles[YAW];
2604
cent->pe.legs.yawing = qfalse;
2605
cent->pe.legs.pitchAngle = 0;
2606
cent->pe.legs.pitching = qfalse;
2608
memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) );
2609
cent->pe.torso.yawAngle = cent->rawAngles[YAW];
2610
cent->pe.torso.yawing = qfalse;
2611
cent->pe.torso.pitchAngle = cent->rawAngles[PITCH];
2612
cent->pe.torso.pitching = qfalse;
2614
if ( cg_debugPosition.integer ) {
2615
CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );