sof2-sdk/code/cgame/cg_players.c

1483 lines
34 KiB
C

// Copyright (C) 2001-2002 Raven Software, Inc.
//
// cg_players.c -- handle the media and animation for player entities
#include "cg_local.h"
#include "..\ghoul2\g2.h"
#include "..\game\inv.h"
typedef struct SCustomSound
{
const char *mGroup;
const int mIndex;
} TCustomSound;
TCustomSound CustomSounds[MAX_CUSTOM_SOUNDS] =
{
{ "Death", 0 },
{ "Death", 1 },
{ "Death", 2 },
{ "Pain", 0 },
{ "Pain", 1 },
{ "Pain", 2 },
};
/*
================
CG_CustomSound
================
*/
sfxHandle_t CG_CustomSound(int clientNum, const char *soundName)
{
return trap_S_RegisterSound( soundName );
}
/*
================
CG_CustomPlayerSound
================
*/
sfxHandle_t CG_CustomPlayerSound(int clientNum, ECustomSounds sound)
{
clientInfo_t *ci;
if ( clientNum < 0 || clientNum >= MAX_CLIENTS )
{
clientNum = 0;
}
ci = &cgs.clientinfo[ clientNum ];
return ci->sounds[sound];
}
/*
==========================
CG_RegisterClientIdentity
==========================
*/
static qboolean CG_RegisterClientIdentity ( clientInfo_t *ci, const char *identityName )
{
char afilename[MAX_QPATH];
TIdentity* identity;
// Find the identity in question
identity = BG_FindIdentity( identityName );
if (!identity )
{
return qfalse;
}
// Register the identity
ci->ghoul2Model = CG_RegisterIdentity( identity, afilename, &ci->gender );
if (!ci->ghoul2Model)
{
return qfalse;
}
// find the bolt point for hanging world models of weapons on
ci->boltWorldWeapon = trap_G2API_AddBolt(ci->ghoul2Model, 0, "rhang_tag_bone");
if ( !BG_ParseAnimationFile( afilename, ci->animations ) )
{
Com_Printf( "Failed to load animation file %s\n", afilename );
trap_G2API_RemoveGhoul2Model(&ci->ghoul2Model, 0);
return qfalse;
}
// Flag model as male or female.
assert(afilename);
ci->isMale=(strstr(afilename,"female"))?qfalse:qtrue;
ci->identity = identity;
return qtrue;
}
/*
====================
CG_ColorFromString
====================
*/
static void CG_ColorFromString( const char *v, vec3_t color ) {
int val;
VectorClear( color );
val = atoi( v );
if ( val < 1 || val > 7 ) {
VectorSet( color, 1, 1, 1 );
return;
}
if ( val & 1 ) {
color[2] = 1.0f;
}
if ( val & 2 ) {
color[1] = 1.0f;
}
if ( val & 4 ) {
color[0] = 1.0f;
}
}
/*
===================
CG_LoadClientInfo
Load it now, taking the disk hits.
This will usually be deferred to a safe time
===================
*/
static void CG_LoadClientInfo( clientInfo_t *ci )
{
int i;
int modelloaded;
int clientNum;
modelloaded = qtrue;
// Initialize all bolt positions
ci->boltWorldWeapon = -1;
ci->boltNightvision = -1;
for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
{
ci->boltGametypeItems[i] = -1;
}
// Register the client identity
if ( !CG_RegisterClientIdentity ( ci, ci->identityName ) )
{
if ( !CG_RegisterClientIdentity ( ci, DEFAULT_IDENTITY ) )
{
Com_Error( ERR_FATAL, "DEFAULT_IDENTITY (%s) failed to register", DEFAULT_IDENTITY );
}
}
// sounds
for( i=0; i< MAX_CUSTOM_SOUNDS; i++ )
{
const char* sound;
ci->sounds[i] = 0;
sound = NULL;
sound = BG_GetModelSound ( ci->identityName, CustomSounds[i].mGroup, CustomSounds[i].mIndex );
// If there is a valid sound to load then load it
if ( sound && sound[0] )
{
ci->sounds[i] = trap_S_RegisterSound( sound );
}
}
ci->deferred = qfalse;
// reset any existing players and bodies, because they might be in bad
// frames for this new model
clientNum = ci - cgs.clientinfo;
for ( i = 0 ; i < MAX_GENTITIES ; i++ )
{
centity_t* cent;
cent = CG_GetEntity ( i );
if ( cent->currentState.clientNum == clientNum &&
cent->currentState.eType == ET_PLAYER )
{
CG_ResetPlayerEntity( cent );
}
}
}
/*
======================
CG_CopyClientInfoModel
======================
*/
static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to )
{
int i;
to->gender = from->gender;
to->boltWorldWeapon = from->boltWorldWeapon;
for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
{
to->boltGametypeItems[i] = from->boltGametypeItems[i];
}
to->boltNightvision = from->boltNightvision;
// Copy all the ghoul2 information too
if (from->ghoul2Model)
{
trap_G2API_DuplicateGhoul2Instance( from->ghoul2Model, &to->ghoul2Model );
}
else
{
assert(0);
Com_Error(ERR_DROP, "CG_CopyClientInfoModel invalid g2 pointer\n");
}
memcpy( to->animations, from->animations, sizeof( to->animations ) );
memcpy( to->sounds, from->sounds, sizeof( to->sounds ) );
}
/*
======================
CG_ScanForExistingClientInfo
======================
*/
static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci )
{
int i;
clientInfo_t *match;
for ( i = 0 ; i < cgs.maxclients ; i++ )
{
match = &cgs.clientinfo[ i ];
if ( !match->infoValid )
{
continue;
}
if ( match->deferred )
{
continue;
}
// Identity name doesnt match
if ( Q_stricmp ( ci->identityName, match->identityName ) )
{
continue;
}
if ( ci->team == TEAM_FREE ||
ci->team == TEAM_SPECTATOR ||
ci->team == match->team )
{
// this clientinfo is identical, so use it's handles
ci->deferred = qfalse;
CG_CopyClientInfoModel( match, ci );
return qtrue;
}
}
// nothing matches, so defer the load
return qfalse;
}
/*
======================
CG_SetDeferredClientInfo
We aren't going to load it now, so grab some other
client's info to use until we have some spare time.
======================
*/
static void CG_SetDeferredClientInfo( clientInfo_t *ci )
{
int i;
clientInfo_t *match;
// spectators can use any skin
if ( ci->team == TEAM_SPECTATOR )
{
for ( i = 0; i < cgs.maxclients; i ++ )
{
match = &cgs.clientinfo[ i ];
if ( !match->infoValid )
{
continue;
}
ci->deferred = qtrue;
CG_CopyClientInfoModel( match, ci );
return;
}
}
// Try to use any skin from the team they are joining
if ( cgs.gametypeData->teams )
{
for ( i = 0; i < cgs.maxclients; i ++ )
{
match = &cgs.clientinfo[ i ];
if ( !match->infoValid )
{
continue;
}
// Must be on the same team
if ( match->team != ci->team )
{
continue;
}
ci->deferred = qtrue;
CG_CopyClientInfoModel( match, ci );
return;
}
}
// find the first valid clientinfo and grab its stuff
else
{
for ( i = 0 ; i < cgs.maxclients ; i++ )
{
match = &cgs.clientinfo[ i ];
if ( !match->infoValid )
{
continue;
}
ci->deferred = qtrue;
CG_CopyClientInfoModel( match, ci );
return;
}
}
CG_LoadClientInfo( ci );
}
/*
======================
CG_ResetClientIdentity
======================
*/
void CG_ResetClientIdentity ( int clientNum )
{
clientInfo_t *ci;
centity_t *cent;
int i;
ci = &cgs.clientinfo[clientNum];
cent = CG_GetEntity ( clientNum );
// Clean up the raw ghoul model
if ( ci->ghoul2Model )
{
trap_G2API_CleanGhoul2Models ( &ci->ghoul2Model );
ci->ghoul2Model = NULL;
}
// Clean up the active ghoul model
if ( cent->ghoul2 )
{
trap_G2API_CleanGhoul2Models ( &cent->ghoul2 );
cent->pe.weaponModelSpot = 0;
cent->pe.weapon = 0;
cent->ghoul2 = NULL;
}
// Reset all bolt position
ci->boltWorldWeapon = -1;
ci->boltNightvision = -1;
for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
{
ci->boltGametypeItems[i] = -1;
}
}
/*
======================
CG_NewClientInfo
======================
*/
void CG_NewClientInfo( int clientNum )
{
clientInfo_t *ci;
clientInfo_t newInfo;
centity_t* cent;
const char *configstring;
const char *v;
ci = &cgs.clientinfo[clientNum];
cent = CG_GetEntity ( clientNum );
configstring = CG_ConfigString( clientNum + CS_PLAYERS );
if ( !configstring[0] )
{
memset( ci, 0, sizeof( *ci ) );
return;
}
// Clear the client models / etc
CG_ResetClientIdentity ( clientNum );
ci->infoValid = qfalse;
// build into a temp buffer so the defer checks can use
// the old value
memset( &newInfo, 0, sizeof( newInfo ) );
// isolate the player's name
v = Info_ValueForKey(configstring, "n");
Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) );
// bot skill
v = Info_ValueForKey( configstring, "skill" );
newInfo.botSkill = atoi( v );
// handicap
v = Info_ValueForKey( configstring, "hc" );
newInfo.handicap = atoi( v );
// wins
v = Info_ValueForKey( configstring, "w" );
newInfo.wins = atoi( v );
// losses
v = Info_ValueForKey( configstring, "l" );
newInfo.losses = atoi( v );
// team
v = Info_ValueForKey( configstring, "t" );
newInfo.team = atoi( v );
// team task
v = Info_ValueForKey( configstring, "tt" );
newInfo.teamTask = atoi(v);
// team leader
v = Info_ValueForKey( configstring, "tl" );
newInfo.teamLeader = atoi(v);
// identity
v = Info_ValueForKey( configstring, "identity" );
Q_strncpyz ( newInfo.identityName, v, sizeof(newInfo.identityName) );
// Inform the ui of the team switch
if ( clientNum == cg.clientNum )
{
char temp[MAX_QPATH];
const char* identityCvar;
trap_Cvar_Set ( "ui_info_team", va("%i",newInfo.team ) );
if (cgs.gametypeData->teams )
{
identityCvar = "team_identity";
}
else
{
identityCvar = "identity";
}
trap_Cvar_VariableStringBuffer ( identityCvar, temp, sizeof(temp) );
if ( Q_stricmp ( temp, newInfo.identityName ) )
{
trap_Cvar_Set ( identityCvar, newInfo.identityName );
}
}
// scan for an existing clientinfo that matches this modelname
// so we can avoid loading checks if possible
if ( !CG_ScanForExistingClientInfo( &newInfo ) )
{
qboolean forceDefer;
forceDefer = trap_MemoryRemaining() < 4000000;
// if we are defering loads, just have it pick the first valid
if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) )
{
// keep whatever they had if it won't violate team skins
CG_SetDeferredClientInfo( &newInfo );
// if we are low on memory, leave them with this model
if ( forceDefer )
{
Com_Printf( "Memory is low. Using deferred model.\n" );
newInfo.deferred = qfalse;
}
}
else
{
CG_LoadClientInfo( &newInfo );
}
}
// replace whatever was there with the new one
newInfo.infoValid = qtrue;
*ci = newInfo;
}
/*
======================
CG_LoadDeferredPlayers
Called each frame when a player is dead
and the scoreboard is up
so deferred players can be loaded
======================
*/
void CG_LoadDeferredPlayers( void )
{
int i;
clientInfo_t *ci;
// scan for a deferred player to load
for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ )
{
if ( ci->infoValid && ci->deferred )
{
// if we are low on memory, leave it deferred
if ( trap_MemoryRemaining() < 4000000 )
{
Com_Printf( "Memory is low. Using deferred model.\n" );
ci->deferred = qfalse;
continue;
}
// Clear the client models / etc
CG_ResetClientIdentity ( i );
CG_LoadClientInfo( ci );
}
}
}
/*
=================
CG_AddPainTwitch
=================
*/
static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles )
{
int t;
float f;
t = cg.time - cent->pe.painTime;
if ( t >= PAIN_TWITCH_TIME ) {
return;
}
f = 1.0 - (float)t / PAIN_TWITCH_TIME;
if ( cent->pe.painDirection ) {
torsoAngles[ROLL] += 20 * f;
} else {
torsoAngles[ROLL] -= 20 * f;
}
}
/*
===============
CG_PlayerFloatSprite
Float a sprite over the player's head
===============
*/
static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader )
{
int rf;
refEntity_t ent;
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )
{
// only show in mirrors
rf = RF_THIRD_PERSON;
}
else
{
rf = 0;
}
memset( &ent, 0, sizeof( ent ) );
VectorCopy( cent->lerpOrigin, ent.origin );
ent.origin[2] += 58;
ent.reType = RT_SPRITE;
ent.customShader = shader;
ent.radius = 10;
ent.renderfx = rf;
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 255;
ent.shaderRGBA[2] = 255;
ent.shaderRGBA[3] = 255;
trap_R_AddRefEntityToScene( &ent );
}
/*
===============
CG_PlayerSprites
Float sprites over the player's head
===============
*/
static void CG_PlayerSprites( centity_t *cent )
{
int team;
team = cgs.clientinfo[ cent->currentState.clientNum ].team;
if ( !(cent->currentState.number == cg.snap->ps.clientNum) &&
!(cent->currentState.eFlags & EF_DEAD) &&
cg.snap->ps.persistant[PERS_TEAM] == team &&
cgs.gametypeData->teams )
{
if (cg_drawFriend.integer)
{
CG_PlayerFloatSprite( cent, team==TEAM_RED?cgs.media.redFriendShader:cgs.media.blueFriendShader );
}
return;
}
}
/*
===============
CG_PlayerShadow
Returns the Z component of the surface being shadowed
should it return a full plane instead of a Z?
===============
*/
#define SHADOW_DISTANCE 256
qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane )
{
vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2};
trace_t trace;
float alpha;
*shadowPlane = 0;
if ( cg_shadows.integer == 0 ) {
return qfalse;
}
// send a trace down from the player to the ground
VectorCopy( cent->lerpOrigin, end );
end[2] -= SHADOW_DISTANCE;
trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
// no shadow if too high
if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) {
return qfalse;
}
*shadowPlane = trace.endpos[2] + 1;
if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows
return qtrue;
}
// fade the shadow out with height
alpha = 1.0 - trace.fraction;
alpha = 1.0;
trap_R_AddDecalToScene ( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal,
cent->currentState.apos.trBase[YAW], 1.0,0.0,1.0,1.0, qfalse, 24, qtrue );
return qtrue;
}
/*
===============
CG_PlayerSplash
Draw a mark at the water surface
===============
*/
static void CG_PlayerSplash( centity_t *cent ) {
vec3_t start, end;
trace_t trace;
int contents;
polyVert_t verts[4];
if ( !cg_shadows.integer ) {
return;
}
VectorCopy( cent->lerpOrigin, end );
end[2] -= 24;
// if the feet aren't in liquid, don't make a mark
// this won't handle moving water brushes, but they wouldn't draw right anyway...
contents = trap_CM_PointContents( end, 0 );
if ( !( contents & ( CONTENTS_WATER | CONTENTS_LAVA ) ) ) {
return;
}
VectorCopy( cent->lerpOrigin, start );
start[2] += 32;
// if the head isn't out of liquid, don't make a mark
contents = trap_CM_PointContents( start, 0 );
if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_LAVA ) ) {
return;
}
// trace down to find the surface
trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_LAVA ) );
if ( trace.fraction == 1.0 ) {
return;
}
// create a mark polygon
VectorCopy( trace.endpos, verts[0].xyz );
verts[0].xyz[0] -= 32;
verts[0].xyz[1] -= 32;
verts[0].st[0] = 0;
verts[0].st[1] = 0;
verts[0].modulate[0] = 255;
verts[0].modulate[1] = 255;
verts[0].modulate[2] = 255;
verts[0].modulate[3] = 255;
VectorCopy( trace.endpos, verts[1].xyz );
verts[1].xyz[0] -= 32;
verts[1].xyz[1] += 32;
verts[1].st[0] = 0;
verts[1].st[1] = 1;
verts[1].modulate[0] = 255;
verts[1].modulate[1] = 255;
verts[1].modulate[2] = 255;
verts[1].modulate[3] = 255;
VectorCopy( trace.endpos, verts[2].xyz );
verts[2].xyz[0] += 32;
verts[2].xyz[1] += 32;
verts[2].st[0] = 1;
verts[2].st[1] = 1;
verts[2].modulate[0] = 255;
verts[2].modulate[1] = 255;
verts[2].modulate[2] = 255;
verts[2].modulate[3] = 255;
VectorCopy( trace.endpos, verts[3].xyz );
verts[3].xyz[0] += 32;
verts[3].xyz[1] -= 32;
verts[3].st[0] = 1;
verts[3].st[1] = 0;
verts[3].modulate[0] = 255;
verts[3].modulate[1] = 255;
verts[3].modulate[2] = 255;
verts[3].modulate[3] = 255;
trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts );
}
/*
===============
CG_UpdatePlayerAnimations
===============
*/
static void CG_UpdatePlayerAnimations ( centity_t* cent )
{
clientInfo_t *ci;
// Make sure its a player
assert ( cent->currentState.eType == ET_PLAYER );
// Retrieve the client info pointer for the given client
ci = &cgs.clientinfo[ cent->currentState.clientNum ];
// Update the leg animation
if ( cent->pe.legs.anim != cent->currentState.legsAnim && cent->currentState.legsAnim != -1 )
{
animation_t *anim;
int flags;
float speed;
anim = &ci->animations[ (cent->currentState.legsAnim & (~ANIM_TOGGLEBIT)) ];
if ( anim->loopFrames == -1 )
{
flags = BONE_ANIM_OVERRIDE|BONE_ANIM_OVERRIDE_FREEZE;
}
else
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
if (cg_animBlend.integer)
{
flags |= BONE_ANIM_BLEND;
}
speed = 1.0f;
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, 50.0f / anim->frameLerp * speed, cg.time, -1, 150);
cent->pe.legs.anim = cent->currentState.legsAnim;
cent->pe.legs.animTime = cg.time;
}
// Update the torso animation
if ( cent->pe.torso.anim != cent->currentState.torsoAnim && cent->currentState.torsoAnim != -1 )
{
animation_t *anim;
int flags;
float time;
anim = &ci->animations[ (cent->currentState.torsoAnim & (~ANIM_TOGGLEBIT)) ];
if ( anim->loopFrames == -1 )
{
flags = BONE_ANIM_OVERRIDE|BONE_ANIM_OVERRIDE_FREEZE;
}
else
{
flags = BONE_ANIM_OVERRIDE_LOOP;
}
if (cg_animBlend.integer)
{
flags |= BONE_ANIM_BLEND;
}
if ( cent->currentState.torsoTimer > 0 )
{
time = cent->currentState.torsoTimer;
time /= anim->numFrames;
}
else
{
time = anim->frameLerp;
}
// Default to 20 FPS.
if ( time <= 0 )
{
time = 1000.0f / 20.0f;
}
trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, 50.0f / time, cg.time, -1, 150);
cent->pe.torso.anim = cent->currentState.torsoAnim ;
cent->pe.torso.animTime = cg.time;
}
}
/*
===============
CG_PlayerGametypeItems
===============
*/
void CG_PlayerGametypeItems ( centity_t* cent, vec3_t angles )
{
clientInfo_t *ci = &cgs.clientinfo[cent->currentState.clientNum];
int i;
int rf;
if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )
{
// only show in mirrors
rf = RF_THIRD_PERSON;
}
else
{
rf = 0;
}
// If the player is wearing goggles then draw them on their head
if ( cent->currentState.eFlags & EF_GOGGLES )
{
refEntity_t item;
if ( ci->boltNightvision == -1 )
{
ci->boltNightvision = trap_G2API_AddBolt( cent->ghoul2, 0, "*head_t" );
}
memset ( &item, 0, sizeof(item) );
item.renderfx = rf | RF_MINLIGHT;
item.ghoul2 = cgs.media.nightVisionModel;
G2_PositionEntityOnBolt ( &item, cent->ghoul2, 0, ci->boltNightvision, cent->lerpOrigin, angles, cent->modelScale );
trap_R_AddRefEntityToScene(&item);
}
// Loop through the gametye items and handle each separately
for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ )
{
// If they dont have the item then skip it
if ( !(cent->currentState.gametypeitems & (1<<i) ))
{
continue;
}
// Need to add bolt to model
if ( cg_items[ MODELINDEX_GAMETYPE_ITEM + i ].boltModel )
{
refEntity_t item;
if ( ci->boltGametypeItems[i] == -1 )
{
ci->boltGametypeItems[i] = trap_G2API_AddBolt( cent->ghoul2, 0, "*back" );
if ( ci->boltGametypeItems[i] == -1 )
{
continue;
}
// Remove all identity items on the back
CG_RemoveIdentityItemsOnBack ( cent );
}
memset ( &item, 0, sizeof(item) );
item.renderfx = rf | RF_MINLIGHT;
item.ghoul2 = cg_items[ MODELINDEX_GAMETYPE_ITEM + i ].boltModel;
G2_PositionEntityOnBolt ( &item, cent->ghoul2, 0, ci->boltGametypeItems[i], cent->lerpOrigin, angles, cent->modelScale );
trap_R_AddRefEntityToScene(&item);
}
}
}
/*
===============
CG_UpdatePlayerWeaponModel
===============
*/
void CG_UpdatePlayerWeaponModel ( centity_t* cent, qboolean force )
{
clientInfo_t *ci = &cgs.clientinfo[cent->currentState.clientNum];
void *model;
model = NULL;
if ( (cent->pe.torso.anim&~ANIM_TOGGLEBIT) == TORSO_USE )
{
int item;
cent->pe.weapon = -1;
for ( item = 0; item < MAX_GAMETYPE_ITEMS; item ++ )
{
if ( cent->currentState.gametypeitems & (1<<item) )
{
model = cg_items[MODELINDEX_GAMETYPE_ITEM+item].useModel;
break;
}
}
}
// If the weapon model hasnt changed then dont update it
else if ( !force && cent->currentState.weapon == cent->pe.weapon )
{
return;
}
else
{
// Ensure the weapon is registered
CG_RegisterWeapon ( cent->currentState.weapon );
model = cg_weapons[cent->currentState.weapon].weaponG2Model;
cent->pe.weapon = cent->currentState.weapon;
}
// Get rid of whats in their hand
if ( cent->pe.weaponModelSpot )
{
trap_G2API_DetachG2Model ( cent->ghoul2, cent->pe.weaponModelSpot );
trap_G2API_RemoveGhoul2Model( &cent->ghoul2, cent->pe.weaponModelSpot );
cent->pe.weaponModelSpot = 0;
}
// If we have a ghoul model then we can attach it to the right hand of the player model
if (model)
{
cent->pe.weaponModelSpot = trap_G2API_CopySpecificGhoul2Model(model, 0, cent->ghoul2, -1 );
trap_G2API_SetBoltInfo(cent->ghoul2, cent->pe.weaponModelSpot, ci->boltWorldWeapon );
trap_G2API_AttachG2Model(cent->ghoul2, cent->pe.weaponModelSpot, cent->ghoul2, ci->boltWorldWeapon, 0);
// Special case to make sure the bayonet shows up for the ak74
switch ( cent->currentState.weapon )
{
case WP_AK74_ASSAULT_RIFLE:
trap_G2API_SetSurfaceOnOff( cent->ghoul2, cent->pe.weaponModelSpot, "bayonet_off", 0 );
break;
case WP_M4_ASSAULT_RIFLE:
trap_G2API_SetSurfaceOnOff( cent->ghoul2, cent->pe.weaponModelSpot, "m203_off", 0 );
break;
}
}
}
/*
===============
CG_UpdatePlayerModel
===============
*/
void CG_UpdatePlayerModel ( centity_t* cent )
{
if ((cent->ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model))
{
CG_ResetPlayerEntity ( cent );
if (!cgs.clientinfo[cent->currentState.clientNum].ghoul2Model)
{
Com_Error(ERR_DROP, "CG_UpdatePlayerModel invalid g2 pointer for client\n");
}
trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model, &cent->ghoul2);
// Force the players weapon model to be updated since their main model changed
CG_UpdatePlayerWeaponModel ( cent, qtrue );
}
}
/*
===============
CG_Player
===============
*/
void CG_Player( centity_t *cent )
{
clientInfo_t *ci;
refEntity_t player;
int clientNum;
int renderfx;
qboolean shadow;
float shadowPlane;
int iwantout = 0;
vec3_t save;
// Dont draw any player models when the round has ended
if ( cg.snap->ps.pm_type == PM_INTERMISSION )
{
return;
}
// the client number is stored in clientNum. It can't be derived
// from the entity number, because a single client may have
// multiple corpses on the level using the same clientinfo
clientNum = cent->currentState.clientNum;
if ( clientNum < 0 || clientNum >= MAX_CLIENTS )
{
Com_Error( ERR_FATAL, "Bad clientNum on player entity");
}
ci = &cgs.clientinfo[ clientNum ];
// it is possible to see corpses from disconnected players that may
// not have valid clientinfo
if ( !ci->infoValid )
{
return;
}
// don't draw the death sequences, as the body queue should be doing it for us
if ( cent->currentState.eFlags & EF_DEAD )
{
return;
}
// Add the player to the radar if on the same team and its a team game
if ( cgs.gametypeData->teams )
{
if ( cg.snap->ps.clientNum != cent->currentState.number && ci->team == cgs.clientinfo[ cg.snap->ps.clientNum ].team )
{
cg.radarEntities[cg.radarEntityCount++] = cent;
}
}
// get the player model information
renderfx = 0;
if ( cent->currentState.number == cg.snap->ps.clientNum)
{
// If rendering third person then we shouldnt draw the player if
// the view origin is too close to the player (ie, inside it)
if ( cg.renderingThirdPerson )
{
vec3_t diff;
vec3_t a;
vec3_t b;
float f;
VectorCopy ( cg.refdef.vieworg, a );
VectorCopy ( cent->lerpOrigin, b );
// a[2] = 0;
// b[2] = 0;
VectorSubtract ( a, b, diff );
f = VectorLengthSquared ( diff );
if ( VectorLengthSquared ( diff ) < 1800 )
{
renderfx = RF_THIRD_PERSON;
}
}
// only draw in mirrors
else
{
renderfx = RF_THIRD_PERSON;
}
}
// Initialize the ref entity
memset (&player, 0, sizeof(player));
// NOTENOTE Temporary
VectorSet(player.modelScale, 1,1,1);
player.radius = 64;
VectorClear(player.angles);
// add the shadow
shadow = CG_PlayerShadow( cent, &shadowPlane );
if (iwantout)
{
return;
}
// add a water splash if partially in and out of water
// CG_PlayerSplash( cent );
if ( cg_shadows.integer == 3 && shadow )
{
renderfx |= RF_SHADOW_PLANE;
}
// use the same origin for all
renderfx |= RF_LIGHTING_ORIGIN | RF_MINLIGHT;
VectorCopy( cent->lerpOrigin, player.origin );
VectorCopy( cent->lerpOrigin, player.lightingOrigin );
player.shadowPlane = shadowPlane;
player.renderfx = renderfx;
// don't positionally lerp at all
VectorCopy (player.origin, player.oldorigin);
// get the animation state (after rotation, to allow feet shuffle)
CG_UpdatePlayerModel ( cent );
CG_UpdatePlayerAnimations ( cent );
CG_UpdatePlayerWeaponModel ( cent, qfalse );
if ( (cent->currentState.eFlags & EF_DEAD ) )
{
AnglesToAxis( cent->lerpAngles, player.axis );
}
else
{
// Force the legs and torso to stay aligned for now to ensure the client
// and server are in sync with the angles.
// TODO: Come up with a way to keep these in sync on both client and server
cent->pe.torso.yawing = qtrue;
cent->pe.torso.pitching = qtrue;
cent->pe.legs.yawing= qtrue;
BG_PlayerAngles( cent->lerpAngles,
player.axis,
cent->pe.ghoulLegsAngles,
cent->pe.ghoulLowerTorsoAngles,
cent->pe.ghoulUpperTorsoAngles,
cent->pe.ghoulHeadAngles,
cent->lerpLeanOffset,
cent->pe.painTime,
cent->pe.painDirection,
cg.time,
&cent->pe.torso,
&cent->pe.legs,
cg.frametime,
cent->lerpVelocity,
(cent->currentState.eFlags & EF_DEAD),
cent->currentState.angles2[YAW],
cent->ghoul2 );
}
VectorCopy ( cent->lerpOrigin, save );
VectorCopy ( player.origin, cent->lerpOrigin );
CG_ScaleModelAxis(&player);
// Copy the ghoul 2 info into the ref entity
CG_SetGhoul2Info(&player, cent);
// Now add the player to the scene
trap_R_AddRefEntityToScene(&player);
//
// add the gun / barrel / flash
//
// need the angle AFTER the lean is added
// vectoangles( player.axis[0], cent->pe.ghoulRootAngles );
// Render any weapon related effects
CG_PlayerWeaponEffects ( &player, cent, ci->team, cent->pe.ghoulLegsAngles );
// Render any of the floating sprites above the players head
CG_PlayerSprites ( cent );
CG_PlayerGametypeItems ( cent, cent->pe.ghoulLegsAngles );
VectorCopy ( save, cent->lerpOrigin );
}
/*
===============
CG_ResetPlayerEntity
A player just came into view or teleported, so reset all animation info
===============
*/
void CG_ResetPlayerEntity( centity_t *cent )
{
cent->errorTime = -99999; // guarantee no error decay added
cent->extrapolated = qfalse;
BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, cent->lerpOrigin );
BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );
VectorCopy( cent->lerpOrigin, cent->rawOrigin );
VectorCopy( cent->lerpAngles, cent->rawAngles );
cent->pe.legs.anim = -1;
cent->pe.torso.anim = -1;
memset( &cent->pe.legs, 0, sizeof( cent->pe.legs ) );
cent->pe.legs.yawAngle = cent->rawAngles[YAW];
cent->pe.legs.yawing = qfalse;
cent->pe.legs.pitchAngle = 0;
cent->pe.legs.pitching = qfalse;
memset( &cent->pe.torso, 0, sizeof( cent->pe.legs ) );
cent->pe.torso.yawAngle = cent->rawAngles[YAW];
cent->pe.torso.yawing = qfalse;
cent->pe.torso.pitchAngle = cent->rawAngles[PITCH];
cent->pe.torso.pitching = qfalse;
}
/*
=================
CG_ProcessIdentityItems
Attaches all the items for a character skin
=================
*/
static void CG_ProcessIdentityItems ( TGhoul2 ghoul2, TInventoryTemplate *items )
{
TSurfaceList *surf;
while ( items )
{
if ( items->mItem )
{
if ( items->mItem->mModel && items->mBolt )
{
items->mModelIndex = trap_G2API_InitGhoul2Model(&ghoul2, items->mItem->mModel, 0, 0, 0, 0, 0);
if (items->mModelIndex != -1)
{
items->mBoltIndex = trap_G2API_AddBolt ( ghoul2, 0, items->mBolt);
if (items->mBoltIndex != -1)
{
trap_G2API_AttachG2Model( ghoul2, items->mModelIndex, ghoul2, items->mBoltIndex, 0);
}
}
}
surf = items->mItem->mOffList;
while(surf)
{
trap_G2API_SetSurfaceOnOff( ghoul2, 0, surf->mName, G2SURFACEFLAG_OFF);
surf = surf->mNext;
}
surf = items->mItem->mOnList;
while(surf)
{
trap_G2API_SetSurfaceOnOff( ghoul2, 0, surf->mName, 0);
surf = surf->mNext;
}
}
items = items->mNext;
}
}
/*
=================
CG_RemoveIdentityItemsOnBack
Removes all identity items on the back of the given entity. There is
currently no way to get the identity items back after removing them
without killing off the player.
=================
*/
void CG_RemoveIdentityItemsOnBack ( centity_t* cent )
{
TInventoryTemplate* items[2];
TIdentity* identity;
int pass;
// Grab the identity structure for this entity
identity = cgs.clientinfo[cent->currentState.number].identity;
if ( !identity )
{
return;
}
// Takes two passes to remove em all since the item lists are in two places
items[0] = identity->mCharacter->mInventory;
items[1] = identity->mSkin->mInventory;
for ( pass = 0; pass < 2; pass ++ )
{
while ( items[pass] )
{
if ( items[pass]->mOnBack && items[pass]->mItem )
{
TSurfaceList *surf;
// Bolt on?
if ( items[pass]->mBolt && *items[pass]->mBolt)
{
trap_G2API_DetachG2Model ( cent->ghoul2, items[pass]->mModelIndex );
trap_G2API_RemoveGhoul2Model ( &cent->ghoul2, items[pass]->mModelIndex );
}
// Surface list? Reversed from what process does
surf = items[pass]->mItem->mOffList;
while(surf)
{
trap_G2API_SetSurfaceOnOff( cent->ghoul2, 0, surf->mName, 0);
surf = surf->mNext;
}
surf = items[pass]->mItem->mOnList;
while(surf)
{
trap_G2API_SetSurfaceOnOff( cent->ghoul2, 0, surf->mName, G2SURFACEFLAG_OFF);
surf = surf->mNext;
}
}
items[pass] = items[pass]->mNext;
}
}
}
/*
=================
CG_RegisterIdentity
Registers an identity
=================
*/
TGhoul2 CG_RegisterIdentity ( TIdentity* identity, char *animationFile, gender_t* gender )
{
char name[MAX_QPATH];
TGenericParser2 skinFile;
TGPGroup *basegroup, *group, *sub;
char temp[20480], *end;
int numPairs;
TGhoul2 ghoul2Ptr;
numPairs = 0;
end = temp;
*end = 0;
ghoul2Ptr = NULL;
*animationFile = 0;
if (!identity )
{
return NULL;
}
if (trap_G2API_InitGhoul2Model( &ghoul2Ptr, identity->mCharacter->mModel, 0, 0, 0, (1<<4), 0) == -1)
{
return NULL;
}
trap_G2API_GetAnimFileNameIndex( ghoul2Ptr, 0, name );
Com_sprintf(animationFile, MAX_QPATH, "%s_mp.cfg", name );
if ( identity->mCharacter->mParent)
{
CG_ProcessIdentityItems( ghoul2Ptr, identity->mCharacter->mParent->mInventory );
}
CG_ProcessIdentityItems( ghoul2Ptr, identity->mCharacter->mInventory);
CG_ProcessIdentityItems( ghoul2Ptr, identity->mSkin->mInventory);
// don't need the mouth
trap_G2API_SetSurfaceOnOff( ghoul2Ptr, 0, "mouth_r", G2SURFACEFLAG_OFF|G2SURFACEFLAG_NODESCENDANTS);
trap_G2API_SetSurfaceOnOff( ghoul2Ptr, 0, "mouth_l", G2SURFACEFLAG_OFF|G2SURFACEFLAG_NODESCENDANTS);
// Parse the g2skin file
Com_sprintf( name, sizeof(name), "models/characters/skins/%s.g2skin", identity->mSkin->mSkin );
skinFile = trap_GP_ParseFile( name, qtrue, qfalse );
if ( !skinFile )
{
trap_G2API_CleanGhoul2Models( &ghoul2Ptr);
return NULL;
}
basegroup = trap_GP_GetBaseParseGroup ( skinFile );
group = trap_GPG_GetSubGroups ( basegroup );
while(group)
{
trap_GPG_GetName ( group, name );
// Parse the material
if ( Q_stricmp ( name, "material") == 0)
{
char matName[MAX_QPATH];
char shaderName[MAX_QPATH];
trap_GPG_FindPairValue ( group, "name", "", matName );
sub = trap_GPG_FindSubGroup ( group, "group");
if (sub)
{
trap_GPG_FindPairValue ( sub, "shader1", "", shaderName );
if (!shaderName[0])
{
trap_GPG_FindPairValue ( sub, "texture1", "", shaderName );
}
}
if (matName[0] && shaderName[0])
{
end += Com_sprintf(end, sizeof(temp) - (end-temp+1), "%s %s ", matName, shaderName);
numPairs++;
}
}
group = trap_GPG_GetNext ( group );
}
trap_GP_Delete(&skinFile);
if (numPairs)
{
qhandle_t handle;
handle = trap_G2API_RegisterSkin( identity->mName, numPairs, temp);
trap_G2API_SetSkin( ghoul2Ptr, 0, handle);
}
// If the gender was requested then look for female in the model
// name as the indicator
if ( gender )
{
Q_strlwr ( (char*)identity->mCharacter->mModel );
*gender = GENDER_MALE;
if ( strstr ( identity->mCharacter->mModel, "female" ) )
{
*gender = GENDER_FEMALE;
}
}
return ghoul2Ptr;
}