mirror of
https://github.com/ReactionQuake3/reaction.git
synced 2024-11-26 14:11:22 +00:00
1412 lines
35 KiB
C
1412 lines
35 KiB
C
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id$
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Log$
|
|
// Revision 1.8 2005/02/15 16:33:39 makro
|
|
// Tons of updates (entity tree attachment system, UI vectors)
|
|
//
|
|
// Revision 1.7 2003/03/28 10:36:03 jbravo
|
|
// Tweaking the replacement system a bit. Reactionmale now the default model
|
|
//
|
|
// Revision 1.6 2002/07/26 22:28:38 jbravo
|
|
// Fixed the server about menu, made the UI handle illegal models and skins
|
|
// better.
|
|
//
|
|
// Revision 1.5 2002/06/16 20:06:15 jbravo
|
|
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
|
|
//
|
|
// Revision 1.4 2002/04/22 18:40:58 makro
|
|
// Model validation
|
|
//
|
|
// Revision 1.3 2002/02/21 20:10:16 jbravo
|
|
// Converted files back from M$ format and added cvs headers again.
|
|
//
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
// ui_players.c
|
|
|
|
#include "ui_local.h"
|
|
|
|
#define UI_TIMER_GESTURE 2300
|
|
#define UI_TIMER_JUMP 1000
|
|
#define UI_TIMER_LAND 130
|
|
#define UI_TIMER_WEAPON_SWITCH 300
|
|
#define UI_TIMER_ATTACK 500
|
|
#define UI_TIMER_MUZZLE_FLASH 20
|
|
#define UI_TIMER_WEAPON_DELAY 250
|
|
|
|
#define JUMP_HEIGHT 56
|
|
|
|
#define SWINGSPEED 0.3f
|
|
|
|
#define SPIN_SPEED 0.9f
|
|
#define COAST_TIME 1000
|
|
|
|
static int dp_realtime;
|
|
static float jumpHeight;
|
|
sfxHandle_t weaponChangeSound;
|
|
|
|
/*
|
|
* ============
|
|
* COM_StripExtensionInPlace
|
|
* ============
|
|
* */
|
|
void COM_StripExtensionInPlace(char *name)
|
|
{
|
|
char* ext = strrchr(name, '.');
|
|
if (ext)
|
|
*ext = 0;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_PlayerInfo_SetWeapon
|
|
===============
|
|
*/
|
|
static void UI_PlayerInfo_SetWeapon(playerInfo_t * pi, weapon_t weaponNum)
|
|
{
|
|
gitem_t *item;
|
|
char path[MAX_QPATH];
|
|
|
|
pi->currentWeapon = weaponNum;
|
|
tryagain:
|
|
pi->realWeapon = weaponNum;
|
|
pi->weaponModel = 0;
|
|
pi->barrelModel = 0;
|
|
pi->flashModel = 0;
|
|
|
|
if (weaponNum == WP_NONE) {
|
|
return;
|
|
}
|
|
|
|
for (item = bg_itemlist + 1; item->classname; item++) {
|
|
if (item->giType != IT_WEAPON) {
|
|
continue;
|
|
}
|
|
if (item->giTag == weaponNum) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (item->classname) {
|
|
pi->weaponModel = trap_R_RegisterModel(item->world_model[0]);
|
|
}
|
|
|
|
if (pi->weaponModel == 0) {
|
|
//Blaze: Changed from WP_MACHINEGUN to WP_PISTOL
|
|
if (weaponNum == WP_PISTOL) {
|
|
weaponNum = WP_NONE;
|
|
goto tryagain;
|
|
}
|
|
//Blaze: Changed from WP_MACHINEGUN to WP_PISTOL
|
|
weaponNum = WP_PISTOL;
|
|
goto tryagain;
|
|
}
|
|
//Blaze: none of our weapons have barrel models
|
|
/*if ( weaponNum == WP_PISTOL || weaponNum == WP_GAUNTLET || weaponNum == WP_BFG ) {
|
|
strcpy( path, item->world_model[0] );
|
|
COM_StripExtension( path, path );
|
|
strcat( path, "_barrel.md3" );
|
|
pi->barrelModel = trap_R_RegisterModel( path );
|
|
} */
|
|
|
|
strcpy(path, item->world_model[0]);
|
|
COM_StripExtensionInPlace(path);
|
|
strcat(path, "_flash.md3");
|
|
pi->flashModel = trap_R_RegisterModel(path);
|
|
|
|
//Blaze: I think this makes some funky colors or something, I dont really know ;)
|
|
//Blaze: Reaction Weapons
|
|
switch (weaponNum) {
|
|
case WP_KNIFE:
|
|
MAKERGB(pi->flashDlightColor, 0.6f, 0.6f, 1);
|
|
break;
|
|
|
|
case WP_PISTOL:
|
|
MAKERGB(pi->flashDlightColor, 1, 1, 0);
|
|
break;
|
|
|
|
case WP_M4:
|
|
MAKERGB(pi->flashDlightColor, 1, 1, 0);
|
|
break;
|
|
|
|
case WP_SSG3000:
|
|
MAKERGB(pi->flashDlightColor, 1, 0.7f, 0.5f);
|
|
break;
|
|
|
|
case WP_MP5:
|
|
MAKERGB(pi->flashDlightColor, 1, 0.75f, 0);
|
|
break;
|
|
|
|
case WP_HANDCANNON:
|
|
MAKERGB(pi->flashDlightColor, 0.6f, 0.6f, 1);
|
|
break;
|
|
|
|
case WP_M3:
|
|
MAKERGB(pi->flashDlightColor, 1, 0.5f, 0);
|
|
break;
|
|
|
|
case WP_AKIMBO:
|
|
MAKERGB(pi->flashDlightColor, 0.6f, 0.6f, 1);
|
|
break;
|
|
|
|
case WP_GRENADE:
|
|
MAKERGB(pi->flashDlightColor, 1, 0.7f, 1);
|
|
break;
|
|
|
|
default:
|
|
MAKERGB(pi->flashDlightColor, 1, 1, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_ForceLegsAnim
|
|
===============
|
|
*/
|
|
static void UI_ForceLegsAnim(playerInfo_t * pi, int anim)
|
|
{
|
|
pi->legsAnim = ((pi->legsAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim;
|
|
|
|
if (anim == LEGS_JUMP) {
|
|
pi->legsAnimationTimer = UI_TIMER_JUMP;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_SetLegsAnim
|
|
===============
|
|
*/
|
|
static void UI_SetLegsAnim(playerInfo_t * pi, int anim)
|
|
{
|
|
if (pi->pendingLegsAnim) {
|
|
anim = pi->pendingLegsAnim;
|
|
pi->pendingLegsAnim = 0;
|
|
}
|
|
UI_ForceLegsAnim(pi, anim);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_ForceTorsoAnim
|
|
===============
|
|
*/
|
|
static void UI_ForceTorsoAnim(playerInfo_t * pi, int anim)
|
|
{
|
|
pi->torsoAnim = ((pi->torsoAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim;
|
|
|
|
if (anim == TORSO_GESTURE) {
|
|
pi->torsoAnimationTimer = UI_TIMER_GESTURE;
|
|
}
|
|
|
|
if (anim == TORSO_ATTACK || anim == TORSO_ATTACK2) {
|
|
pi->torsoAnimationTimer = UI_TIMER_ATTACK;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_SetTorsoAnim
|
|
===============
|
|
*/
|
|
static void UI_SetTorsoAnim(playerInfo_t * pi, int anim)
|
|
{
|
|
if (pi->pendingTorsoAnim) {
|
|
anim = pi->pendingTorsoAnim;
|
|
pi->pendingTorsoAnim = 0;
|
|
}
|
|
|
|
UI_ForceTorsoAnim(pi, anim);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_TorsoSequencing
|
|
===============
|
|
*/
|
|
static void UI_TorsoSequencing(playerInfo_t * pi)
|
|
{
|
|
int currentAnim;
|
|
|
|
currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
|
|
|
|
if (pi->weapon != pi->currentWeapon) {
|
|
if (currentAnim != TORSO_DROP) {
|
|
pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
|
|
UI_ForceTorsoAnim(pi, TORSO_DROP);
|
|
}
|
|
}
|
|
|
|
if (pi->torsoAnimationTimer > 0) {
|
|
return;
|
|
}
|
|
|
|
if (currentAnim == TORSO_GESTURE) {
|
|
UI_SetTorsoAnim(pi, TORSO_STAND);
|
|
return;
|
|
}
|
|
|
|
if (currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2) {
|
|
UI_SetTorsoAnim(pi, TORSO_STAND);
|
|
return;
|
|
}
|
|
|
|
if (currentAnim == TORSO_DROP) {
|
|
UI_PlayerInfo_SetWeapon(pi, pi->weapon);
|
|
pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
|
|
UI_ForceTorsoAnim(pi, TORSO_RAISE);
|
|
return;
|
|
}
|
|
|
|
if (currentAnim == TORSO_RAISE) {
|
|
UI_SetTorsoAnim(pi, TORSO_STAND);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_LegsSequencing
|
|
===============
|
|
*/
|
|
static void UI_LegsSequencing(playerInfo_t * pi)
|
|
{
|
|
int currentAnim;
|
|
|
|
currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
|
|
|
|
if (pi->legsAnimationTimer > 0) {
|
|
if (currentAnim == LEGS_JUMP) {
|
|
jumpHeight = JUMP_HEIGHT * sin(M_PI * (UI_TIMER_JUMP - pi->legsAnimationTimer) / UI_TIMER_JUMP);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (currentAnim == LEGS_JUMP) {
|
|
UI_ForceLegsAnim(pi, LEGS_LAND);
|
|
pi->legsAnimationTimer = UI_TIMER_LAND;
|
|
jumpHeight = 0;
|
|
return;
|
|
}
|
|
|
|
if (currentAnim == LEGS_LAND) {
|
|
UI_SetLegsAnim(pi, LEGS_IDLE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
UI_PositionEntityOnTag
|
|
======================
|
|
*/
|
|
static void UI_PositionEntityOnTag(refEntity_t * entity, const refEntity_t * parent,
|
|
clipHandle_t parentModel, char *tagName)
|
|
{
|
|
int i;
|
|
orientation_t lerped;
|
|
|
|
// lerp the tag
|
|
trap_CM_LerpTag(&lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName);
|
|
|
|
// FIXME: allow origin offsets along tag?
|
|
VectorCopy(parent->origin, entity->origin);
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(entity->origin, lerped.origin[i], parent->axis[i], entity->origin);
|
|
}
|
|
|
|
// cast away const because of compiler problems
|
|
MatrixMultiply(lerped.axis, ((refEntity_t *) parent)->axis, entity->axis);
|
|
entity->backlerp = parent->backlerp;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
UI_PositionRotatedEntityOnTag
|
|
======================
|
|
*/
|
|
static void UI_PositionRotatedEntityOnTag(refEntity_t * entity, const refEntity_t * parent,
|
|
clipHandle_t parentModel, char *tagName)
|
|
{
|
|
int i;
|
|
orientation_t lerped;
|
|
vec3_t tempAxis[3];
|
|
|
|
// lerp the tag
|
|
trap_CM_LerpTag(&lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName);
|
|
|
|
// FIXME: allow origin offsets along tag?
|
|
VectorCopy(parent->origin, entity->origin);
|
|
for (i = 0; i < 3; i++) {
|
|
VectorMA(entity->origin, lerped.origin[i], parent->axis[i], entity->origin);
|
|
}
|
|
|
|
// cast away const because of compiler problems
|
|
MatrixMultiply(entity->axis, ((refEntity_t *) parent)->axis, tempAxis);
|
|
MatrixMultiply(lerped.axis, tempAxis, entity->axis);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_SetLerpFrameAnimation
|
|
===============
|
|
*/
|
|
static void UI_SetLerpFrameAnimation(playerInfo_t * ci, lerpFrame_t * lf, int newAnimation)
|
|
{
|
|
animation_t *anim;
|
|
|
|
lf->animationNumber = newAnimation;
|
|
newAnimation &= ~ANIM_TOGGLEBIT;
|
|
|
|
if (newAnimation < 0 || newAnimation >= MAX_ANIMATIONS) {
|
|
trap_Error(va("Bad animation number: %i", newAnimation));
|
|
}
|
|
|
|
anim = &ci->animations[newAnimation];
|
|
|
|
lf->animation = anim;
|
|
lf->animationTime = lf->frameTime + anim->initialLerp;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_RunLerpFrame
|
|
===============
|
|
*/
|
|
static void UI_RunLerpFrame(playerInfo_t * ci, lerpFrame_t * lf, int newAnimation)
|
|
{
|
|
int f;
|
|
animation_t *anim;
|
|
|
|
// see if the animation sequence is switching
|
|
if (newAnimation != lf->animationNumber || !lf->animation) {
|
|
UI_SetLerpFrameAnimation(ci, lf, newAnimation);
|
|
}
|
|
// if we have passed the current frame, move it to
|
|
// oldFrame and calculate a new frame
|
|
if (dp_realtime >= lf->frameTime) {
|
|
lf->oldFrame = lf->frame;
|
|
lf->oldFrameTime = lf->frameTime;
|
|
|
|
// get the next frame based on the animation
|
|
anim = lf->animation;
|
|
if (dp_realtime < lf->animationTime) {
|
|
lf->frameTime = lf->animationTime; // initial lerp
|
|
} else {
|
|
lf->frameTime = lf->oldFrameTime + anim->frameLerp;
|
|
}
|
|
f = (lf->frameTime - lf->animationTime) / anim->frameLerp;
|
|
if (f >= anim->numFrames) {
|
|
f -= anim->numFrames;
|
|
if (anim->loopFrames) {
|
|
f %= anim->loopFrames;
|
|
f += anim->numFrames - anim->loopFrames;
|
|
} else {
|
|
f = anim->numFrames - 1;
|
|
// the animation is stuck at the end, so it
|
|
// can immediately transition to another sequence
|
|
lf->frameTime = dp_realtime;
|
|
}
|
|
}
|
|
lf->frame = anim->firstFrame + f;
|
|
if (dp_realtime > lf->frameTime) {
|
|
lf->frameTime = dp_realtime;
|
|
}
|
|
}
|
|
|
|
if (lf->frameTime > dp_realtime + 200) {
|
|
lf->frameTime = dp_realtime;
|
|
}
|
|
|
|
if (lf->oldFrameTime > dp_realtime) {
|
|
lf->oldFrameTime = dp_realtime;
|
|
}
|
|
// calculate current lerp value
|
|
if (lf->frameTime == lf->oldFrameTime) {
|
|
lf->backlerp = 0;
|
|
} else {
|
|
lf->backlerp = 1.0 - (float) (dp_realtime - lf->oldFrameTime) / (lf->frameTime - lf->oldFrameTime);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_PlayerAnimation
|
|
===============
|
|
*/
|
|
static void UI_PlayerAnimation(playerInfo_t * pi, int *legsOld, int *legs, float *legsBackLerp,
|
|
int *torsoOld, int *torso, float *torsoBackLerp)
|
|
{
|
|
|
|
// legs animation
|
|
pi->legsAnimationTimer -= uiInfo.uiDC.frameTime;
|
|
if (pi->legsAnimationTimer < 0) {
|
|
pi->legsAnimationTimer = 0;
|
|
}
|
|
|
|
UI_LegsSequencing(pi);
|
|
|
|
if (pi->legs.yawing && (pi->legsAnim & ~ANIM_TOGGLEBIT) == LEGS_IDLE) {
|
|
UI_RunLerpFrame(pi, &pi->legs, LEGS_TURN);
|
|
} else {
|
|
UI_RunLerpFrame(pi, &pi->legs, pi->legsAnim);
|
|
}
|
|
*legsOld = pi->legs.oldFrame;
|
|
*legs = pi->legs.frame;
|
|
*legsBackLerp = pi->legs.backlerp;
|
|
|
|
// torso animation
|
|
pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime;
|
|
if (pi->torsoAnimationTimer < 0) {
|
|
pi->torsoAnimationTimer = 0;
|
|
}
|
|
|
|
UI_TorsoSequencing(pi);
|
|
|
|
UI_RunLerpFrame(pi, &pi->torso, pi->torsoAnim);
|
|
*torsoOld = pi->torso.oldFrame;
|
|
*torso = pi->torso.frame;
|
|
*torsoBackLerp = pi->torso.backlerp;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
UI_SwingAngles
|
|
==================
|
|
*/
|
|
static void UI_SwingAngles(float destination, float swingTolerance, float clampTolerance,
|
|
float speed, float *angle, qboolean * swinging)
|
|
{
|
|
float swing;
|
|
float move;
|
|
float scale;
|
|
|
|
if (!*swinging) {
|
|
// see if a swing should be started
|
|
swing = AngleSubtract(*angle, destination);
|
|
if (swing > swingTolerance || swing < -swingTolerance) {
|
|
*swinging = qtrue;
|
|
}
|
|
}
|
|
|
|
if (!*swinging) {
|
|
return;
|
|
}
|
|
// modify the speed depending on the delta
|
|
// so it doesn't seem so linear
|
|
swing = AngleSubtract(destination, *angle);
|
|
scale = fabs(swing);
|
|
if (scale < swingTolerance * 0.5) {
|
|
scale = 0.5;
|
|
} else if (scale < swingTolerance) {
|
|
scale = 1.0;
|
|
} else {
|
|
scale = 2.0;
|
|
}
|
|
|
|
// swing towards the destination angle
|
|
if (swing >= 0) {
|
|
move = uiInfo.uiDC.frameTime * scale * speed;
|
|
if (move >= swing) {
|
|
move = swing;
|
|
*swinging = qfalse;
|
|
}
|
|
*angle = AngleMod(*angle + move);
|
|
} else if (swing < 0) {
|
|
move = uiInfo.uiDC.frameTime * scale * -speed;
|
|
if (move <= swing) {
|
|
move = swing;
|
|
*swinging = qfalse;
|
|
}
|
|
*angle = AngleMod(*angle + move);
|
|
}
|
|
// clamp to no more than tolerance
|
|
swing = AngleSubtract(destination, *angle);
|
|
if (swing > clampTolerance) {
|
|
*angle = AngleMod(destination - (clampTolerance - 1));
|
|
} else if (swing < -clampTolerance) {
|
|
*angle = AngleMod(destination + (clampTolerance - 1));
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
UI_MovedirAdjustment
|
|
======================
|
|
*/
|
|
static float UI_MovedirAdjustment(playerInfo_t * pi)
|
|
{
|
|
vec3_t relativeAngles;
|
|
vec3_t moveVector;
|
|
|
|
VectorSubtract(pi->viewAngles, pi->moveAngles, relativeAngles);
|
|
AngleVectors(relativeAngles, moveVector, NULL, NULL);
|
|
if (Q_fabs(moveVector[0]) < 0.01) {
|
|
moveVector[0] = 0.0;
|
|
}
|
|
if (Q_fabs(moveVector[1]) < 0.01) {
|
|
moveVector[1] = 0.0;
|
|
}
|
|
|
|
if (moveVector[1] == 0 && moveVector[0] > 0) {
|
|
return 0;
|
|
}
|
|
if (moveVector[1] < 0 && moveVector[0] > 0) {
|
|
return 22;
|
|
}
|
|
if (moveVector[1] < 0 && moveVector[0] == 0) {
|
|
return 45;
|
|
}
|
|
if (moveVector[1] < 0 && moveVector[0] < 0) {
|
|
return -22;
|
|
}
|
|
if (moveVector[1] == 0 && moveVector[0] < 0) {
|
|
return 0;
|
|
}
|
|
if (moveVector[1] > 0 && moveVector[0] < 0) {
|
|
return 22;
|
|
}
|
|
if (moveVector[1] > 0 && moveVector[0] == 0) {
|
|
return -45;
|
|
}
|
|
|
|
return -22;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_PlayerAngles
|
|
===============
|
|
*/
|
|
static void UI_PlayerAngles(playerInfo_t * pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3])
|
|
{
|
|
vec3_t legsAngles, torsoAngles, headAngles;
|
|
float dest;
|
|
float adjust;
|
|
|
|
VectorCopy(pi->viewAngles, headAngles);
|
|
headAngles[YAW] = AngleMod(headAngles[YAW]);
|
|
VectorClear(legsAngles);
|
|
VectorClear(torsoAngles);
|
|
|
|
// --------- yaw -------------
|
|
|
|
// allow yaw to drift a bit
|
|
if ((pi->legsAnim & ~ANIM_TOGGLEBIT) != LEGS_IDLE || (pi->torsoAnim & ~ANIM_TOGGLEBIT) != TORSO_STAND) {
|
|
// if not standing still, always point all in the same direction
|
|
pi->torso.yawing = qtrue; // always center
|
|
pi->torso.pitching = qtrue; // always center
|
|
pi->legs.yawing = qtrue; // always center
|
|
}
|
|
// adjust legs for movement dir
|
|
adjust = UI_MovedirAdjustment(pi);
|
|
legsAngles[YAW] = headAngles[YAW] + adjust;
|
|
torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust;
|
|
|
|
// torso
|
|
UI_SwingAngles(torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing);
|
|
UI_SwingAngles(legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing);
|
|
|
|
torsoAngles[YAW] = pi->torso.yawAngle;
|
|
legsAngles[YAW] = pi->legs.yawAngle;
|
|
|
|
// --------- pitch -------------
|
|
|
|
// only show a fraction of the pitch angle in the torso
|
|
if (headAngles[PITCH] > 180) {
|
|
dest = (-360 + headAngles[PITCH]) * 0.75;
|
|
} else {
|
|
dest = headAngles[PITCH] * 0.75;
|
|
}
|
|
UI_SwingAngles(dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching);
|
|
torsoAngles[PITCH] = pi->torso.pitchAngle;
|
|
|
|
// pull the angles back out of the hierarchial chain
|
|
AnglesSubtract(headAngles, torsoAngles, headAngles);
|
|
AnglesSubtract(torsoAngles, legsAngles, torsoAngles);
|
|
AnglesToAxis(legsAngles, legs);
|
|
AnglesToAxis(torsoAngles, torso);
|
|
AnglesToAxis(headAngles, head);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_PlayerFloatSprite
|
|
===============
|
|
*/
|
|
static void UI_PlayerFloatSprite(playerInfo_t * pi, vec3_t origin, qhandle_t shader)
|
|
{
|
|
refEntity_t ent;
|
|
|
|
memset(&ent, 0, sizeof(ent));
|
|
VectorCopy(origin, ent.origin);
|
|
ent.origin[2] += 48;
|
|
ent.reType = RT_SPRITE;
|
|
ent.customShader = shader;
|
|
ent.radius = 10;
|
|
ent.renderfx = 0;
|
|
trap_R_AddRefEntityToScene(&ent);
|
|
}
|
|
|
|
/*
|
|
======================
|
|
UI_MachinegunSpinAngle
|
|
======================
|
|
*/
|
|
float UI_MachinegunSpinAngle(playerInfo_t * pi)
|
|
{
|
|
int delta;
|
|
float angle;
|
|
float speed;
|
|
int torsoAnim;
|
|
|
|
delta = dp_realtime - pi->barrelTime;
|
|
if (pi->barrelSpinning) {
|
|
angle = pi->barrelAngle + delta * SPIN_SPEED;
|
|
} else {
|
|
if (delta > COAST_TIME) {
|
|
delta = COAST_TIME;
|
|
}
|
|
|
|
speed = 0.5 * (SPIN_SPEED + (float) (COAST_TIME - delta) / COAST_TIME);
|
|
angle = pi->barrelAngle + delta * speed;
|
|
}
|
|
|
|
torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
|
|
if (torsoAnim == TORSO_ATTACK2) {
|
|
torsoAnim = TORSO_ATTACK;
|
|
}
|
|
if (pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK)) {
|
|
pi->barrelTime = dp_realtime;
|
|
pi->barrelAngle = AngleMod(angle);
|
|
pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK);
|
|
}
|
|
|
|
return angle;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_DrawPlayer
|
|
===============
|
|
*/
|
|
void UI_DrawPlayer(float x, float y, float w, float h, playerInfo_t * pi, int time)
|
|
{
|
|
refdef_t refdef;
|
|
refEntity_t legs;
|
|
refEntity_t torso;
|
|
refEntity_t head;
|
|
refEntity_t gun;
|
|
|
|
//Makro - barrel is no longer used
|
|
//refEntity_t barrel;
|
|
refEntity_t flash;
|
|
vec3_t origin;
|
|
int renderfx;
|
|
vec3_t mins = { -16, -16, -24 };
|
|
vec3_t maxs = { 16, 16, 32 };
|
|
float len;
|
|
float xx;
|
|
|
|
if (!pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames) {
|
|
return;
|
|
}
|
|
// this allows the ui to cache the player model on the main menu
|
|
if (w == 0 || h == 0) {
|
|
return;
|
|
}
|
|
|
|
dp_realtime = time;
|
|
|
|
if (pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer) {
|
|
pi->weapon = pi->pendingWeapon;
|
|
pi->lastWeapon = pi->pendingWeapon;
|
|
pi->pendingWeapon = -1;
|
|
pi->weaponTimer = 0;
|
|
if (pi->currentWeapon != pi->weapon) {
|
|
trap_S_StartLocalSound(weaponChangeSound, CHAN_LOCAL);
|
|
}
|
|
}
|
|
|
|
UI_AdjustFrom640(&x, &y, &w, &h);
|
|
|
|
y -= jumpHeight;
|
|
|
|
memset(&refdef, 0, sizeof(refdef));
|
|
memset(&legs, 0, sizeof(legs));
|
|
memset(&torso, 0, sizeof(torso));
|
|
memset(&head, 0, sizeof(head));
|
|
|
|
refdef.rdflags = RDF_NOWORLDMODEL;
|
|
|
|
AxisClear(refdef.viewaxis);
|
|
|
|
refdef.x = x;
|
|
refdef.y = y;
|
|
refdef.width = w;
|
|
refdef.height = h;
|
|
|
|
refdef.fov_x = (int)((float)refdef.width / uiInfo.uiDC.xscale / 640.0f * 90.0f);
|
|
xx = refdef.width / uiInfo.uiDC.xscale / tan( refdef.fov_x / 360 * M_PI );
|
|
refdef.fov_y = atan2( refdef.height / uiInfo.uiDC.yscale, xx );
|
|
refdef.fov_y *= (360 / (float) M_PI);
|
|
|
|
// calculate distance so the player nearly fills the box
|
|
len = 0.7 * ( maxs[2] - mins[2] );
|
|
origin[0] = len / tan(DEG2RAD(refdef.fov_x) * 0.5);
|
|
origin[1] = 0.5 * (mins[1] + maxs[1]);
|
|
origin[2] = -0.5 * (mins[2] + maxs[2]);
|
|
|
|
refdef.time = dp_realtime;
|
|
|
|
//trap_R_ClearScene();
|
|
uiInfo.uiDC.clearScene();
|
|
|
|
|
|
// get the rotation information
|
|
UI_PlayerAngles(pi, legs.axis, torso.axis, head.axis);
|
|
|
|
// get the animation state (after rotation, to allow feet shuffle)
|
|
UI_PlayerAnimation(pi, &legs.oldframe, &legs.frame, &legs.backlerp,
|
|
&torso.oldframe, &torso.frame, &torso.backlerp);
|
|
|
|
renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
|
|
|
|
//
|
|
// add the legs
|
|
//
|
|
legs.hModel = pi->legsModel;
|
|
legs.customSkin = pi->legsSkin;
|
|
|
|
VectorCopy(origin, legs.origin);
|
|
|
|
VectorCopy(origin, legs.lightingOrigin);
|
|
legs.renderfx = renderfx;
|
|
VectorCopy(legs.origin, legs.oldorigin);
|
|
|
|
trap_R_AddRefEntityToScene(&legs);
|
|
|
|
if (!legs.hModel) {
|
|
return;
|
|
}
|
|
//
|
|
// add the torso
|
|
//
|
|
torso.hModel = pi->torsoModel;
|
|
if (!torso.hModel) {
|
|
return;
|
|
}
|
|
|
|
torso.customSkin = pi->torsoSkin;
|
|
|
|
VectorCopy(origin, torso.lightingOrigin);
|
|
|
|
UI_PositionRotatedEntityOnTag(&torso, &legs, pi->legsModel, "tag_torso");
|
|
|
|
torso.renderfx = renderfx;
|
|
|
|
trap_R_AddRefEntityToScene(&torso);
|
|
|
|
//
|
|
// add the head
|
|
//
|
|
head.hModel = pi->headModel;
|
|
if (!head.hModel) {
|
|
return;
|
|
}
|
|
head.customSkin = pi->headSkin;
|
|
|
|
VectorCopy(origin, head.lightingOrigin);
|
|
|
|
UI_PositionRotatedEntityOnTag(&head, &torso, pi->torsoModel, "tag_head");
|
|
|
|
head.renderfx = renderfx;
|
|
|
|
trap_R_AddRefEntityToScene(&head);
|
|
|
|
//
|
|
// add the gun
|
|
//
|
|
if (pi->currentWeapon != WP_NONE) {
|
|
memset(&gun, 0, sizeof(gun));
|
|
gun.hModel = pi->weaponModel;
|
|
VectorCopy(origin, gun.lightingOrigin);
|
|
UI_PositionEntityOnTag(&gun, &torso, pi->torsoModel, "tag_weapon");
|
|
gun.renderfx = renderfx;
|
|
trap_R_AddRefEntityToScene(&gun);
|
|
}
|
|
//
|
|
// add the spinning barrel
|
|
//
|
|
//Blaze: No spinning barrels in rq3
|
|
/*
|
|
if ( pi->realWeapon == WP_MACHINEGUN || pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
|
|
vec3_t angles;
|
|
|
|
memset( &barrel, 0, sizeof(barrel) );
|
|
VectorCopy( origin, barrel.lightingOrigin );
|
|
barrel.renderfx = renderfx;
|
|
|
|
barrel.hModel = pi->barrelModel;
|
|
angles[YAW] = 0;
|
|
angles[PITCH] = 0;
|
|
angles[ROLL] = UI_MachinegunSpinAngle( pi );
|
|
if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
|
|
angles[PITCH] = angles[ROLL];
|
|
angles[ROLL] = 0;
|
|
}
|
|
AnglesToAxis( angles, barrel.axis );
|
|
|
|
UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel");
|
|
|
|
trap_R_AddRefEntityToScene( &barrel );
|
|
}
|
|
*/
|
|
|
|
//
|
|
// add muzzle flash
|
|
//
|
|
if (dp_realtime <= pi->muzzleFlashTime) {
|
|
if (pi->flashModel) {
|
|
memset(&flash, 0, sizeof(flash));
|
|
flash.hModel = pi->flashModel;
|
|
VectorCopy(origin, flash.lightingOrigin);
|
|
UI_PositionEntityOnTag(&flash, &gun, pi->weaponModel, "tag_flash");
|
|
flash.renderfx = renderfx;
|
|
trap_R_AddRefEntityToScene(&flash);
|
|
}
|
|
// make a dlight for the flash
|
|
if (pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2]) {
|
|
trap_R_AddLightToScene(flash.origin, 200 + (rand() & 31), pi->flashDlightColor[0],
|
|
pi->flashDlightColor[1], pi->flashDlightColor[2]);
|
|
}
|
|
}
|
|
//
|
|
// add the chat icon
|
|
//
|
|
if (pi->chat) {
|
|
UI_PlayerFloatSprite(pi, origin, trap_R_RegisterShaderNoMip("sprites/balloon3"));
|
|
}
|
|
//
|
|
// add an accent light
|
|
//
|
|
origin[0] -= 100; // + = behind, - = in front
|
|
origin[1] += 100; // + = left, - = right
|
|
origin[2] += 100; // + = above, - = below
|
|
trap_R_AddLightToScene(origin, 500, 1.0, 1.0, 1.0);
|
|
|
|
origin[0] -= 100;
|
|
origin[1] -= 100;
|
|
origin[2] -= 100;
|
|
trap_R_AddLightToScene(origin, 500, 1.0, 0.0, 0.0);
|
|
|
|
//trap_R_RenderScene(&refdef);
|
|
uiInfo.uiDC.renderScene(&refdef);
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
UI_FindClientHeadFile
|
|
==========================
|
|
*/
|
|
static qboolean UI_FindClientHeadFile(char *filename, int length, const char *teamName, const char *headModelName,
|
|
const char *headSkinName, const char *base, const char *ext)
|
|
{
|
|
char *team, *headsFolder;
|
|
int i;
|
|
|
|
team = "default";
|
|
|
|
if (headModelName[0] == '*') {
|
|
headsFolder = "heads/";
|
|
headModelName++;
|
|
} else {
|
|
headsFolder = "";
|
|
}
|
|
while (1) {
|
|
for (i = 0; i < 2; i++) {
|
|
if (i == 0 && teamName && *teamName) {
|
|
Com_sprintf(filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder,
|
|
headModelName, headSkinName, teamName, base, team, ext);
|
|
} else {
|
|
Com_sprintf(filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder,
|
|
headModelName, headSkinName, base, team, ext);
|
|
}
|
|
if (UI_FileExists(filename)) {
|
|
return qtrue;
|
|
}
|
|
if (i == 0 && teamName && *teamName) {
|
|
Com_sprintf(filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder,
|
|
headModelName, teamName, base, headSkinName, ext);
|
|
} else {
|
|
Com_sprintf(filename, length, "models/players/%s%s/%s_%s.%s", headsFolder,
|
|
headModelName, base, headSkinName, ext);
|
|
}
|
|
if (UI_FileExists(filename)) {
|
|
return qtrue;
|
|
}
|
|
if (!teamName || !*teamName) {
|
|
break;
|
|
}
|
|
}
|
|
// if tried the heads folder first
|
|
if (headsFolder[0]) {
|
|
break;
|
|
}
|
|
headsFolder = "heads/";
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
UI_RegisterClientSkin
|
|
==========================
|
|
*/
|
|
static qboolean UI_RegisterClientSkin(playerInfo_t * pi, const char *modelName, const char *skinName,
|
|
const char *headModelName, const char *headSkinName, const char *teamName)
|
|
{
|
|
char filename[MAX_QPATH];
|
|
|
|
if (teamName && *teamName) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/%s/lower_%s.skin", modelName, teamName,
|
|
skinName);
|
|
} else {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/lower_%s.skin", modelName, skinName);
|
|
}
|
|
pi->legsSkin = trap_R_RegisterSkin(filename);
|
|
if (!pi->legsSkin) {
|
|
if (teamName && *teamName) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/%s/lower_%s.skin",
|
|
modelName, teamName, skinName);
|
|
} else {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/lower_%s.skin", modelName,
|
|
skinName);
|
|
}
|
|
pi->legsSkin = trap_R_RegisterSkin(filename);
|
|
}
|
|
// JBravo: adding
|
|
if (!pi->legsSkin) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/lower_%s.skin",
|
|
modelName, skinName);
|
|
pi->legsSkin = trap_R_RegisterSkin(filename);
|
|
}
|
|
if (teamName && *teamName) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/%s/upper_%s.skin", modelName, teamName,
|
|
skinName);
|
|
} else {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/upper_%s.skin", modelName, skinName);
|
|
}
|
|
pi->torsoSkin = trap_R_RegisterSkin(filename);
|
|
if (!pi->torsoSkin) {
|
|
if (teamName && *teamName) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/%s/upper_%s.skin",
|
|
modelName, teamName, skinName);
|
|
} else {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/upper_%s.skin", modelName,
|
|
skinName);
|
|
}
|
|
pi->torsoSkin = trap_R_RegisterSkin(filename);
|
|
}
|
|
// JBravo: adding
|
|
if (!pi->torsoSkin) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/upper_%s.skin", modelName, skinName);
|
|
pi->torsoSkin = trap_R_RegisterSkin(filename);
|
|
}
|
|
|
|
if (UI_FindClientHeadFile(filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin")) {
|
|
pi->headSkin = trap_R_RegisterSkin(filename);
|
|
}
|
|
|
|
if (!pi->legsSkin || !pi->torsoSkin || !pi->headSkin) {
|
|
// JBravo: No need for errors if the skin doesnt exsist. Lets drop in a default instead.
|
|
Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/lower_default.skin");
|
|
pi->legsSkin = trap_R_RegisterSkin(filename);
|
|
Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/upper_default.skin");
|
|
pi->torsoSkin = trap_R_RegisterSkin(filename);
|
|
Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/head_default.skin");
|
|
pi->headSkin = trap_R_RegisterSkin(filename);
|
|
trap_Cvar_Set("model", "reactionmale/default");
|
|
trap_Cvar_Set("headmodel", "reactionmale/default");
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
UI_ParseAnimationFile
|
|
======================
|
|
*/
|
|
static qboolean UI_ParseAnimationFile(const char *filename, animation_t * animations)
|
|
{
|
|
char *text_p, *prev;
|
|
int len;
|
|
int i;
|
|
char *token;
|
|
float fps;
|
|
int skip;
|
|
char text[20000];
|
|
fileHandle_t f;
|
|
|
|
memset(animations, 0, sizeof(animation_t) * MAX_ANIMATIONS);
|
|
|
|
// load the file
|
|
len = trap_FS_FOpenFile(filename, &f, FS_READ);
|
|
if (len <= 0) {
|
|
return qfalse;
|
|
}
|
|
if (len >= (sizeof(text) - 1)) {
|
|
Com_Printf("File %s too long\n", filename);
|
|
return qfalse;
|
|
}
|
|
trap_FS_Read(text, len, f);
|
|
text[len] = 0;
|
|
trap_FS_FCloseFile(f);
|
|
|
|
COM_Compress(text);
|
|
|
|
// parse the text
|
|
text_p = text;
|
|
skip = 0; // quite the compiler warning
|
|
|
|
// read optional parameters
|
|
while (1) {
|
|
prev = text_p; // so we can unget
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
if (!Q_stricmp(token, "footsteps")) {
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
continue;
|
|
} else if (!Q_stricmp(token, "headoffset")) {
|
|
for (i = 0; i < 3; i++) {
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
} else if (!Q_stricmp(token, "sex")) {
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
// if it is a number, start parsing animations
|
|
if (token[0] >= '0' && token[0] <= '9') {
|
|
text_p = prev; // unget the token
|
|
break;
|
|
}
|
|
|
|
Com_Printf("unknown token '%s' is %s\n", token, filename);
|
|
}
|
|
|
|
// read information for each frame
|
|
for (i = 0; i < MAX_ANIMATIONS; i++) {
|
|
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
animations[i].firstFrame = atoi(token);
|
|
// leg only frames are adjusted to not count the upper body only frames
|
|
if (i == LEGS_WALKCR) {
|
|
skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
|
|
}
|
|
if (i >= LEGS_WALKCR) {
|
|
animations[i].firstFrame -= skip;
|
|
}
|
|
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
animations[i].numFrames = atoi(token);
|
|
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
animations[i].loopFrames = atoi(token);
|
|
|
|
token = COM_Parse(&text_p);
|
|
if (!token) {
|
|
break;
|
|
}
|
|
fps = atof(token);
|
|
if (fps == 0) {
|
|
fps = 1;
|
|
}
|
|
animations[i].frameLerp = 1000 / fps;
|
|
animations[i].initialLerp = 1000 / fps;
|
|
}
|
|
|
|
if (i != MAX_ANIMATIONS) {
|
|
Com_Printf("Error parsing animation file: %s", filename);
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
UI_RegisterClientModelname
|
|
==========================
|
|
*/
|
|
qboolean UI_RegisterClientModelname(playerInfo_t * pi, const char *modelSkinName, const char *headModelSkinName,
|
|
const char *teamName)
|
|
{
|
|
char modelName[MAX_QPATH];
|
|
char skinName[MAX_QPATH];
|
|
char headModelName[MAX_QPATH];
|
|
char headSkinName[MAX_QPATH];
|
|
char filename[MAX_QPATH];
|
|
char *slash;
|
|
|
|
pi->torsoModel = 0;
|
|
pi->headModel = 0;
|
|
|
|
if (!modelSkinName[0]) {
|
|
return qfalse;
|
|
}
|
|
|
|
Q_strncpyz(modelName, modelSkinName, sizeof(modelName));
|
|
slash = strchr(modelName, '/');
|
|
if (!slash) {
|
|
// modelName did not include a skin name
|
|
Q_strncpyz(skinName, "default", sizeof(skinName));
|
|
} else {
|
|
Q_strncpyz(skinName, slash + 1, sizeof(skinName));
|
|
*slash = '\0';
|
|
}
|
|
|
|
Q_strncpyz(headModelName, headModelSkinName, sizeof(headModelName));
|
|
slash = strchr(headModelName, '/');
|
|
if (!slash) {
|
|
// modelName did not include a skin name
|
|
Q_strncpyz(headSkinName, "default", sizeof(skinName));
|
|
} else {
|
|
Q_strncpyz(headSkinName, slash + 1, sizeof(skinName));
|
|
*slash = '\0';
|
|
}
|
|
|
|
// load cmodels before models so filecache works
|
|
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/lower.md3", modelName);
|
|
pi->legsModel = trap_R_RegisterModel(filename);
|
|
if (!pi->legsModel) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/lower.md3", modelName);
|
|
pi->legsModel = trap_R_RegisterModel(filename);
|
|
if (!pi->legsModel) {
|
|
// JBravo: no errors on bad models. Defaults in stead.
|
|
Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/lower.md3");
|
|
pi->legsModel = trap_R_RegisterModel(filename);
|
|
trap_Cvar_Set("model", "reactionmale/default");
|
|
//Com_Printf("Failed to load model file %s\n", filename);
|
|
//return qfalse;
|
|
}
|
|
}
|
|
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/upper.md3", modelName);
|
|
pi->torsoModel = trap_R_RegisterModel(filename);
|
|
if (!pi->torsoModel) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/upper.md3", modelName);
|
|
pi->torsoModel = trap_R_RegisterModel(filename);
|
|
if (!pi->torsoModel) {
|
|
// JBravo: no errors on bad models. Defaults in stead.
|
|
Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/upper.md3");
|
|
pi->torsoModel = trap_R_RegisterModel(filename);
|
|
trap_Cvar_Set("model", "reactionmale/default");
|
|
//Com_Printf("Failed to load model file %s\n", filename);
|
|
//return qfalse;
|
|
}
|
|
}
|
|
|
|
// JBravo: fixed a warning...
|
|
if (headModelName[0] == '*') {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/heads/%s/%s.md3", &headModelName[1],
|
|
&headModelName[1]);
|
|
} else {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/head.md3", headModelName);
|
|
}
|
|
pi->headModel = trap_R_RegisterModel(filename);
|
|
if (!pi->headModel && headModelName[0] != '*') {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/heads/%s/%s.md3", headModelName, headModelName);
|
|
pi->headModel = trap_R_RegisterModel(filename);
|
|
}
|
|
|
|
if (!pi->headModel) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/head.md3");
|
|
pi->headModel = trap_R_RegisterModel(filename);
|
|
trap_Cvar_Set("model", "reactionmale/default");
|
|
//Com_Printf("Failed to load model file %s\n", filename);
|
|
//return qfalse;
|
|
}
|
|
// if any skins failed to load, fall back to default
|
|
if (!UI_RegisterClientSkin(pi, modelName, skinName, headModelName, headSkinName, teamName)) {
|
|
if (!UI_RegisterClientSkin(pi, modelName, "default", headModelName, "default", teamName)) {
|
|
Com_Printf("Failed to load skin file: %s : %s\n", modelName, skinName);
|
|
return qfalse;
|
|
}
|
|
}
|
|
// load the animations
|
|
Com_sprintf(filename, sizeof(filename), "models/players/%s/animation.cfg", modelName);
|
|
if (!UI_ParseAnimationFile(filename, pi->animations)) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/animation.cfg", modelName);
|
|
if (!UI_ParseAnimationFile(filename, pi->animations)) {
|
|
Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/animation.cfg");
|
|
if (!UI_ParseAnimationFile(filename, pi->animations)) {
|
|
Com_Printf("Failed to load animation file %s\n", filename);
|
|
return qfalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_PlayerInfo_SetModel
|
|
===============
|
|
*/
|
|
void UI_PlayerInfo_SetModel(playerInfo_t * pi, const char *model, const char *headmodel, char *teamName)
|
|
{
|
|
memset(pi, 0, sizeof(*pi));
|
|
UI_RegisterClientModelname(pi, model, headmodel, teamName);
|
|
//Blaze: Changed from WP_MACHINEGUN to WP_PISTOL
|
|
pi->weapon = WP_PISTOL;
|
|
pi->currentWeapon = pi->weapon;
|
|
pi->lastWeapon = pi->weapon;
|
|
pi->pendingWeapon = -1;
|
|
pi->weaponTimer = 0;
|
|
pi->chat = qfalse;
|
|
pi->newModel = qtrue;
|
|
UI_PlayerInfo_SetWeapon(pi, pi->weapon);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
UI_PlayerInfo_SetInfo
|
|
===============
|
|
*/
|
|
void UI_PlayerInfo_SetInfo(playerInfo_t * pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles,
|
|
weapon_t weaponNumber, qboolean chat)
|
|
{
|
|
int currentAnim;
|
|
weapon_t weaponNum;
|
|
|
|
pi->chat = chat;
|
|
|
|
// view angles
|
|
VectorCopy(viewAngles, pi->viewAngles);
|
|
|
|
// move angles
|
|
VectorCopy(moveAngles, pi->moveAngles);
|
|
|
|
if (pi->newModel) {
|
|
pi->newModel = qfalse;
|
|
|
|
jumpHeight = 0;
|
|
pi->pendingLegsAnim = 0;
|
|
UI_ForceLegsAnim(pi, legsAnim);
|
|
pi->legs.yawAngle = viewAngles[YAW];
|
|
pi->legs.yawing = qfalse;
|
|
|
|
pi->pendingTorsoAnim = 0;
|
|
UI_ForceTorsoAnim(pi, torsoAnim);
|
|
pi->torso.yawAngle = viewAngles[YAW];
|
|
pi->torso.yawing = qfalse;
|
|
|
|
if (weaponNumber != -1) {
|
|
pi->weapon = weaponNumber;
|
|
pi->currentWeapon = weaponNumber;
|
|
pi->lastWeapon = weaponNumber;
|
|
pi->pendingWeapon = -1;
|
|
pi->weaponTimer = 0;
|
|
UI_PlayerInfo_SetWeapon(pi, pi->weapon);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// weapon
|
|
if (weaponNumber == -1) {
|
|
pi->pendingWeapon = -1;
|
|
pi->weaponTimer = 0;
|
|
} else if (weaponNumber != WP_NONE) {
|
|
pi->pendingWeapon = weaponNumber;
|
|
pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY;
|
|
}
|
|
weaponNum = pi->lastWeapon;
|
|
pi->weapon = weaponNum;
|
|
|
|
if (torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1) {
|
|
torsoAnim = legsAnim = BOTH_DEATH1;
|
|
pi->weapon = pi->currentWeapon = WP_NONE;
|
|
UI_PlayerInfo_SetWeapon(pi, pi->weapon);
|
|
|
|
jumpHeight = 0;
|
|
pi->pendingLegsAnim = 0;
|
|
UI_ForceLegsAnim(pi, legsAnim);
|
|
|
|
pi->pendingTorsoAnim = 0;
|
|
UI_ForceTorsoAnim(pi, torsoAnim);
|
|
|
|
return;
|
|
}
|
|
// leg animation
|
|
currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
|
|
if (legsAnim != LEGS_JUMP && (currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND)) {
|
|
pi->pendingLegsAnim = legsAnim;
|
|
} else if (legsAnim != currentAnim) {
|
|
jumpHeight = 0;
|
|
pi->pendingLegsAnim = 0;
|
|
UI_ForceLegsAnim(pi, legsAnim);
|
|
}
|
|
// torso animation
|
|
if (torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2) {
|
|
//Changed from WP_GAUNTLET to WP_KNIFE
|
|
if (weaponNum == WP_NONE || weaponNum == WP_KNIFE) {
|
|
torsoAnim = TORSO_STAND2;
|
|
} else {
|
|
torsoAnim = TORSO_STAND;
|
|
}
|
|
}
|
|
|
|
if (torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2) {
|
|
//Changed from WP_GAUNTLET to WP_KNIFE
|
|
if (weaponNum == WP_NONE || weaponNum == WP_KNIFE) {
|
|
torsoAnim = TORSO_ATTACK2;
|
|
} else {
|
|
torsoAnim = TORSO_ATTACK;
|
|
}
|
|
pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
|
|
//FIXME play firing sound here
|
|
}
|
|
|
|
currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
|
|
|
|
if (weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP) {
|
|
pi->pendingTorsoAnim = torsoAnim;
|
|
} else if ((currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK) && (torsoAnim != currentAnim)) {
|
|
pi->pendingTorsoAnim = torsoAnim;
|
|
} else if (torsoAnim != currentAnim) {
|
|
pi->pendingTorsoAnim = 0;
|
|
UI_ForceTorsoAnim(pi, torsoAnim);
|
|
}
|
|
}
|