1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
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.
11
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.
16
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 ===========================================================================
21 */
22 //
23 // ui_players.c
24
25 #include "ui_local.h"
26
27
28 #define UI_TIMER_GESTURE 2300
29 #define UI_TIMER_JUMP 1000
30 #define UI_TIMER_LAND 130
31 #define UI_TIMER_WEAPON_SWITCH 300
32 #define UI_TIMER_ATTACK 500
33 #define UI_TIMER_MUZZLE_FLASH 20
34 #define UI_TIMER_WEAPON_DELAY 250
35
36 #define JUMP_HEIGHT 56
37
38 #define SWINGSPEED 0.3f
39
40 #define SPIN_SPEED 0.9f
41 #define COAST_TIME 1000
42
43
44 static int dp_realtime;
45 static float jumpHeight;
46 sfxHandle_t weaponChangeSound;
47
48
49 /*
50 ===============
51 UI_PlayerInfo_SetWeapon
52 ===============
53 */
UI_PlayerInfo_SetWeapon(playerInfo_t * pi,weapon_t weaponNum)54 static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) {
55 gitem_t * item;
56 char path[MAX_QPATH];
57
58 pi->currentWeapon = weaponNum;
59 tryagain:
60 pi->realWeapon = weaponNum;
61 pi->weaponModel = 0;
62 pi->barrelModel = 0;
63 pi->flashModel = 0;
64
65 if ( weaponNum == WP_NONE ) {
66 return;
67 }
68
69 for ( item = bg_itemlist + 1; item->classname ; item++ ) {
70 if ( item->giType != IT_WEAPON ) {
71 continue;
72 }
73 if ( item->giTag == weaponNum ) {
74 break;
75 }
76 }
77
78 if ( item->classname ) {
79 pi->weaponModel = trap_R_RegisterModel( item->world_model[0] );
80 }
81
82 if( pi->weaponModel == 0 ) {
83 if( weaponNum == WP_MACHINEGUN ) {
84 weaponNum = WP_NONE;
85 goto tryagain;
86 }
87 weaponNum = WP_MACHINEGUN;
88 goto tryagain;
89 }
90
91 if ( weaponNum == WP_MACHINEGUN || weaponNum == WP_GAUNTLET || weaponNum == WP_BFG ) {
92 strcpy( path, item->world_model[0] );
93 COM_StripExtension(path, path, sizeof(path));
94 strcat( path, "_barrel.md3" );
95 pi->barrelModel = trap_R_RegisterModel( path );
96 }
97
98 strcpy( path, item->world_model[0] );
99 COM_StripExtension(path, path, sizeof(path));
100 strcat( path, "_flash.md3" );
101 pi->flashModel = trap_R_RegisterModel( path );
102
103 switch( weaponNum ) {
104 case WP_GAUNTLET:
105 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
106 break;
107
108 case WP_MACHINEGUN:
109 MAKERGB( pi->flashDlightColor, 1, 1, 0 );
110 break;
111
112 case WP_SHOTGUN:
113 MAKERGB( pi->flashDlightColor, 1, 1, 0 );
114 break;
115
116 case WP_GRENADE_LAUNCHER:
117 MAKERGB( pi->flashDlightColor, 1, 0.7f, 0.5f );
118 break;
119
120 case WP_ROCKET_LAUNCHER:
121 MAKERGB( pi->flashDlightColor, 1, 0.75f, 0 );
122 break;
123
124 case WP_LIGHTNING:
125 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
126 break;
127
128 case WP_RAILGUN:
129 MAKERGB( pi->flashDlightColor, 1, 0.5f, 0 );
130 break;
131
132 case WP_PLASMAGUN:
133 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
134 break;
135
136 case WP_BFG:
137 MAKERGB( pi->flashDlightColor, 1, 0.7f, 1 );
138 break;
139
140 case WP_GRAPPLING_HOOK:
141 MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
142 break;
143
144 default:
145 MAKERGB( pi->flashDlightColor, 1, 1, 1 );
146 break;
147 }
148 }
149
150
151 /*
152 ===============
153 UI_ForceLegsAnim
154 ===============
155 */
UI_ForceLegsAnim(playerInfo_t * pi,int anim)156 static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) {
157 pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
158
159 if ( anim == LEGS_JUMP ) {
160 pi->legsAnimationTimer = UI_TIMER_JUMP;
161 }
162 }
163
164
165 /*
166 ===============
167 UI_SetLegsAnim
168 ===============
169 */
UI_SetLegsAnim(playerInfo_t * pi,int anim)170 static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) {
171 if ( pi->pendingLegsAnim ) {
172 anim = pi->pendingLegsAnim;
173 pi->pendingLegsAnim = 0;
174 }
175 UI_ForceLegsAnim( pi, anim );
176 }
177
178
179 /*
180 ===============
181 UI_ForceTorsoAnim
182 ===============
183 */
UI_ForceTorsoAnim(playerInfo_t * pi,int anim)184 static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) {
185 pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
186
187 if ( anim == TORSO_GESTURE ) {
188 pi->torsoAnimationTimer = UI_TIMER_GESTURE;
189 }
190
191 if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) {
192 pi->torsoAnimationTimer = UI_TIMER_ATTACK;
193 }
194 }
195
196
197 /*
198 ===============
199 UI_SetTorsoAnim
200 ===============
201 */
UI_SetTorsoAnim(playerInfo_t * pi,int anim)202 static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) {
203 if ( pi->pendingTorsoAnim ) {
204 anim = pi->pendingTorsoAnim;
205 pi->pendingTorsoAnim = 0;
206 }
207
208 UI_ForceTorsoAnim( pi, anim );
209 }
210
211
212 /*
213 ===============
214 UI_TorsoSequencing
215 ===============
216 */
UI_TorsoSequencing(playerInfo_t * pi)217 static void UI_TorsoSequencing( playerInfo_t *pi ) {
218 int currentAnim;
219
220 currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
221
222 if ( pi->weapon != pi->currentWeapon ) {
223 if ( currentAnim != TORSO_DROP ) {
224 pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
225 UI_ForceTorsoAnim( pi, TORSO_DROP );
226 }
227 }
228
229 if ( pi->torsoAnimationTimer > 0 ) {
230 return;
231 }
232
233 if( currentAnim == TORSO_GESTURE ) {
234 UI_SetTorsoAnim( pi, TORSO_STAND );
235 return;
236 }
237
238 if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) {
239 UI_SetTorsoAnim( pi, TORSO_STAND );
240 return;
241 }
242
243 if ( currentAnim == TORSO_DROP ) {
244 UI_PlayerInfo_SetWeapon( pi, pi->weapon );
245 pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
246 UI_ForceTorsoAnim( pi, TORSO_RAISE );
247 return;
248 }
249
250 if ( currentAnim == TORSO_RAISE ) {
251 UI_SetTorsoAnim( pi, TORSO_STAND );
252 return;
253 }
254 }
255
256
257 /*
258 ===============
259 UI_LegsSequencing
260 ===============
261 */
UI_LegsSequencing(playerInfo_t * pi)262 static void UI_LegsSequencing( playerInfo_t *pi ) {
263 int currentAnim;
264
265 currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
266
267 if ( pi->legsAnimationTimer > 0 ) {
268 if ( currentAnim == LEGS_JUMP ) {
269 jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP );
270 }
271 return;
272 }
273
274 if ( currentAnim == LEGS_JUMP ) {
275 UI_ForceLegsAnim( pi, LEGS_LAND );
276 pi->legsAnimationTimer = UI_TIMER_LAND;
277 jumpHeight = 0;
278 return;
279 }
280
281 if ( currentAnim == LEGS_LAND ) {
282 UI_SetLegsAnim( pi, LEGS_IDLE );
283 return;
284 }
285 }
286
287
288 /*
289 ======================
290 UI_PositionEntityOnTag
291 ======================
292 */
UI_PositionEntityOnTag(refEntity_t * entity,const refEntity_t * parent,clipHandle_t parentModel,char * tagName)293 static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
294 clipHandle_t parentModel, char *tagName ) {
295 int i;
296 orientation_t lerped;
297
298 // lerp the tag
299 trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
300 1.0 - parent->backlerp, tagName );
301
302 // FIXME: allow origin offsets along tag?
303 VectorCopy( parent->origin, entity->origin );
304 for ( i = 0 ; i < 3 ; i++ ) {
305 VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
306 }
307
308 // cast away const because of compiler problems
309 MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis );
310 entity->backlerp = parent->backlerp;
311 }
312
313
314 /*
315 ======================
316 UI_PositionRotatedEntityOnTag
317 ======================
318 */
UI_PositionRotatedEntityOnTag(refEntity_t * entity,const refEntity_t * parent,clipHandle_t parentModel,char * tagName)319 static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
320 clipHandle_t parentModel, char *tagName ) {
321 int i;
322 orientation_t lerped;
323 vec3_t tempAxis[3];
324
325 // lerp the tag
326 trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
327 1.0 - parent->backlerp, tagName );
328
329 // FIXME: allow origin offsets along tag?
330 VectorCopy( parent->origin, entity->origin );
331 for ( i = 0 ; i < 3 ; i++ ) {
332 VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
333 }
334
335 // cast away const because of compiler problems
336 MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis );
337 MatrixMultiply( lerped.axis, tempAxis, entity->axis );
338 }
339
340
341 /*
342 ===============
343 UI_SetLerpFrameAnimation
344 ===============
345 */
UI_SetLerpFrameAnimation(playerInfo_t * ci,lerpFrame_t * lf,int newAnimation)346 static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
347 animation_t *anim;
348
349 lf->animationNumber = newAnimation;
350 newAnimation &= ~ANIM_TOGGLEBIT;
351
352 if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) {
353 trap_Error( va("Bad animation number: %i", newAnimation) );
354 }
355
356 anim = &ci->animations[ newAnimation ];
357
358 lf->animation = anim;
359 lf->animationTime = lf->frameTime + anim->initialLerp;
360 }
361
362
363 /*
364 ===============
365 UI_RunLerpFrame
366 ===============
367 */
UI_RunLerpFrame(playerInfo_t * ci,lerpFrame_t * lf,int newAnimation)368 static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
369 int f;
370 animation_t *anim;
371
372 // see if the animation sequence is switching
373 if ( newAnimation != lf->animationNumber || !lf->animation ) {
374 UI_SetLerpFrameAnimation( ci, lf, newAnimation );
375 }
376
377 // if we have passed the current frame, move it to
378 // oldFrame and calculate a new frame
379 if ( dp_realtime >= lf->frameTime ) {
380 lf->oldFrame = lf->frame;
381 lf->oldFrameTime = lf->frameTime;
382
383 // get the next frame based on the animation
384 anim = lf->animation;
385 if ( dp_realtime < lf->animationTime ) {
386 lf->frameTime = lf->animationTime; // initial lerp
387 } else {
388 lf->frameTime = lf->oldFrameTime + anim->frameLerp;
389 }
390 f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
391 if ( f >= anim->numFrames ) {
392 f -= anim->numFrames;
393 if ( anim->loopFrames ) {
394 f %= anim->loopFrames;
395 f += anim->numFrames - anim->loopFrames;
396 } else {
397 f = anim->numFrames - 1;
398 // the animation is stuck at the end, so it
399 // can immediately transition to another sequence
400 lf->frameTime = dp_realtime;
401 }
402 }
403 lf->frame = anim->firstFrame + f;
404 if ( dp_realtime > lf->frameTime ) {
405 lf->frameTime = dp_realtime;
406 }
407 }
408
409 if ( lf->frameTime > dp_realtime + 200 ) {
410 lf->frameTime = dp_realtime;
411 }
412
413 if ( lf->oldFrameTime > dp_realtime ) {
414 lf->oldFrameTime = dp_realtime;
415 }
416 // calculate current lerp value
417 if ( lf->frameTime == lf->oldFrameTime ) {
418 lf->backlerp = 0;
419 } else {
420 lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
421 }
422 }
423
424
425 /*
426 ===============
427 UI_PlayerAnimation
428 ===============
429 */
UI_PlayerAnimation(playerInfo_t * pi,int * legsOld,int * legs,float * legsBackLerp,int * torsoOld,int * torso,float * torsoBackLerp)430 static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp,
431 int *torsoOld, int *torso, float *torsoBackLerp ) {
432
433 // legs animation
434 pi->legsAnimationTimer -= uiInfo.uiDC.frameTime;
435 if ( pi->legsAnimationTimer < 0 ) {
436 pi->legsAnimationTimer = 0;
437 }
438
439 UI_LegsSequencing( pi );
440
441 if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) {
442 UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN );
443 } else {
444 UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim );
445 }
446 *legsOld = pi->legs.oldFrame;
447 *legs = pi->legs.frame;
448 *legsBackLerp = pi->legs.backlerp;
449
450 // torso animation
451 pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime;
452 if ( pi->torsoAnimationTimer < 0 ) {
453 pi->torsoAnimationTimer = 0;
454 }
455
456 UI_TorsoSequencing( pi );
457
458 UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim );
459 *torsoOld = pi->torso.oldFrame;
460 *torso = pi->torso.frame;
461 *torsoBackLerp = pi->torso.backlerp;
462 }
463
464
465 /*
466 ==================
467 UI_SwingAngles
468 ==================
469 */
UI_SwingAngles(float destination,float swingTolerance,float clampTolerance,float speed,float * angle,qboolean * swinging)470 static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance,
471 float speed, float *angle, qboolean *swinging ) {
472 float swing;
473 float move;
474 float scale;
475
476 if ( !*swinging ) {
477 // see if a swing should be started
478 swing = AngleSubtract( *angle, destination );
479 if ( swing > swingTolerance || swing < -swingTolerance ) {
480 *swinging = qtrue;
481 }
482 }
483
484 if ( !*swinging ) {
485 return;
486 }
487
488 // modify the speed depending on the delta
489 // so it doesn't seem so linear
490 swing = AngleSubtract( destination, *angle );
491 scale = fabs( swing );
492 if ( scale < swingTolerance * 0.5 ) {
493 scale = 0.5;
494 } else if ( scale < swingTolerance ) {
495 scale = 1.0;
496 } else {
497 scale = 2.0;
498 }
499
500 // swing towards the destination angle
501 if ( swing >= 0 ) {
502 move = uiInfo.uiDC.frameTime * scale * speed;
503 if ( move >= swing ) {
504 move = swing;
505 *swinging = qfalse;
506 }
507 *angle = AngleMod( *angle + move );
508 } else if ( swing < 0 ) {
509 move = uiInfo.uiDC.frameTime * scale * -speed;
510 if ( move <= swing ) {
511 move = swing;
512 *swinging = qfalse;
513 }
514 *angle = AngleMod( *angle + move );
515 }
516
517 // clamp to no more than tolerance
518 swing = AngleSubtract( destination, *angle );
519 if ( swing > clampTolerance ) {
520 *angle = AngleMod( destination - (clampTolerance - 1) );
521 } else if ( swing < -clampTolerance ) {
522 *angle = AngleMod( destination + (clampTolerance - 1) );
523 }
524 }
525
526
527 /*
528 ======================
529 UI_MovedirAdjustment
530 ======================
531 */
UI_MovedirAdjustment(playerInfo_t * pi)532 static float UI_MovedirAdjustment( playerInfo_t *pi ) {
533 vec3_t relativeAngles;
534 vec3_t moveVector;
535
536 VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles );
537 AngleVectors( relativeAngles, moveVector, NULL, NULL );
538 if ( Q_fabs( moveVector[0] ) < 0.01 ) {
539 moveVector[0] = 0.0;
540 }
541 if ( Q_fabs( moveVector[1] ) < 0.01 ) {
542 moveVector[1] = 0.0;
543 }
544
545 if ( moveVector[1] == 0 && moveVector[0] > 0 ) {
546 return 0;
547 }
548 if ( moveVector[1] < 0 && moveVector[0] > 0 ) {
549 return 22;
550 }
551 if ( moveVector[1] < 0 && moveVector[0] == 0 ) {
552 return 45;
553 }
554 if ( moveVector[1] < 0 && moveVector[0] < 0 ) {
555 return -22;
556 }
557 if ( moveVector[1] == 0 && moveVector[0] < 0 ) {
558 return 0;
559 }
560 if ( moveVector[1] > 0 && moveVector[0] < 0 ) {
561 return 22;
562 }
563 if ( moveVector[1] > 0 && moveVector[0] == 0 ) {
564 return -45;
565 }
566
567 return -22;
568 }
569
570
571 /*
572 ===============
573 UI_PlayerAngles
574 ===============
575 */
UI_PlayerAngles(playerInfo_t * pi,vec3_t legs[3],vec3_t torso[3],vec3_t head[3])576 static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
577 vec3_t legsAngles, torsoAngles, headAngles;
578 float dest;
579 float adjust;
580
581 VectorCopy( pi->viewAngles, headAngles );
582 headAngles[YAW] = AngleMod( headAngles[YAW] );
583 VectorClear( legsAngles );
584 VectorClear( torsoAngles );
585
586 // --------- yaw -------------
587
588 // allow yaw to drift a bit
589 if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE
590 || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) {
591 // if not standing still, always point all in the same direction
592 pi->torso.yawing = qtrue; // always center
593 pi->torso.pitching = qtrue; // always center
594 pi->legs.yawing = qtrue; // always center
595 }
596
597 // adjust legs for movement dir
598 adjust = UI_MovedirAdjustment( pi );
599 legsAngles[YAW] = headAngles[YAW] + adjust;
600 torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust;
601
602
603 // torso
604 UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing );
605 UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing );
606
607 torsoAngles[YAW] = pi->torso.yawAngle;
608 legsAngles[YAW] = pi->legs.yawAngle;
609
610 // --------- pitch -------------
611
612 // only show a fraction of the pitch angle in the torso
613 if ( headAngles[PITCH] > 180 ) {
614 dest = (-360 + headAngles[PITCH]) * 0.75;
615 } else {
616 dest = headAngles[PITCH] * 0.75;
617 }
618 UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching );
619 torsoAngles[PITCH] = pi->torso.pitchAngle;
620
621 // pull the angles back out of the hierarchial chain
622 AnglesSubtract( headAngles, torsoAngles, headAngles );
623 AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
624 AnglesToAxis( legsAngles, legs );
625 AnglesToAxis( torsoAngles, torso );
626 AnglesToAxis( headAngles, head );
627 }
628
629
630 /*
631 ===============
632 UI_PlayerFloatSprite
633 ===============
634 */
UI_PlayerFloatSprite(playerInfo_t * pi,vec3_t origin,qhandle_t shader)635 static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) {
636 refEntity_t ent;
637
638 memset( &ent, 0, sizeof( ent ) );
639 VectorCopy( origin, ent.origin );
640 ent.origin[2] += 48;
641 ent.reType = RT_SPRITE;
642 ent.customShader = shader;
643 ent.radius = 10;
644 ent.renderfx = 0;
645 trap_R_AddRefEntityToScene( &ent );
646 }
647
648
649 /*
650 ======================
651 UI_MachinegunSpinAngle
652 ======================
653 */
UI_MachinegunSpinAngle(playerInfo_t * pi)654 float UI_MachinegunSpinAngle( playerInfo_t *pi ) {
655 int delta;
656 float angle;
657 float speed;
658 int torsoAnim;
659
660 delta = dp_realtime - pi->barrelTime;
661 if ( pi->barrelSpinning ) {
662 angle = pi->barrelAngle + delta * SPIN_SPEED;
663 } else {
664 if ( delta > COAST_TIME ) {
665 delta = COAST_TIME;
666 }
667
668 speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
669 angle = pi->barrelAngle + delta * speed;
670 }
671
672 torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
673 if( torsoAnim == TORSO_ATTACK2 ) {
674 torsoAnim = TORSO_ATTACK;
675 }
676 if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) {
677 pi->barrelTime = dp_realtime;
678 pi->barrelAngle = AngleMod( angle );
679 pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK);
680 }
681
682 return angle;
683 }
684
685
686 /*
687 ===============
688 UI_DrawPlayer
689 ===============
690 */
UI_DrawPlayer(float x,float y,float w,float h,playerInfo_t * pi,int time)691 void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) {
692 refdef_t refdef;
693 refEntity_t legs;
694 refEntity_t torso;
695 refEntity_t head;
696 refEntity_t gun;
697 refEntity_t barrel;
698 refEntity_t flash;
699 vec3_t origin;
700 int renderfx;
701 vec3_t mins = {-16, -16, -24};
702 vec3_t maxs = {16, 16, 32};
703 float len;
704 float xx;
705
706 if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) {
707 return;
708 }
709
710 // this allows the ui to cache the player model on the main menu
711 if (w == 0 || h == 0) {
712 return;
713 }
714
715 dp_realtime = time;
716
717 if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) {
718 pi->weapon = pi->pendingWeapon;
719 pi->lastWeapon = pi->pendingWeapon;
720 pi->pendingWeapon = -1;
721 pi->weaponTimer = 0;
722 if( pi->currentWeapon != pi->weapon ) {
723 trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL );
724 }
725 }
726
727 UI_AdjustFrom640( &x, &y, &w, &h );
728
729 y -= jumpHeight;
730
731 memset( &refdef, 0, sizeof( refdef ) );
732 memset( &legs, 0, sizeof(legs) );
733 memset( &torso, 0, sizeof(torso) );
734 memset( &head, 0, sizeof(head) );
735
736 refdef.rdflags = RDF_NOWORLDMODEL;
737
738 AxisClear( refdef.viewaxis );
739
740 refdef.x = x;
741 refdef.y = y;
742 refdef.width = w;
743 refdef.height = h;
744
745 refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
746 xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
747 refdef.fov_y = atan2( refdef.height, xx );
748 refdef.fov_y *= ( 360 / (float)M_PI );
749
750 // calculate distance so the player nearly fills the box
751 len = 0.7 * ( maxs[2] - mins[2] );
752 origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 );
753 origin[1] = 0.5 * ( mins[1] + maxs[1] );
754 origin[2] = -0.5 * ( mins[2] + maxs[2] );
755
756 refdef.time = dp_realtime;
757
758 trap_R_ClearScene();
759
760 // get the rotation information
761 UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis );
762
763 // get the animation state (after rotation, to allow feet shuffle)
764 UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp,
765 &torso.oldframe, &torso.frame, &torso.backlerp );
766
767 renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
768
769 //
770 // add the legs
771 //
772 legs.hModel = pi->legsModel;
773 legs.customSkin = pi->legsSkin;
774
775 VectorCopy( origin, legs.origin );
776
777 VectorCopy( origin, legs.lightingOrigin );
778 legs.renderfx = renderfx;
779 VectorCopy (legs.origin, legs.oldorigin);
780
781 trap_R_AddRefEntityToScene( &legs );
782
783 if (!legs.hModel) {
784 return;
785 }
786
787 //
788 // add the torso
789 //
790 torso.hModel = pi->torsoModel;
791 if (!torso.hModel) {
792 return;
793 }
794
795 torso.customSkin = pi->torsoSkin;
796
797 VectorCopy( origin, torso.lightingOrigin );
798
799 UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso");
800
801 torso.renderfx = renderfx;
802
803 trap_R_AddRefEntityToScene( &torso );
804
805 //
806 // add the head
807 //
808 head.hModel = pi->headModel;
809 if (!head.hModel) {
810 return;
811 }
812 head.customSkin = pi->headSkin;
813
814 VectorCopy( origin, head.lightingOrigin );
815
816 UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head");
817
818 head.renderfx = renderfx;
819
820 trap_R_AddRefEntityToScene( &head );
821
822 //
823 // add the gun
824 //
825 if ( pi->currentWeapon != WP_NONE ) {
826 memset( &gun, 0, sizeof(gun) );
827 gun.hModel = pi->weaponModel;
828 VectorCopy( origin, gun.lightingOrigin );
829 UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon");
830 gun.renderfx = renderfx;
831 trap_R_AddRefEntityToScene( &gun );
832 }
833
834 //
835 // add the spinning barrel
836 //
837 if ( pi->realWeapon == WP_MACHINEGUN || pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
838 vec3_t angles;
839
840 memset( &barrel, 0, sizeof(barrel) );
841 VectorCopy( origin, barrel.lightingOrigin );
842 barrel.renderfx = renderfx;
843
844 barrel.hModel = pi->barrelModel;
845 angles[YAW] = 0;
846 angles[PITCH] = 0;
847 angles[ROLL] = UI_MachinegunSpinAngle( pi );
848 if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
849 angles[PITCH] = angles[ROLL];
850 angles[ROLL] = 0;
851 }
852 AnglesToAxis( angles, barrel.axis );
853
854 UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel");
855
856 trap_R_AddRefEntityToScene( &barrel );
857 }
858
859 //
860 // add muzzle flash
861 //
862 if ( dp_realtime <= pi->muzzleFlashTime ) {
863 if ( pi->flashModel ) {
864 memset( &flash, 0, sizeof(flash) );
865 flash.hModel = pi->flashModel;
866 VectorCopy( origin, flash.lightingOrigin );
867 UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash");
868 flash.renderfx = renderfx;
869 trap_R_AddRefEntityToScene( &flash );
870 }
871
872 // make a dlight for the flash
873 if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) {
874 trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0],
875 pi->flashDlightColor[1], pi->flashDlightColor[2] );
876 }
877 }
878
879 //
880 // add the chat icon
881 //
882 if ( pi->chat ) {
883 UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) );
884 }
885
886 //
887 // add an accent light
888 //
889 origin[0] -= 100; // + = behind, - = in front
890 origin[1] += 100; // + = left, - = right
891 origin[2] += 100; // + = above, - = below
892 trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 );
893
894 origin[0] -= 100;
895 origin[1] -= 100;
896 origin[2] -= 100;
897 trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 );
898
899 trap_R_RenderScene( &refdef );
900 }
901
902 /*
903 ==========================
904 UI_FileExists
905 ==========================
906 */
UI_FileExists(const char * filename)907 static qboolean UI_FileExists(const char *filename) {
908 int len;
909
910 len = trap_FS_FOpenFile( filename, NULL, FS_READ );
911 if (len>0) {
912 return qtrue;
913 }
914 return qfalse;
915 }
916
917 /*
918 ==========================
919 UI_FindClientHeadFile
920 ==========================
921 */
UI_FindClientHeadFile(char * filename,int length,const char * teamName,const char * headModelName,const char * headSkinName,const char * base,const char * ext)922 static qboolean UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) {
923 char *team, *headsFolder;
924 int i;
925
926 team = "default";
927
928 if ( headModelName[0] == '*' ) {
929 headsFolder = "heads/";
930 headModelName++;
931 }
932 else {
933 headsFolder = "";
934 }
935 while(1) {
936 for ( i = 0; i < 2; i++ ) {
937 if ( i == 0 && teamName && *teamName ) {
938 Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext );
939 }
940 else {
941 Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext );
942 }
943 if ( UI_FileExists( filename ) ) {
944 return qtrue;
945 }
946 if ( i == 0 && teamName && *teamName ) {
947 Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext );
948 }
949 else {
950 Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext );
951 }
952 if ( UI_FileExists( filename ) ) {
953 return qtrue;
954 }
955 if ( !teamName || !*teamName ) {
956 break;
957 }
958 }
959 // if tried the heads folder first
960 if ( headsFolder[0] ) {
961 break;
962 }
963 headsFolder = "heads/";
964 }
965
966 return qfalse;
967 }
968
969 /*
970 ==========================
971 UI_RegisterClientSkin
972 ==========================
973 */
UI_RegisterClientSkin(playerInfo_t * pi,const char * modelName,const char * skinName,const char * headModelName,const char * headSkinName,const char * teamName)974 static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) {
975 char filename[MAX_QPATH*2];
976
977 if (teamName && *teamName) {
978 Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName );
979 } else {
980 Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName );
981 }
982 pi->legsSkin = trap_R_RegisterSkin( filename );
983 if (!pi->legsSkin) {
984 if (teamName && *teamName) {
985 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName );
986 } else {
987 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower_%s.skin", modelName, skinName );
988 }
989 pi->legsSkin = trap_R_RegisterSkin( filename );
990 }
991
992 if (teamName && *teamName) {
993 Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName );
994 } else {
995 Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName );
996 }
997 pi->torsoSkin = trap_R_RegisterSkin( filename );
998 if (!pi->torsoSkin) {
999 if (teamName && *teamName) {
1000 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName );
1001 } else {
1002 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper_%s.skin", modelName, skinName );
1003 }
1004 pi->torsoSkin = trap_R_RegisterSkin( filename );
1005 }
1006
1007 if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) {
1008 pi->headSkin = trap_R_RegisterSkin( filename );
1009 }
1010
1011 if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) {
1012 return qfalse;
1013 }
1014
1015 return qtrue;
1016 }
1017
1018
1019 /*
1020 ======================
1021 UI_ParseAnimationFile
1022 ======================
1023 */
UI_ParseAnimationFile(const char * filename,animation_t * animations)1024 static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) {
1025 char *text_p, *prev;
1026 int len;
1027 int i;
1028 char *token;
1029 float fps;
1030 int skip;
1031 char text[20000];
1032 fileHandle_t f;
1033
1034 memset( animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS );
1035
1036 // load the file
1037 len = trap_FS_FOpenFile( filename, &f, FS_READ );
1038 if ( len <= 0 ) {
1039 return qfalse;
1040 }
1041 if ( len >= ( sizeof( text ) - 1 ) ) {
1042 Com_Printf( "File %s too long\n", filename );
1043 trap_FS_FCloseFile( f );
1044 return qfalse;
1045 }
1046 trap_FS_Read( text, len, f );
1047 text[len] = 0;
1048 trap_FS_FCloseFile( f );
1049
1050 COM_Compress(text);
1051
1052 // parse the text
1053 text_p = text;
1054 skip = 0; // quite the compiler warning
1055
1056 // read optional parameters
1057 while ( 1 ) {
1058 prev = text_p; // so we can unget
1059 token = COM_Parse( &text_p );
1060 if ( !token ) {
1061 break;
1062 }
1063 if ( !Q_stricmp( token, "footsteps" ) ) {
1064 token = COM_Parse( &text_p );
1065 if ( !token ) {
1066 break;
1067 }
1068 continue;
1069 } else if ( !Q_stricmp( token, "headoffset" ) ) {
1070 for ( i = 0 ; i < 3 ; i++ ) {
1071 token = COM_Parse( &text_p );
1072 if ( !token ) {
1073 break;
1074 }
1075 }
1076 continue;
1077 } else if ( !Q_stricmp( token, "sex" ) ) {
1078 token = COM_Parse( &text_p );
1079 if ( !token ) {
1080 break;
1081 }
1082 continue;
1083 }
1084
1085 // if it is a number, start parsing animations
1086 if ( token[0] >= '0' && token[0] <= '9' ) {
1087 text_p = prev; // unget the token
1088 break;
1089 }
1090
1091 Com_Printf( "unknown token '%s' is %s\n", token, filename );
1092 }
1093
1094 // read information for each frame
1095 for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) {
1096
1097 token = COM_Parse( &text_p );
1098 if ( !token ) {
1099 break;
1100 }
1101 animations[i].firstFrame = atoi( token );
1102 // leg only frames are adjusted to not count the upper body only frames
1103 if ( i == LEGS_WALKCR ) {
1104 skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
1105 }
1106 if ( i >= LEGS_WALKCR ) {
1107 animations[i].firstFrame -= skip;
1108 }
1109
1110 token = COM_Parse( &text_p );
1111 if ( !token ) {
1112 break;
1113 }
1114 animations[i].numFrames = atoi( token );
1115
1116 token = COM_Parse( &text_p );
1117 if ( !token ) {
1118 break;
1119 }
1120 animations[i].loopFrames = atoi( token );
1121
1122 token = COM_Parse( &text_p );
1123 if ( !token ) {
1124 break;
1125 }
1126 fps = atof( token );
1127 if ( fps == 0 ) {
1128 fps = 1;
1129 }
1130 animations[i].frameLerp = 1000 / fps;
1131 animations[i].initialLerp = 1000 / fps;
1132 }
1133
1134 if ( i != MAX_ANIMATIONS ) {
1135 Com_Printf( "Error parsing animation file: %s", filename );
1136 return qfalse;
1137 }
1138
1139 return qtrue;
1140 }
1141
1142 /*
1143 ==========================
1144 UI_RegisterClientModelname
1145 ==========================
1146 */
UI_RegisterClientModelname(playerInfo_t * pi,const char * modelSkinName,const char * headModelSkinName,const char * teamName)1147 qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) {
1148 char modelName[MAX_QPATH];
1149 char skinName[MAX_QPATH];
1150 char headModelName[MAX_QPATH];
1151 char headSkinName[MAX_QPATH];
1152 char filename[MAX_QPATH];
1153 char *slash;
1154
1155 pi->torsoModel = 0;
1156 pi->headModel = 0;
1157
1158 if ( !modelSkinName[0] ) {
1159 return qfalse;
1160 }
1161
1162 Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) );
1163
1164 slash = strchr( modelName, '/' );
1165 if ( !slash ) {
1166 // modelName did not include a skin name
1167 Q_strncpyz( skinName, "default", sizeof( skinName ) );
1168 } else {
1169 Q_strncpyz( skinName, slash + 1, sizeof( skinName ) );
1170 *slash = '\0';
1171 }
1172
1173 Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) );
1174 slash = strchr( headModelName, '/' );
1175 if ( !slash ) {
1176 // modelName did not include a skin name
1177 Q_strncpyz( headSkinName, "default", sizeof( skinName ) );
1178 } else {
1179 Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) );
1180 *slash = '\0';
1181 }
1182
1183 // load cmodels before models so filecache works
1184
1185 Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName );
1186 pi->legsModel = trap_R_RegisterModel( filename );
1187 if ( !pi->legsModel ) {
1188 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName );
1189 pi->legsModel = trap_R_RegisterModel( filename );
1190 if ( !pi->legsModel ) {
1191 Com_Printf( "Failed to load model file %s\n", filename );
1192 return qfalse;
1193 }
1194 }
1195
1196 Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName );
1197 pi->torsoModel = trap_R_RegisterModel( filename );
1198 if ( !pi->torsoModel ) {
1199 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName );
1200 pi->torsoModel = trap_R_RegisterModel( filename );
1201 if ( !pi->torsoModel ) {
1202 Com_Printf( "Failed to load model file %s\n", filename );
1203 return qfalse;
1204 }
1205 }
1206
1207 if (headModelName[0] == '*' ) {
1208 Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] );
1209 }
1210 else {
1211 Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName );
1212 }
1213 pi->headModel = trap_R_RegisterModel( filename );
1214 if ( !pi->headModel && headModelName[0] != '*') {
1215 Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName );
1216 pi->headModel = trap_R_RegisterModel( filename );
1217 }
1218
1219 if (!pi->headModel) {
1220 Com_Printf( "Failed to load model file %s\n", filename );
1221 return qfalse;
1222 }
1223
1224 // if any skins failed to load, fall back to default
1225 if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) {
1226 if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) {
1227 Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
1228 return qfalse;
1229 }
1230 }
1231
1232 // load the animations
1233 Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
1234 if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
1235 Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName );
1236 if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
1237 Com_Printf( "Failed to load animation file %s\n", filename );
1238 return qfalse;
1239 }
1240 }
1241
1242 return qtrue;
1243 }
1244
1245
1246 /*
1247 ===============
1248 UI_PlayerInfo_SetModel
1249 ===============
1250 */
UI_PlayerInfo_SetModel(playerInfo_t * pi,const char * model,const char * headmodel,char * teamName)1251 void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) {
1252 memset( pi, 0, sizeof(*pi) );
1253 UI_RegisterClientModelname( pi, model, headmodel, teamName );
1254 pi->weapon = WP_MACHINEGUN;
1255 pi->currentWeapon = pi->weapon;
1256 pi->lastWeapon = pi->weapon;
1257 pi->pendingWeapon = -1;
1258 pi->weaponTimer = 0;
1259 pi->chat = qfalse;
1260 pi->newModel = qtrue;
1261 UI_PlayerInfo_SetWeapon( pi, pi->weapon );
1262 }
1263
1264
1265 /*
1266 ===============
1267 UI_PlayerInfo_SetInfo
1268 ===============
1269 */
UI_PlayerInfo_SetInfo(playerInfo_t * pi,int legsAnim,int torsoAnim,vec3_t viewAngles,vec3_t moveAngles,weapon_t weaponNumber,qboolean chat)1270 void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) {
1271 int currentAnim;
1272 weapon_t weaponNum;
1273
1274 pi->chat = chat;
1275
1276 // view angles
1277 VectorCopy( viewAngles, pi->viewAngles );
1278
1279 // move angles
1280 VectorCopy( moveAngles, pi->moveAngles );
1281
1282 if ( pi->newModel ) {
1283 pi->newModel = qfalse;
1284
1285 jumpHeight = 0;
1286 pi->pendingLegsAnim = 0;
1287 UI_ForceLegsAnim( pi, legsAnim );
1288 pi->legs.yawAngle = viewAngles[YAW];
1289 pi->legs.yawing = qfalse;
1290
1291 pi->pendingTorsoAnim = 0;
1292 UI_ForceTorsoAnim( pi, torsoAnim );
1293 pi->torso.yawAngle = viewAngles[YAW];
1294 pi->torso.yawing = qfalse;
1295
1296 if ( weaponNumber != -1 ) {
1297 pi->weapon = weaponNumber;
1298 pi->currentWeapon = weaponNumber;
1299 pi->lastWeapon = weaponNumber;
1300 pi->pendingWeapon = -1;
1301 pi->weaponTimer = 0;
1302 UI_PlayerInfo_SetWeapon( pi, pi->weapon );
1303 }
1304
1305 return;
1306 }
1307
1308 // weapon
1309 if ( weaponNumber == -1 ) {
1310 pi->pendingWeapon = -1;
1311 pi->weaponTimer = 0;
1312 }
1313 else if ( weaponNumber != WP_NONE ) {
1314 pi->pendingWeapon = weaponNumber;
1315 pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY;
1316 }
1317 weaponNum = pi->lastWeapon;
1318 pi->weapon = weaponNum;
1319
1320 if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) {
1321 torsoAnim = legsAnim = BOTH_DEATH1;
1322 pi->weapon = pi->currentWeapon = WP_NONE;
1323 UI_PlayerInfo_SetWeapon( pi, pi->weapon );
1324
1325 jumpHeight = 0;
1326 pi->pendingLegsAnim = 0;
1327 UI_ForceLegsAnim( pi, legsAnim );
1328
1329 pi->pendingTorsoAnim = 0;
1330 UI_ForceTorsoAnim( pi, torsoAnim );
1331
1332 return;
1333 }
1334
1335 // leg animation
1336 currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
1337 if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) {
1338 pi->pendingLegsAnim = legsAnim;
1339 }
1340 else if ( legsAnim != currentAnim ) {
1341 jumpHeight = 0;
1342 pi->pendingLegsAnim = 0;
1343 UI_ForceLegsAnim( pi, legsAnim );
1344 }
1345
1346 // torso animation
1347 if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) {
1348 if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) {
1349 torsoAnim = TORSO_STAND2;
1350 }
1351 else {
1352 torsoAnim = TORSO_STAND;
1353 }
1354 }
1355
1356 if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) {
1357 if ( weaponNum == WP_NONE || weaponNum == WP_GAUNTLET ) {
1358 torsoAnim = TORSO_ATTACK2;
1359 }
1360 else {
1361 torsoAnim = TORSO_ATTACK;
1362 }
1363 pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
1364 //FIXME play firing sound here
1365 }
1366
1367 currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
1368
1369 if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) {
1370 pi->pendingTorsoAnim = torsoAnim;
1371 }
1372 else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) {
1373 pi->pendingTorsoAnim = torsoAnim;
1374 }
1375 else if ( torsoAnim != currentAnim ) {
1376 pi->pendingTorsoAnim = 0;
1377 UI_ForceTorsoAnim( pi, torsoAnim );
1378 }
1379 }
1380