rpgxef/code/ui/ui_players.c

2028 lines
50 KiB
C
Raw Normal View History

// 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.2 //TiM - 0.3
#define SPIN_SPEED 0.9
#define COAST_TIME 1000
static int dp_realtime;
static float jumpHeight;
//TiM : Bolton Table
stringID_table_t BoltonTable[BOLTON_MAX + 1] =
{
{ ENUM2STRING(BOLTON_HEAD) },
{ ENUM2STRING(BOLTON_TORSO) },
{ ENUM2STRING(BOLTON_LEGS) },
{ NULL, -1 }
};
/*
===============
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_0 ) {
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 );
}
if( pi->weaponModel == 0 )
{
if( weaponNum == WP_5 )
{
weaponNum = WP_0;
goto tryagain;
}
weaponNum = WP_5;
goto tryagain;
}
strcpy( path, item->world_model );
COM_StripExtension( path, path );
strcat( path, "_flash.md3" );
pi->flashModel = trap_R_RegisterModel( path );
switch( weaponNum ) {
case WP_8:
MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
break;
case WP_10:
MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
break;
case WP_5:
MAKERGB( pi->flashDlightColor, 0, 0, 0 );
break;
case WP_13:
MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
break;
case WP_1:
//MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
break;
case WP_6:
MAKERGB( pi->flashDlightColor, 0.16, 0.16, 1 );
break;
case WP_7:
MAKERGB( pi->flashDlightColor, 0.6, 0.6, 1 );
break;
case WP_4:
MAKERGB( pi->flashDlightColor, 1, 0.6, 0.6 );
break;
case WP_9:
MAKERGB( pi->flashDlightColor, 0.6, 0.6, 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 == BOTH_JUMP1 ) {
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 == UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue ) ) { //BOTH_ATTACK1 //Hack ROFL. Code can't see the ANIM defines from here. ANIM_ATTACK = 22
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 && !pi->upperEmoting ) {
if ( currentAnim != TORSO_DROPWEAP1 ) {
pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
UI_ForceTorsoAnim( pi, TORSO_DROPWEAP1 );
}
}
if ( pi->torsoAnimationTimer > 0 ) {
return;
}
/*if( currentAnim == TORSO_GESTURE ) {
UI_SetTorsoAnim( pi, TORSO_STAND );
return;
}*/
if( currentAnim == UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue ) ) { //BOTH_ATTACK1 22 = ANIM_ATTACK
//if ( pi->currentWeapon == WP_0 || pi->currentWeapon == WP_5 )
if ( pi->currentWeapon != WP_6
&& pi->currentWeapon != WP_7
&& pi->currentWeapon != WP_8
&& pi->currentWeapon != WP_9 )
{
UI_SetTorsoAnim( pi, BOTH_STAND1 ); //TORSO_STAND
}
else
{
UI_SetTorsoAnim( pi, BOTH_STAND4 ); //TORSO_STAND
}
return;
}
if ( currentAnim == TORSO_DROPWEAP1 ) {
UI_PlayerInfo_SetWeapon( pi, pi->weapon );
pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
UI_ForceTorsoAnim( pi, TORSO_RAISEWEAP1 ); //TORSO_RAISE
return;
}
if ( currentAnim == TORSO_RAISEWEAP1 ) {
UI_SetTorsoAnim( pi, BOTH_STAND1 ); //STAND2
return;
}
//TiM: Was playing a non-loop emote, so go back to default now
if ( pi->upperEmoting ) {
if ( !pi->upperLoopEmote )
{
UI_SetTorsoAnim( pi, BOTH_STAND1 );
pi->upperEmoting = qfalse;
}
else
{
UI_SetTorsoAnim( pi, pi->upperLoopEmote );
pi->upperEmoting = qfalse;
}
return;
}
}
/*
===============
UI_LegsSequencing
===============
*/
static void UI_LegsSequencing( playerInfo_t *pi ) {
int currentAnim;
currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
if ( pi->legsAnimationTimer > 0 ) {
if ( currentAnim == BOTH_JUMP1) {
jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP );
}
return;
}
if ( currentAnim == BOTH_JUMP1) {
UI_ForceLegsAnim( pi, BOTH_LAND1 );
pi->legsAnimationTimer = UI_TIMER_LAND;
jumpHeight = 0;
return;
}
if ( currentAnim == BOTH_LAND1 ) {
UI_SetLegsAnim( pi, BOTH_STAND1 );
return;
}
//TiM: Was playing a non-loop emote, so go back to default now
if ( pi->lowerEmoting ) {
if ( !pi->lowerLoopEmote )
{
UI_SetLegsAnim( pi, BOTH_STAND1 );
pi->lowerEmoting = qfalse;
}
else
{
UI_SetLegsAnim( pi, pi->lowerLoopEmote );
pi->lowerEmoting = qfalse;
}
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 ( anim->numFrames < 0 ) {
UI_SetLerpFrameAnimation( ci, lf, BOTH_STAND1 );
}*/
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 -= uis.frametime;
if ( pi->legsAnimationTimer < 0 ) {
pi->legsAnimationTimer = 0;
}
UI_LegsSequencing( pi );
if ( pi->legs.yawing && ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND1
|| ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND2
|| ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND4 ) )
{
UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN1 );
} else {
UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim );
}
*legsOld = pi->legs.oldFrame;
*legs = pi->legs.frame;
*legsBackLerp = pi->legs.backlerp;
// torso animation
pi->torsoAnimationTimer -= uis.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 = uis.frametime * scale * speed;
if ( move >= swing ) {
move = swing;
*swinging = qfalse;
}
*angle = AngleMod( *angle + move );
} else if ( swing < 0 ) {
move = uis.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 ) != UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qfalse ) //TORSO_STAND2
|| ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qtrue ) ) {
// 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
if ( !uis.spinView ) {
//adjust = UI_MovedirAdjustment( pi ); //TiM: Do we really need this?
adjust = 0;
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 );
}
else {
pi->torso.yawAngle = headAngles[YAW];
pi->legs.yawAngle = headAngles[YAW];
}
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.1, &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.data.sprite.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, vec3_t pOrigin, playerInfo_t *pi, int time ) { //RPG-X : TiM- Origin added
refdef_t refdef;
refEntity_t legs;
refEntity_t torso;
refEntity_t head;
refEntity_t gun;
refEntity_t flash;
vec3_t origin;
int renderfx;
vec3_t mins = {-16, -24, -24};
vec3_t maxs = {16, 16, 32};
float len;
float xx;
int anim;
if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) {
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( trap_S_RegisterSound( "sound/weapons/change.wav" ), 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 / 640.0f * 10.0f); //RPG-X : TiM- 90.0f //Anyone else noticed how the high FOV value distorted the model horribly in the menus? O_o
xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
refdef.fov_y = atan2( refdef.height, xx );
refdef.fov_y *= ( 360 / M_PI );
// calculate distance so the player nearly fills the box
//len = 0.7f * ( 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] );
len = 0.35f * ( maxs[2] - mins[2] ); //TiM: 0.35f
origin[0] = (len / tan( DEG2RAD(refdef.fov_x) * 0.5 )) + pOrigin[0]; //0.5 //Z scale - Conventional 3d, not Q3 ;)
origin[1] = 0.5 * ( mins[1] + maxs[1] ) + pOrigin[1]; //Xscale, adding numbers pushes the model to the left
origin[2] = -0.5 * ( mins[2] + maxs[2] ) + pOrigin[2]; //yScale, adding numbers pushes up
refdef.time = dp_realtime;
trap_R_ClearScene();
//spinView
if ( uis.spinView )
{
pi->viewAngles[YAW] = AngleNormalize360( ( uis.cursorx - uis.cursorpx) + uis.lastYaw );
if ( !trap_Key_IsDown( K_MOUSE1 ) )
{
uis.spinView = qfalse;
uis.lastYaw = pi->viewAngles[YAW];
}
}
//TiM: random emote functionality :)
//first init the timer so this will start a minute after loading the menu
if ( pi->randomEmote && pi->nextEmoteTime == 0 ) {
pi->nextEmoteTime = uis.realtime + ( irandom( 15, 20 ) * 1000 );
}
//whup, time to play a random emote
if ( pi->randomEmote && uis.realtime > pi->nextEmoteTime ) {
//randomly pick an anim
anim = irandom( BOTH_STAND1_RANDOM2, BOTH_STAND1_RANDOM11 );
//make sure we can play this emote
if ( pi->animations[anim].numFrames > 0 ) {
UI_ForceLegsAnim( pi, anim );
UI_ForceTorsoAnim( pi, anim );
//play lower
pi->legsAnimationTimer = pi->animations[ anim ].numFrames * pi->animations[ anim ].frameLerp * ((float)uis.realtime/(float)dp_realtime);
pi->lowerEmoting = qtrue;
pi->torsoAnimationTimer = pi->animations[ anim ].numFrames * pi->animations[ anim ].frameLerp * ((float)uis.realtime/(float)dp_realtime);
pi->upperEmoting = qtrue;
pi->nextEmoteTime = uis.realtime + ( irandom( 10, 20 ) * 1000 ) + pi->legsAnimationTimer;
}
else {
pi->nextEmoteTime = uis.realtime + ( irandom( 3, 8 ) * 1000 );
}
}
// 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);
VectorScale( legs.axis[0], pi->height, legs.axis[0]);
VectorScale( legs.axis[1], (pi->height * pi->weight), legs.axis[1]); //weight... i think
VectorScale( legs.axis[2], pi->height, legs.axis[2]);
legs.origin[2] = legs.origin[2] - (24.0f * (1.0f - pi->height));
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_0 ) {
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 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/chat" ) );
}
//
// add an accent light
// TiM: Holy Hell. This explains why the models are washed out when overBrightBits is active. O_o
// 500 is WAY too high
//
origin[0] -= 100; // + = behind, - = in front
origin[1] += 100; // + = left, - = right
origin[2] += 100; // + = above, - = below
trap_R_AddLightToScene( origin, 100, 1.0, 1.0, 1.0 ); //500
origin[0] -= 100;
origin[1] -= 100;
origin[2] -= 100;
trap_R_AddLightToScene( origin, 100, 1.0, 0.0, 0.0 );
trap_R_RenderScene( &refdef );
}
/*
==========================
UI_RegisterClientSkin
==========================
*/
/*extern char* BG_RegisterRace( const char *name );
static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName ) {
char filename[MAX_QPATH];
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower_%s.skin", modelName, skinName );
pi->legsSkin = trap_R_RegisterSkin( filename );
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper_%s.skin", modelName, skinName );
pi->torsoSkin = trap_R_RegisterSkin( filename );
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/head_%s.skin", modelName, skinName );
pi->headSkin = trap_R_RegisterSkin( filename );
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/groups.cfg", modelName);
strcpy(pi->race, BG_RegisterRace( filename ));
if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) {
return qfalse;
}
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 );
// 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[0] ) {
break;
}
if ( !Q_stricmp( token, "footsteps" ) ) {
token = COM_Parse( &text_p );
if ( !token[0] ) {
break;
}
continue;
} else if ( !Q_stricmp( token, "headoffset" ) ) {
for ( i = 0 ; i < 3 ; i++ ) {
token = COM_Parse( &text_p );
if ( !token[0] ) {
break;
}
}
continue;
} else if ( !Q_stricmp( token, "sex" ) ) {
token = COM_Parse( &text_p );
if ( !token[0] ) {
break;
}
continue;
} else if ( !Q_stricmp( token, "soundpath" ) ) {
token = COM_Parse( &text_p );
if ( !token[0] ) {
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[0] ) {
break;
}
animations[i].firstFrame = atoi( token );
// leg only frames are adjusted to not count the upper body only frames
if ( i == LEGS_KNEEL1 ) {
skip = animations[LEGS_KNEEL1].firstFrame - animations[TORSO_ACTIVATEMEDKIT1].firstFrame;
}
if ( i >= LEGS_KNEEL1) {
animations[i].firstFrame -= skip;
}
token = COM_Parse( &text_p );
if ( !token[0] ) {
break;
}
animations[i].numFrames = atoi( token );
token = COM_Parse( &text_p );
if ( !token[0] ) {
break;
}
animations[i].loopFrames = atoi( token );
token = COM_Parse( &text_p );
if ( !token[0] ) {
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_InitModelData
by TiM
Initialize default values
in case the crazy modder
left out some of the keys.
In most cases, the fields
will just be left blank.
No point in using extra
resources if they weren't
specified.
======================
*/
static void UI_InitModelData( playerInfo_t *pi ) {
pi->hasRanks = qfalse;
//initialize all model + skin data as 0, so it can be told if they don't get
//values assigned in the script parser, in which case we exit.
pi->headModel = 0;
pi->torsoModel = 0;
pi->legsModel = 0;
pi->headSkin = 0;
pi->headSkinBlink = 0; //doesn't matter if left 0; won't end the parser
pi->torsoSkin = 0;
pi->legsSkin = 0;
//doesn't matter if left 0
pi->headBlinkTime.minSeconds = 0;
pi->headBlinkTime.maxSeconds = 0;
pi->nextTalkTime = 0;
pi->currentTalkSkin = 0;
pi->headSkinTalk[0] = 0;
pi->headSkinTalk[1] = 0;
pi->headSkinTalk[2] = 0;
pi->headSkinTalk[3] = 0;
memset( &pi->boltonTags, 0, sizeof(pi->boltonTags));
}
/*
=====================
UI_ParseSkinSetDataFile
by TiM
Parses a separate.skinset
file to get the skin data
for this model.
======================
*/
static qboolean UI_ParseSkinSetDataFile( playerInfo_t *pi, const char *skinSetFrame, const char *charName, const char *skinName )
{
char* skinStar;
char skinSetName[MAX_QPATH];
char skinSetRoute[MAX_QPATH];
char* token;
char* textPtr;
char buffer[5000];
int len;
fileHandle_t f;
int n, i;
int noBlinking = trap_Cvar_VariableValue( "cg_noBlinkingHeads" );
if ( ( skinStar = strstr( skinSetFrame, "*" ) ) == NULL )
{
Com_Printf( S_COLOR_RED "ERROR: No '*' specified in model skin set!\n" );
return qfalse;
}
else
{
//star is at front
if ( skinStar == skinSetFrame )
{
skinStar++;
Com_sprintf( skinSetName, sizeof( skinSetName ), "%s%s", skinName, skinStar );
}
//star is at end
else if ((int)(skinStar - skinSetFrame)+1 == (int)strlen(skinSetFrame) )
{
Q_strncpyz( skinSetName, skinSetFrame, strlen( skinSetFrame ) );
Q_strcat( skinSetName, sizeof( skinSetName ), skinName );
}
else
{
Com_Printf( "ERROR: The '*' in %s must be on either the start or end, not the middle.\n", skinSetFrame );
return qfalse;
}
}
//Com_Printf( S_COLOR_RED "DEBUG: skinSetName = %s \n", skinSetName );
Com_sprintf( skinSetRoute, sizeof( skinSetRoute ), "models/players_rpgx/%s/%s.skinset", charName, skinSetName );
len = trap_FS_FOpenFile( skinSetRoute, &f, FS_READ );
if ( len <= 0 )
{
Com_Printf( S_COLOR_RED "ERROR: Could not open file: %s\n", skinSetRoute );
return qfalse;
}
if ( len > sizeof( buffer) - 1 )
{
Com_Printf( S_COLOR_RED "ERROR: Imported file is too big for buffer: %s. Len is %i\n", skinSetRoute, len );
return qfalse;
}
trap_FS_Read( buffer, len, f );
trap_FS_FCloseFile( f );
if ( !buffer[0] )
{
Com_Printf( S_COLOR_RED "ERROR: Could not import data from %s\n", skinSetRoute );
return qfalse;
}
buffer[len] = '\0';
textPtr = buffer;
token = COM_Parse( &textPtr );
if ( Q_stricmp( token, "{" ) )
{
Com_Printf( S_COLOR_RED "ERROR: Skinset %s did not start with a '{'\n", skinSetRoute );
return qfalse;
}
else
{
while ( 1 )
{ //while we don't hit the closing brace
token = COM_Parse( &textPtr ); //parse
if ( !token[0] ) { //error check
break;
}
//head skin when blinking
//must be before headskin, or the damn thing will think the two are the same :P
if ( !Q_stricmpn( token, "headSkinBlink", 13 ) ) {
if ( COM_ParseString( &textPtr, &token ) ) {
continue;
}
if ( !noBlinking ) {
pi->headSkinBlink = trap_R_RegisterSkin( token );
}
if ( !noBlinking && !pi->headSkinBlink ) {
//We'll alert them, but not cancel the loop
Com_Printf( S_COLOR_RED "WARNING: Couldn't load headSkinBlink: %s\n", token);
}
continue;
}
//head blink time
else if ( !Q_stricmpn( token, "headBlinkTime", 13 ) )
{
//Done this way so we know we got two valid args b4 proceeding
if ( COM_ParseInt( &textPtr, &n ) ) { //first arg
SkipRestOfLine( &textPtr );
continue;
}
if ( COM_ParseInt( &textPtr, &i ) ) { //2nd arg
SkipRestOfLine( &textPtr );
continue;
}
//Bug: if the stupid n00b of a modder made
//the minimum time larger than the max time >.<
if ( n > i )
{
Com_Printf( S_COLOR_RED "ERROR: Minimum blink time was larger than maximum blink time.\n" );
continue;
}
if ( !noBlinking ) {
pi->headBlinkTime.minSeconds = n;
pi->headBlinkTime.maxSeconds = i;
}
continue;
}
else if ( !Q_stricmpn( token, "torsoSkin", 9 ) ) {
if (COM_ParseString( &textPtr, &token ) ) {
continue;
}
pi->torsoSkin = trap_R_RegisterSkin( token );
if (!pi->torsoSkin ) {
Com_Printf( S_COLOR_RED "ERROR: Couldn't load torsoSkin: %s\n", token);
}
continue;
}
else if ( !Q_stricmpn( token, "legsSkin", 8 ) ) {
if (COM_ParseString( &textPtr, &token ) ) {
continue;
}
pi->legsSkin = trap_R_RegisterSkin( token );
if (!pi->legsSkin ) {
Com_Printf( S_COLOR_RED "ERROR: Couldn't load legsSkin: %s\n", token);
}
continue;
}
else if ( !Q_stricmpn( token, "headSkinTalk", 12 ) )
{
SkipBracedSection( &textPtr );
continue;
}
//head skin
else if ( !Q_stricmp( token, "headSkin" ) ) {
if ( COM_ParseString( &textPtr, &token ) ) {
continue;
}
pi->headSkin = trap_R_RegisterSkin( token );
if ( !pi->headSkin ) {
Com_Printf( S_COLOR_RED "ERROR: Couldn't load headSkin: %s\n", token );
return qfalse;
}
continue;
}
if ( !Q_stricmpn( token, "}", 1) ) {
break;
}
}
}
return qtrue;
}
/*
======================
UI_ParseModelDataFile
by TiM
Reads in the .model file
needed to put together
a character model.
======================
*/
qboolean UI_ParseModelDataFile( playerInfo_t *pi, const char *charName,
const char *modelName, const char *skinName ) {
fileHandle_t file;
int file_len;
char charText[20000];
char *textPtr;
char fileName[MAX_QPATH];
int i, n;
char *token;
char legsFileRoute[MAX_QPATH];
qboolean didAnims = qfalse;
qboolean skinSetFound=qfalse;
//create the file route
Com_sprintf( fileName, sizeof(fileName), "models/players_rpgx/%s/%s.model", charName, modelName);
//Okay... gotta get the hang of ANSI C text parsing >.<
//first... I guess load the file
file_len = trap_FS_FOpenFile( fileName, &file, FS_READ );
//Error handle
//if length was 0, ie file not found or was empty
if (file_len <= 0 ) {
return qfalse;
}
//Another error... if text is WAY bigger than our available buffer O_O
if ( file_len >= sizeof( charText ) - 1 ) {
Com_Printf( S_COLOR_RED "Model Data File %s too long... WAY too long\n", fileName );
return qfalse;
}
//initialize the buffer
memset( charText, 0, sizeof( charText ) );
//read data into char array
//i guess we use a char array so we can actually specify size/width.
trap_FS_Read( charText, file_len, file );
//I guess this is needed to mark the EOF.
charText[file_len] = 0;
//Free memory. Close Files
trap_FS_FCloseFile( file );
//default values if needed
UI_InitModelData( pi );
//Used to just clear any previous parse temp data
COM_BeginParseSession();
//transfer our data from a char array to a char ptr.
//needed for the parsing func methinks
textPtr = charText;
token = COM_Parse( &textPtr ); //COM_Parse seems to work by splitting up each line of text by the spaces,
//and then removes that chunk from the original
//Okay, we should have the beginning variable first... which should be a '{'
//from the looks of this, I think we have to do this after
//every parse call. O_O
if ( !token[0] ) {
Com_Printf( S_COLOR_RED "No data found in model data buffer!\n");
return qfalse;
}
if ( Q_stricmp(token, "{" ) ) {
Com_Printf(S_COLOR_RED "Missing { in %s\n", fileName);
return qfalse;
}
while ( 1 ) {
token = COM_Parse( &textPtr );
if (!token[0] || !token ) { //we've hit the end of the file. w00t! exit!
break;
}
//if we randomly find a brace in here (ie a sub-struct that may have no header)
//just skip it. :P
if ( !Q_stricmpn( token, "{", 1 ) ) {
SkipBracedSection ( &textPtr );
}
if ( !Q_stricmpn( token, "animsConfig", 11 ) ) {
if( COM_ParseString( &textPtr, &token ) ) {
continue;
}
//no valid anim file found. Don't give up hope though.
//We have a backup resort at the end if need be. :)
if ( ( didAnims = UI_ParseAnimationFile( token, pi->animations ) ) == qfalse ) {
Com_Printf( S_COLOR_RED "WARNING: Was unable to load file %s.\n", token );
}
continue;
}
//playermodel gender
else if ( !Q_stricmpn( token, "sex", 3 ) ) {
if (COM_ParseString( &textPtr, &token ) ) {
continue;
}
if ( token[0] == 'f' || token[0] == 'F' ) {
pi->gender = GENDER_FEMALE;
} else if ( token[0] == 'n' || token[0] == 'N' ) {
pi->gender = GENDER_NEUTER;
} else {
pi->gender = GENDER_MALE;
}
continue;
}
//character's legs model
else if ( !Q_stricmpn( token, "legsModel", 9 ) ) {
if( COM_ParseString( &textPtr, &token ) ) {
continue;
}
pi->legsModel = trap_R_RegisterModel( token );
if (!pi->legsModel) {
Com_Printf( S_COLOR_RED "ERROR: Unable to load legs model: %s\n", token);
return qfalse;
}
//if loaded no anims yet, copy the legs route to this variable,
//and we'll try again at the end of the function
//if ( ci->animIndex == -1 ) {
Q_strncpyz( legsFileRoute, token, sizeof( legsFileRoute ) );
//} Actually. just copy it regardless. Just in case
continue;
}
//character's torso model
else if ( !Q_stricmpn( token, "torsoModel", 10 ) ) {
if( COM_ParseString( &textPtr, &token ) ) {
continue;
}
pi->torsoModel = trap_R_RegisterModel( token );
//Com_Printf("Torsomodel passed as %s, %i\n", token, (int)ci->torsoModel);
if (!pi->torsoModel) {
Com_Printf( S_COLOR_RED "ERROR: Unable to load torso model: %s\n", token);
return qfalse;
}
continue;
}
//character's headmodel
else if ( !Q_stricmpn( token, "headModel", 9 ) ) {
//return true = no extra text found on this line - bad! O_O!
if( COM_ParseString( &textPtr, &token ) ) {
continue;
}
pi->headModel = trap_R_RegisterModel( token );
if (!pi->headModel) {
Com_Printf( S_COLOR_RED "ERROR: Unable to load head model: %s\n", token);
return qfalse;
}
continue;
}
// Custom bolton models... oi O_o
else if ( !Q_stricmpn( token, "boltonModels", 12 ) ) {
//needed coz '{' could also be on next line
token = COM_Parse( &textPtr );
if ( !token[0] ) { //if that was it
break;
} else { //else, if next character is '{'
if ( !Q_stricmpn( token, "{", 1 ) ) {
token = COM_Parse( &textPtr );
if ( !token[0] ) {
break;
}
//loop till we hit the end of the brackets
i = 0;
while ( Q_stricmp( token, "}" ) ) {
if ( !Q_stricmpn( token, "BOLTON_", 7 ) ) {
pi->boltonTags[i].modelBase = GetIDForString( BoltonTable, token );
if( COM_ParseString( &textPtr, &token ) ) {
continue;
}
if (!Q_stricmpn( token, "tag_", 4 ) ) {
Q_strncpyz(pi->boltonTags[i].tagName, token, sizeof (pi->boltonTags[i].tagName) );
if( COM_ParseString( &textPtr, &token ) ) {
continue;
}
pi->boltonTags[i].tagModel = trap_R_RegisterModel( token );
if (!pi->boltonTags[i].tagModel) {
Com_Printf( S_COLOR_RED "WARNING: Unable to load bolton model: %s\n", token);
}
i++;
if (i > MAX_BOLTONS -1) {
break;
}
}
}
//Com_Printf("Index: %i, Name: %s, Handle: %i\n", ci->boltonTags[ci->numBoltOns].modelBase, ci->boltonTags[ci->numBoltOns].tagName, ci->boltonTags[ci->numBoltOns].tagModel );
token = COM_Parse( &textPtr );
if ( !token[0] ) {
break;
}
}
}
}
}
//whether char is allowed to wear ranks
else if ( !Q_stricmpn( token, "hasRanks", 8 ) ) {
if (COM_ParseInt(&textPtr, &n ) ) {
continue;
}
pi->hasRanks = n;
continue;
}
//TiM - The skinset is defined
else if ( !Q_stricmpn( token, "skinSet", 7 ) ) {
if ( COM_ParseString( &textPtr, &token ) ) {
continue;
}
if ( !UI_ParseSkinSetDataFile( pi, token, charName, skinName ) )
{
Com_Printf( S_COLOR_RED "WARNING: Could not load data from specified skin set in char: %s. Attempting to load default.\n", charName );
}
else
{
skinSetFound = qtrue;
}
continue;
}
}
if ( !skinSetFound )
{
if ( !UI_ParseSkinSetDataFile( pi, va("%s_*", modelName ), charName, skinName ) )
{
Com_Printf( S_COLOR_RED "ERROR: Tried loading default skin set, however it failed.\n");
}
}
//if any of the models or skins were left blank, then output false. Coz we need them. :P
if (!pi->headModel || !pi->torsoModel || !pi->legsModel ) {
Com_Printf( S_COLOR_RED "One or more necessary model files weren't loaded from %s\n", fileName );
return qfalse;
}
if (!pi->headSkin || !pi->torsoSkin || !pi->legsSkin ) {
Com_Printf( S_COLOR_RED "One or more necessary skin files weren't loaded from %s\n", fileName );
return qfalse;
}
//if modder specified no animations file route, or they did, and it sucked (ie -1 ),
//Then try looking for one in the same directory as the lower.mdr file
//k... the goal of this is to take a string like
//models/players_rpgx/crewman_male/lower.mdr
//and turn it into
//models/players_rpgx/crewman_male/animation.cfg
if ( !didAnims && strlen( legsFileRoute ) > 0 ) {
//get length of file route
i = strlen(legsFileRoute);
while( 1 ) {
//if we looped all the way to the end.... ie BAD
if (i <= 0) {
//we obviously have no animation directory :(
Com_Printf(S_COLOR_RED "ERROR: Was unable to calculate location of animation.cfg for %s\n", fileName);
return qfalse;
}
//if this is the first '/' we come across from going from the end to the start
if (legsFileRoute[i] == '/' ) {
//copy i bytes of data from token to animpath (effectively giving us the route, with no file)
Q_strncpyz(legsFileRoute, legsFileRoute, (i = i + 2 )); //+2 for the null char these things auto assign at the end... i think
break; //won't work without it anyway :P
}
i--;
}
//add animation.cfg to the end of the string
Q_strcat(legsFileRoute, sizeof(legsFileRoute), "animation.cfg");
//Com_Printf( S_COLOR_RED "WARNING: Failed to load animation file specified in model config, attempting to load %s\n", legsFileRoute );
//Parse it
if ( !UI_ParseAnimationFile( legsFileRoute, pi->animations) ) {
Com_Printf( "Tried loading anim data from location %s, however nothing was valid.\n", legsFileRoute );
return qfalse;
}
}
else {
if ( !legsFileRoute[0] ) {
Com_Printf( S_COLOR_RED "Couldn't load/locate any player animation data for player: %s.\n", charName );
return qfalse;
}
}
//holy fudgenuggets. after all that checking, we actually made it to the end and have a valid freaking
//model! OWNED!
return qtrue;
}
/*
==========================
UI_RegisterClientModelname
==========================
*/
qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName ) {
char charName[MAX_QPATH];
char modelName[MAX_QPATH];
char skinName[MAX_QPATH];
//char filename[MAX_QPATH];
char *model, *skin;
//char *slash;
int len;
pi->torsoModel = 0;
pi->headModel = 0;
if ( !modelSkinName[0] ) {
return qfalse;
}
Q_strncpyz( charName, modelSkinName, sizeof( charName ) );
/*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 ) );
// truncate modelName
*slash = 0;
}*/
//step 1, take the first bit of the string and put it in the charName var.
if ( ( model = strchr( charName, '/') ) == NULL ) { //if there's no slash
Q_strncpyz( charName, modelSkinName, sizeof( charName ) ); //just set it
} else { //otherwise, isolate the first bit, and copy that
len = strlen( modelSkinName );
Q_strncpyz( charName, modelSkinName, ((int)len - (int)strlen(model)) + 1 );
}
//Com_Printf("%s\n", newInfo.charName);
//slash = strchr( newInfo.modelName, '/' );
if ( !model || !model[1] ) {
// modelName didn not include a skin name
//Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
Q_strncpyz( modelName, "main", sizeof( modelName ) );
Q_strncpyz( skinName, "default", sizeof( skinName ) );
if ( model && !model[1] )
{//if we had a slash, but nothing after, clear it
*model = 0;
}
} else {
//*model++; //bypass the slash
model++;
len = strlen(model);
skin = strchr( model, '/' );
//if there was a model defined, but no skin
if ( !skin || !skin[1] ) {
//no skin, but I'm guessing we gotz a model at least
if ( !skin ) {
Q_strncpyz( modelName, model, sizeof( modelName ) );
}
else {
if ( !skin[1] ) {
Q_strncpyz( modelName, model, (int)strlen(model) );
}
}
Q_strncpyz( skinName, "default", sizeof( skinName ) );
if ( skin && !skin[1] ) {
*skin = 0;
}
} else {
//*skin++;
skin++;
Q_strncpyz( modelName, model, ((int)len - (int)strlen(skin)) );
Q_strncpyz( skinName, skin, sizeof( skinName ) );
}
//Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
// truncate modelName
*model = 0;
}
// load cmodels before models so filecache works
//try loading the main model
if ( !UI_ParseModelDataFile( pi, charName, modelName, skinName ) )
{
Com_Printf( S_COLOR_RED "Was unable to parse model file for character: %s/%s/%s\n", charName, modelName, skinName );
//if that fails, try and load the model's default data at least
if ( !UI_ParseModelDataFile( pi, charName, DEFAULT_MODEL, DEFAULT_SKIN ) )
{
//if THAT fails, try loading our defualt char, with the specfied char's model and skin parms
if ( !UI_ParseModelDataFile( pi, ui_defaultChar.string, modelName, skinName ) )
{
if ( !UI_ParseModelDataFile( pi, DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ) )
{
//if all else fails, try and load the normal default model
return qfalse;
}
}
}
}
/*Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.mdr", modelName );
pi->legsModel = trap_R_RegisterModel( filename );
if ( !pi->legsModel )
{
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.md3", modelName );
pi->legsModel = trap_R_RegisterModel( filename );
if ( !pi->legsModel )
{
Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename );
return qfalse;
}
}
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper.mdr", modelName );
pi->torsoModel = trap_R_RegisterModel( filename );
if ( !pi->torsoModel )
{
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper.md3", modelName );
pi->torsoModel = trap_R_RegisterModel( filename );
if ( !pi->torsoModel ) {
Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename );
return qfalse;
}
}
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/head.md3", modelName );
pi->headModel = trap_R_RegisterModel( filename );
if ( !pi->headModel )
{
Com_Printf( S_COLOR_RED"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 ) ) {
if ( !UI_RegisterClientSkin( pi, modelName, "default" ) ) {
Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
return qfalse;
}
}
// load the animations
Com_sprintf( filename, sizeof( filename ), "models/players2/%s/animation.cfg", modelName );
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 ) {
memset( pi, 0, sizeof(*pi) );
UI_RegisterClientModelname( pi, model );
Q_strncpyz( pi->modelName, model, sizeof( pi->modelName ) );
pi->weapon = WP_0;
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, float height, float weight, qboolean chat ) {
int currentAnim;
weapon_t weaponNum;
pi->chat = chat;
// view angles
VectorCopy( viewAngles, pi->viewAngles );
// move angles
VectorCopy( moveAngles, pi->moveAngles );
//TiM : Clamp weight and height
pi->height = height;
pi->weight = weight;
if ( !pi->weight )
pi->weight = 1.0f;
if ( !pi->height )
pi->height = 1.0f;
pi->height = Com_Clamp( 0.9f, 1.15f, pi->height );
pi->weight = Com_Clamp( 0.9f, 1.1f, pi->weight );
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_0 ) {
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_0;
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 != BOTH_JUMP1 && ( currentAnim == BOTH_JUMP1 || currentAnim == BOTH_LAND1 ) ) {
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) //TiM: TORSO_STAND2
if ( torsoAnim == UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qtrue ) )
{
/*if ( weaponNum == WP_0 || weaponNum == WP_5 )
{
torsoAnim = TORSO_STAND2;
}
else
{
torsoAnim = TORSO_STAND2;
}*/
/*if ( weaponNum == WP_6 || weaponNum == WP_7 )
{
torsoAnim = TORSO_STAND;
}
else
{
torsoAnim = TORSO_STAND2;
}*/
torsoAnim = UI_GetAnim( ANIM_IDLE, pi->currentWeapon, qtrue );
}
//if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 )
if ( torsoAnim == UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue ) )
{
/*if ( weaponNum == WP_0 || weaponNum == WP_5 )
{
torsoAnim = TORSO_ATTACK2;
}
else
{
torsoAnim = TORSO_ATTACK;
}*/
/*if ( weaponNum == WP_6 || weaponNum == WP_7 )
{
torsoAnim = TORSO_ATTACK;
}
else
{
torsoAnim = TORSO_ATTACK2;
}*/
torsoAnim = UI_GetAnim( ANIM_ATTACK, pi->currentWeapon, qtrue );
pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
//FIXME play firing sound here
}
currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISEWEAP1 || currentAnim == TORSO_DROPWEAP1 ) {
pi->pendingTorsoAnim = torsoAnim;
}
else if ( ( /*currentAnim == TORSO_GESTURE ||*/ currentAnim == UI_GetAnim(ANIM_ATTACK, pi->currentWeapon, qtrue) ) && ( torsoAnim != currentAnim ) ) {
pi->pendingTorsoAnim = torsoAnim;
}
else if ( torsoAnim != currentAnim ) {
pi->pendingTorsoAnim = 0;
UI_ForceTorsoAnim( pi, torsoAnim );
}
}