// Copyright (C) 1999-2000 Id Software, Inc. // // cg_players.c -- handle the media and animation for player entities #include "cg_local.h" #include "../ghoul2/G2.h" #include "../game/bg_saga.h" #define max(x,y) ((x)>(y)?(x):(y)) extern vmCvar_t cg_thirdPersonAlpha; extern int cgSiegeTeam1PlShader; extern int cgSiegeTeam2PlShader; extern void CG_AddRadarEnt(centity_t *cent); //cg_ents.c extern void CG_AddBracketedEnt(centity_t *cent); //cg_ents.c extern qboolean CG_InFighter( void ); extern qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum ); //for g2 surface routines #define TURN_ON 0x00000000 #define TURN_OFF 0x00000100 extern stringID_table_t animTable [MAX_ANIMATIONS+1]; char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { "*death1", "*death2", "*death3", "*jump1", "*pain25", "*pain50", "*pain75", "*pain100", "*falling1", "*choke1", "*choke2", "*choke3", "*gasp", "*land1", "*taunt", NULL }; //NPC sounds: //Used as a supplement to the basic set for enemies and hazard team // (keep numbers in ascending order in order for variant-capping to work) const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS] = { "*anger1", //Say when acquire an enemy when didn't have one before "*anger2", "*anger3", "*victory1", //Say when killed an enemy "*victory2", "*victory3", "*confuse1", //Say when confused "*confuse2", "*confuse3", "*pushed1", //Say when force-pushed "*pushed2", "*pushed3", "*choke1", "*choke2", "*choke3", "*ffwarn", "*ffturn", NULL }; //Used as a supplement to the basic set for stormtroopers // (keep numbers in ascending order in order for variant-capping to work) const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS] = { "*chase1", "*chase2", "*chase3", "*cover1", "*cover2", "*cover3", "*cover4", "*cover5", "*detected1", "*detected2", "*detected3", "*detected4", "*detected5", "*lost1", "*outflank1", "*outflank2", "*escaping1", "*escaping2", "*escaping3", "*giveup1", "*giveup2", "*giveup3", "*giveup4", "*look1", "*look2", "*sight1", "*sight2", "*sight3", "*sound1", "*sound2", "*sound3", "*suspicious1", "*suspicious2", "*suspicious3", "*suspicious4", "*suspicious5", NULL }; //Used as a supplement to the basic set for jedi // (keep numbers in ascending order in order for variant-capping to work) const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS] = { "*combat1", "*combat2", "*combat3", "*jdetected1", "*jdetected2", "*jdetected3", "*taunt1", "*taunt2", "*taunt3", "*jchase1", "*jchase2", "*jchase3", "*jlost1", "*jlost2", "*jlost3", "*deflect1", "*deflect2", "*deflect3", "*gloat1", "*gloat2", "*gloat3", "*pushfail", NULL }; //Used for DUEL taunts const char *cg_customDuelSoundNames[MAX_CUSTOM_DUEL_SOUNDS] = { "*anger1", //Say when acquire an enemy when didn't have one before "*anger2", "*anger3", "*victory1", //Say when killed an enemy "*victory2", "*victory3", "*taunt1", "*taunt2", "*taunt3", "*deflect1", "*deflect2", "*deflect3", "*gloat1", "*gloat2", "*gloat3", NULL }; void CG_Disintegration(centity_t *cent, refEntity_t *ent); /* ================ CG_CustomSound ================ */ sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { clientInfo_t *ci; int i; int numCSounds = 0; int numCComSounds = 0; int numCExSounds = 0; int numCJediSounds = 0; int numCSiegeSounds = 0; int numCDuelSounds = 0; char lSoundName[MAX_QPATH]; if ( soundName[0] != '*' ) { return trap_S_RegisterSound( soundName ); } COM_StripExtension(soundName, lSoundName); if ( clientNum < 0 ) { clientNum = 0; } if (clientNum >= MAX_CLIENTS) { ci = cg_entities[clientNum].npcClient; } else { ci = &cgs.clientinfo[ clientNum ]; } if (!ci) { return 0; } for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) { if (!cg_customSoundNames[i]) { numCSounds = i; break; } } if (clientNum >= MAX_CLIENTS) { //these are only for npc's for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) { if (!cg_customCombatSoundNames[i]) { numCComSounds = i; break; } } for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) { if (!cg_customExtraSoundNames[i]) { numCExSounds = i; break; } } for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) { if (!cg_customJediSoundNames[i]) { numCJediSounds = i; break; } } } if (cgs.gametype >= GT_TEAM || cg_buildScript.integer) { //siege only for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) { if (!bg_customSiegeSoundNames[i]) { numCSiegeSounds = i; break; } } } if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) { //Duel only for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) { if (!cg_customDuelSoundNames[i]) { numCDuelSounds = i; break; } } } for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { if ( i < numCSounds && !strcmp( lSoundName, cg_customSoundNames[i] ) ) { return ci->sounds[i]; } else if ( (cgs.gametype >= GT_TEAM || cg_buildScript.integer) && i < numCSiegeSounds && !strcmp( lSoundName, bg_customSiegeSoundNames[i] ) ) { //siege only return ci->siegeSounds[i]; } else if ( (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) && i < numCDuelSounds && !strcmp( lSoundName, cg_customDuelSoundNames[i] ) ) { //siege only return ci->duelSounds[i]; } else if ( clientNum >= MAX_CLIENTS && i < numCComSounds && !strcmp( lSoundName, cg_customCombatSoundNames[i] ) ) { //npc only return ci->combatSounds[i]; } else if ( clientNum >= MAX_CLIENTS && i < numCExSounds && !strcmp( lSoundName, cg_customExtraSoundNames[i] ) ) { //npc only return ci->extraSounds[i]; } else if ( clientNum >= MAX_CLIENTS && i < numCJediSounds && !strcmp( lSoundName, cg_customJediSoundNames[i] ) ) { //npc only return ci->jediSounds[i]; } } //CG_Error( "Unknown custom sound: %s", lSoundName ); #ifndef FINAL_BUILD Com_Printf( "Unknown custom sound: %s", lSoundName ); #endif return 0; } /* ============================================================================= CLIENT INFO ============================================================================= */ #define MAX_SURF_LIST_SIZE 1024 qboolean CG_ParseSurfsFile( const char *modelName, const char *skinName, char *surfOff, char *surfOn ) { const char *text_p; int len; const char *token; const char *value; char text[20000]; char sfilename[MAX_QPATH]; fileHandle_t f; int i = 0; while (skinName && skinName[i]) { if (skinName[i] == '|') { //this is a multi-part skin, said skins do not support .surf files return qfalse; } i++; } // Load and parse .surf file Com_sprintf( sfilename, sizeof( sfilename ), "models/players/%s/model_%s.surf", modelName, skinName ); // load the file len = trap_FS_FOpenFile( sfilename, &f, FS_READ ); if ( len <= 0 ) {//no file return qfalse; } if ( len >= sizeof( text ) - 1 ) { Com_Printf( "File %s too long\n", sfilename ); trap_FS_FCloseFile( f ); return qfalse; } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = text; memset( (char *)surfOff, 0, sizeof(surfOff) ); memset( (char *)surfOn, 0, sizeof(surfOn) ); // read information for surfOff and surfOn while ( 1 ) { token = COM_ParseExt( &text_p, qtrue ); if ( !token || !token[0] ) { break; } // surfOff if ( !Q_stricmp( token, "surfOff" ) ) { if ( COM_ParseString( &text_p, &value ) ) { continue; } if ( surfOff && surfOff[0] ) { Q_strcat( surfOff, MAX_SURF_LIST_SIZE, "," ); Q_strcat( surfOff, MAX_SURF_LIST_SIZE, value ); } else { Q_strncpyz( surfOff, value, MAX_SURF_LIST_SIZE ); } continue; } // surfOn if ( !Q_stricmp( token, "surfOn" ) ) { if ( COM_ParseString( &text_p, &value ) ) { continue; } if ( surfOn && surfOn[0] ) { Q_strcat( surfOn, MAX_SURF_LIST_SIZE, ","); Q_strcat( surfOn, MAX_SURF_LIST_SIZE, value ); } else { Q_strncpyz( surfOn, value, MAX_SURF_LIST_SIZE ); } continue; } } return qtrue; } /* ========================== CG_RegisterClientModelname ========================== */ #include "../namespace_begin.h" qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName); qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors ); #include "../namespace_end.h" static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *teamName, int clientNum ) { int handle; char afilename[MAX_QPATH]; char /**GLAName,*/ *slash; char GLAName[MAX_QPATH]; vec3_t tempVec = {0,0,0}; qboolean badModel = qfalse; char surfOff[MAX_SURF_LIST_SIZE]; char surfOn[MAX_SURF_LIST_SIZE]; int checkSkin; char *useSkinName; retryModel: if (badModel) { if (modelName && modelName[0]) { Com_Printf("WARNING: Attempted to load an unsupported multiplayer model %s! (bad or missing bone, or missing animation sequence)\n", modelName); } modelName = "kyle"; skinName = "default"; badModel = qfalse; } // First things first. If this is a ghoul2 model, then let's make sure we demolish this first. if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model)); } if (!BG_IsValidCharacterModel(modelName, skinName)) { modelName = "kyle"; skinName = "default"; } if ( cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE ) { //We won't force colors for siege. BG_ValidateSkinForTeam( ci->modelName, ci->skinName, ci->team, ci->colorOverride ); skinName = ci->skinName; } else { ci->colorOverride[0] = ci->colorOverride[1] = ci->colorOverride[2] = 0.0f; } if (strchr(skinName, '|')) {//three part skin useSkinName = va("models/players/%s/|%s", modelName, skinName); } else { useSkinName = va("models/players/%s/model_%s.skin", modelName, skinName); } checkSkin = trap_R_RegisterSkin(useSkinName); if (checkSkin) { ci->torsoSkin = checkSkin; } else { //fallback to the default skin ci->torsoSkin = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName, skinName)); } Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", modelName ); handle = trap_G2API_InitGhoul2Model(&ci->ghoul2Model, afilename, 0, ci->torsoSkin, 0, 0, 0); if (handle<0) { return qfalse; } // The model is now loaded. trap_G2API_SetSkin(ci->ghoul2Model, 0, ci->torsoSkin, ci->torsoSkin); GLAName[0] = 0; trap_G2API_GetGLAName( ci->ghoul2Model, 0, GLAName); if (GLAName[0] != 0) { if (!strstr(GLAName, "players/_humanoid/") /*&& (!strstr(GLAName, "players/rockettrooper/") || cgs.gametype != GT_SIEGE)*/) //only allow rockettrooper in siege { //Bad! badModel = qtrue; goto retryModel; } } if (!BGPAFtextLoaded) { if (GLAName[0] == 0/*GLAName == NULL*/) { badModel = qtrue; goto retryModel; } Q_strncpyz( afilename, GLAName, sizeof( afilename )); slash = Q_strrchr( afilename, '/' ); if ( slash ) { strcpy(slash, "/animation.cfg"); } // Now afilename holds just the path to the animation.cfg else { // Didn't find any slashes, this is a raw filename right in base (whish isn't a good thing) return qfalse; } //rww - All player models must use humanoid, no matter what. if (Q_stricmp(afilename, "models/players/_humanoid/animation.cfg") /*&& Q_stricmp(afilename, "models/players/rockettrooper/animation.cfg")*/) { Com_Printf( "Model does not use supported animation config.\n"); return qfalse; } else if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1) { Com_Printf( "Failed to load animation file models/players/_humanoid/animation.cfg\n" ); return qfalse; } BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 ); //get the sounds for the humanoid anims // if (cgs.gametype == GT_SIEGE) // { // BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 ); //parse rockettrooper too // } //For the time being, we're going to have all real players use the generic humanoid soundset and that's it. //Only npc's will use model-specific soundsets. // BG_ParseAnimationSndFile(va("models/players/%s/", modelName), 0, -1); } else if (!bgAllEvents[0].eventsParsed) { //make sure the player anim sounds are loaded even if the anims already are BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 ); // if (cgs.gametype == GT_SIEGE) // { // BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 ); // } } if ( CG_ParseSurfsFile( modelName, skinName, surfOff, surfOn ) ) {//turn on/off any surfs const char *token; const char *p; //Now turn on/off any surfaces if ( surfOff && surfOff[0] ) { p = surfOff; while ( 1 ) { token = COM_ParseExt( &p, qtrue ); if ( !token[0] ) {//reached end of list break; } //turn off this surf trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0x00000002/*G2SURFACEFLAG_OFF*/ ); } } if ( surfOn && surfOn[0] ) { p = surfOn; while ( 1 ) { token = COM_ParseExt( &p, qtrue ); if ( !token[0] ) {//reached end of list break; } //turn on this surf trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0 ); } } } ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand"); if (!trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg.time, -1, -1)) { badModel = qtrue; } if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg.time)) { badModel = qtrue; } if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg.time)) { badModel = qtrue; } ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand"); //rhand must always be first bolt. lhand always second. Whichever you want the //jetpack bolted to must always be third. trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg"); //claw bolts trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand_cap_r_arm"); trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand_cap_l_arm"); ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top"); if (ci->bolt_head == -1) { ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow"); } ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion"); //We need a lower lumbar bolt for footsteps ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar"); if (ci->bolt_rhand == -1 || ci->bolt_lhand == -1 || ci->bolt_head == -1 || ci->bolt_motion == -1 || ci->bolt_llumbar == -1) { badModel = qtrue; } if (badModel) { goto retryModel; } if (!Q_stricmp(modelName, "boba_fett")) { //special case, turn off the jetpack surfs trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_rjet", TURN_OFF); trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_cjet", TURN_OFF); trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_ljet", TURN_OFF); } // ent->s.radius = 90; if (clientNum != -1) { /* if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) { trap_G2API_CleanGhoul2Models(&(cg_entities[clientNum].ghoul2)); } trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); */ cg_entities[clientNum].ghoul2weapon = NULL; } Q_strncpyz (ci->teamName, teamName, sizeof(ci->teamName)); // Model icon for drawing the portrait on screen ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/icon_%s", modelName, skinName ) ); if (!ci->modelIcon) { int i = 0; int j; char iconName[1024]; strcpy(iconName, "icon_"); j = strlen(iconName); while (skinName[i] && skinName[i] != '|' && j < 1024) { iconName[j] = skinName[i]; j++; i++; } iconName[j] = 0; if (skinName[i] == '|') { //looks like it actually may be a custom model skin, let's try getting the icon... ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/%s", modelName, iconName ) ); } } 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_ColorFromInt ==================== */ static void CG_ColorFromInt( int val, vec3_t color ) { VectorClear( color ); 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; } } //load anim info int CG_G2SkelForModel(void *g2) { int animIndex = -1; char GLAName[MAX_QPATH]; char *slash; GLAName[0] = 0; trap_G2API_GetGLAName(g2, 0, GLAName); slash = Q_strrchr( GLAName, '/' ); if ( slash ) { strcpy(slash, "/animation.cfg"); animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); } return animIndex; } //get the appropriate anim events file index int CG_G2EvIndexForModel(void *g2, int animIndex) { int evtIndex = -1; char GLAName[MAX_QPATH]; char *slash; if (animIndex == -1) { assert(!"shouldn't happen, bad animIndex"); return -1; } GLAName[0] = 0; trap_G2API_GetGLAName(g2, 0, GLAName); slash = Q_strrchr( GLAName, '/' ); if ( slash ) { slash++; *slash = 0; evtIndex = BG_ParseAnimationEvtFile(GLAName, animIndex, bgNumAnimEvents); } return evtIndex; } #define DEFAULT_FEMALE_SOUNDPATH "chars/mp_generic_female/misc"//"chars/tavion/misc" #define DEFAULT_MALE_SOUNDPATH "chars/mp_generic_male/misc"//"chars/kyle/misc" void CG_LoadCISounds(clientInfo_t *ci, qboolean modelloaded) { fileHandle_t f; qboolean isFemale = qfalse; int i = 0; int fLen = 0; const char *dir; char soundpath[MAX_QPATH]; char soundName[1024]; const char *s; dir = ci->modelName; if ( !ci->skinName || !Q_stricmp( "default", ci->skinName ) ) {//try default sounds.cfg first fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ); if ( !f ) {//no? Look for _default sounds.cfg fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_default.cfg", dir), &f, FS_READ); } } else {//use the .skin associated with this skin fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_%s.cfg", dir, ci->skinName), &f, FS_READ); if ( !f ) {//fall back to default sounds fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ); } } soundpath[0] = 0; if (f) { trap_FS_Read(soundpath, fLen, f); soundpath[fLen] = 0; i = fLen; while (i >= 0 && soundpath[i] != '\n') { if (soundpath[i] == 'f') { isFemale = qtrue; soundpath[i] = 0; } i--; } i = 0; while (soundpath[i] && soundpath[i] != '\r' && soundpath[i] != '\n') { i++; } soundpath[i] = 0; trap_FS_FCloseFile(f); } if (isFemale) { ci->gender = GENDER_FEMALE; } else { ci->gender = GENDER_MALE; } trap_S_ShutUp(qtrue); for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { s = cg_customSoundNames[i]; if ( !s ) { break; } Com_sprintf(soundName, sizeof(soundName), "%s", s+1); COM_StripExtension(soundName, soundName); //strip the extension because we might want .mp3's ci->sounds[i] = 0; // if the model didn't load use the sounds of the default model if (soundpath[0]) { ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) ); } else { if (modelloaded) { ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); } } if (!ci->sounds[i]) { //failed the load, try one out of the generic path if (isFemale) { ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); } else { ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); } } } if (cgs.gametype >= GT_TEAM || cg_buildScript.integer) { //load the siege sounds then for ( i = 0 ; i < MAX_CUSTOM_SIEGE_SOUNDS; i++ ) { s = bg_customSiegeSoundNames[i]; if ( !s ) { break; } Com_sprintf(soundName, sizeof(soundName), "%s", s+1); COM_StripExtension(soundName, soundName); //strip the extension because we might want .mp3's ci->siegeSounds[i] = 0; // if the model didn't load use the sounds of the default model if (soundpath[0]) { ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", soundpath, soundName) ); } else { if (modelloaded) { ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); } } if (!ci->siegeSounds[i]) { //failed the load, try one out of the generic path if (isFemale) { ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); } else { ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); } } } } if (cgs.gametype == GT_DUEL ||cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) { //load the Duel sounds then for ( i = 0 ; i < MAX_CUSTOM_DUEL_SOUNDS; i++ ) { s = cg_customDuelSoundNames[i]; if ( !s ) { break; } Com_sprintf(soundName, sizeof(soundName), "%s", s+1); COM_StripExtension(soundName, soundName); //strip the extension because we might want .mp3's ci->duelSounds[i] = 0; // if the model didn't load use the sounds of the default model if (soundpath[0]) { ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) ); } else { if (modelloaded) { ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); } } if (!ci->duelSounds[i]) { //failed the load, try one out of the generic path if (isFemale) { ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); } else { ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); } } } } trap_S_ShutUp(qfalse); } /* =================== CG_LoadClientInfo Load it now, taking the disk hits. This will usually be deferred to a safe time =================== */ void CG_LoadClientInfo( clientInfo_t *ci ) { qboolean modelloaded; int clientNum; int i; char teamname[MAX_QPATH]; clientNum = ci - cgs.clientinfo; if (clientNum < 0 || clientNum >= MAX_CLIENTS) { clientNum = -1; } ci->deferred = qfalse; /* if (ci->team == TEAM_SPECTATOR) { // 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++ ) { if ( cg_entities[i].currentState.clientNum == clientNum && cg_entities[i].currentState.eType == ET_PLAYER ) { CG_ResetPlayerEntity( &cg_entities[i] ); } } if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); } return; } */ teamname[0] = 0; if( cgs.gametype >= GT_TEAM) { if( ci->team == TEAM_BLUE ) { Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME/*cg_blueTeamName.string*/, sizeof(teamname) ); } else { Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME/*cg_redTeamName.string*/, sizeof(teamname) ); } } if( teamname[0] ) { strcat( teamname, "/" ); } modelloaded = qtrue; if (cgs.gametype == GT_SIEGE && (ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1)) { //yeah.. kind of a hack I guess. Don't care until they are actually ingame with a valid class. if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) ) { CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); } } else { if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, teamname, clientNum ) ) { //CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); //rww - DO NOT error out here! Someone could just type in a nonsense model name and crash everyone's client. //Give it a chance to load default model for this client instead. // fall back to default team name if( cgs.gametype >= GT_TEAM) { // keep skin name if( ci->team == TEAM_BLUE ) { Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); } else { Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); } if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, ci->skinName, teamname, -1 ) ) { CG_Error( "DEFAULT_MODEL / skin (%s/%s) failed to register", DEFAULT_MODEL, ci->skinName ); } } else { if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) ) { CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); } } modelloaded = qfalse; } } if (clientNum != -1) { trap_G2API_ClearAttachedInstance(clientNum); } if (clientNum != -1 && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) { trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2); } trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); //Attach the instance to this entity num so we can make use of client-server //shared operations if possible. trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse); if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1) { //check now to see if we have this bone for setting anims and such cg_entities[clientNum].noFace = qtrue; } cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2); cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex); } ci->newAnims = qfalse; if ( ci->torsoModel ) { orientation_t tag; // if the torso model has the "tag_flag" if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { ci->newAnims = qtrue; } } // sounds if (cgs.gametype == GT_SIEGE && (ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1)) { //don't need to load sounds } else { CG_LoadCISounds(ci, modelloaded); } 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++ ) { if ( cg_entities[i].currentState.clientNum == clientNum && cg_entities[i].currentState.eType == ET_PLAYER ) { CG_ResetPlayerEntity( &cg_entities[i] ); } } } //Take care of initializing all the ghoul2 saber stuff based on clientinfo data. -rww static void CG_InitG2SaberData(int saberNum, clientInfo_t *ci) { trap_G2API_InitGhoul2Model(&ci->ghoul2Weapons[saberNum], ci->saber[saberNum].model, 0, ci->saber[saberNum].skin, 0, 0, 0); if (ci->ghoul2Weapons[saberNum]) { int k = 0; int tagBolt; char *tagName; if (ci->saber[saberNum].skin) { trap_G2API_SetSkin(ci->ghoul2Weapons[saberNum], 0, ci->saber[saberNum].skin, ci->saber[saberNum].skin); } if (ci->saber[saberNum].saberFlags & SFL_BOLT_TO_WRIST) { trap_G2API_SetBoltInfo(ci->ghoul2Weapons[saberNum], 0, 3+saberNum); } else { trap_G2API_SetBoltInfo(ci->ghoul2Weapons[saberNum], 0, saberNum); } while (k < ci->saber[saberNum].numBlades) { tagName = va("*blade%i", k+1); tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, tagName); if (tagBolt == -1) { if (k == 0) { //guess this is an 0ldsk3wl saber tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, "*flash"); if (tagBolt == -1) { assert(0); } break; } if (tagBolt == -1) { assert(0); break; } } k++; } } } /* ====================== CG_CopyClientInfoModel ====================== */ static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { VectorCopy( from->headOffset, to->headOffset ); // to->footsteps = from->footsteps; to->gender = from->gender; to->legsModel = from->legsModel; to->legsSkin = from->legsSkin; to->torsoModel = from->torsoModel; to->torsoSkin = from->torsoSkin; //to->headModel = from->headModel; //to->headSkin = from->headSkin; to->modelIcon = from->modelIcon; to->newAnims = from->newAnims; //to->ghoul2Model = from->ghoul2Model; //rww - Trying to use the same ghoul2 pointer for two seperate clients == DISASTER assert(to->ghoul2Model != from->ghoul2Model); if (to->ghoul2Model && trap_G2_HaveWeGhoul2Models(to->ghoul2Model)) { trap_G2API_CleanGhoul2Models(&to->ghoul2Model); } if (from->ghoul2Model && trap_G2_HaveWeGhoul2Models(from->ghoul2Model)) { trap_G2API_DuplicateGhoul2Instance(from->ghoul2Model, &to->ghoul2Model); } //Don't do this, I guess. Just leave the saber info in the original, so it will be //properly initialized. /* strcpy(to->saberName, from->saberName); strcpy(to->saber2Name, from->saber2Name); while (i < MAX_SABERS) { if (to->ghoul2Weapons[i] && trap_G2_HaveWeGhoul2Models(to->ghoul2Weapons[i])) { trap_G2API_CleanGhoul2Models(&to->ghoul2Weapons[i]); } WP_SetSaber(to->saber, 0, to->saberName); WP_SetSaber(to->saber, 1, to->saber2Name); j = 0; while (j < MAX_SABERS) { if (to->saber[j].model[0]) { CG_InitG2SaberData(j, to); } j++; } i++; } */ to->bolt_head = from->bolt_head; to->bolt_lhand = from->bolt_lhand; to->bolt_rhand = from->bolt_rhand; to->bolt_motion = from->bolt_motion; to->bolt_llumbar = from->bolt_llumbar; to->siegeIndex = from->siegeIndex; memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); memcpy( to->siegeSounds, from->siegeSounds, sizeof( to->siegeSounds ) ); memcpy( to->duelSounds, from->duelSounds, sizeof( to->duelSounds ) ); } /* ====================== CG_ScanForExistingClientInfo ====================== */ static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci, int clientNum ) { int i; clientInfo_t *match; for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid ) { continue; } if ( match->deferred ) { continue; } if ( !Q_stricmp( ci->modelName, match->modelName ) && !Q_stricmp( ci->skinName, match->skinName ) && !Q_stricmp( ci->saberName, match->saberName) && !Q_stricmp( ci->saber2Name, match->saber2Name) // && !Q_stricmp( ci->headModelName, match->headModelName ) // && !Q_stricmp( ci->headSkinName, match->headSkinName ) // && !Q_stricmp( ci->blueTeam, match->blueTeam ) // && !Q_stricmp( ci->redTeam, match->redTeam ) && (cgs.gametype < GT_TEAM || ci->team == match->team) && ci->siegeIndex == match->siegeIndex && match->ghoul2Model && match->bolt_head) //if the bolts haven't been initialized, this "match" is useless to us { // this clientinfo is identical, so use it's handles ci->deferred = qfalse; //rww - Filthy hack. If this is actually the info already belonging to us, just reassign the pointer. //Switching instances when not necessary produces small animation glitches. //Actually, before, were we even freeing the instance attached to the old clientinfo before copying //this new clientinfo over it? Could be a nasty leak possibility. (though this should remedy it in theory) if (clientNum == i) { if (match->ghoul2Model && trap_G2_HaveWeGhoul2Models(match->ghoul2Model)) { //The match has a valid instance (if it didn't, we'd probably already be fudged (^_^) at this state) if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { //First kill the copy we have if we have one. (but it should be null) trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); } VectorCopy( match->headOffset, ci->headOffset ); // ci->footsteps = match->footsteps; ci->gender = match->gender; ci->legsModel = match->legsModel; ci->legsSkin = match->legsSkin; ci->torsoModel = match->torsoModel; ci->torsoSkin = match->torsoSkin; ci->modelIcon = match->modelIcon; ci->newAnims = match->newAnims; ci->bolt_head = match->bolt_head; ci->bolt_lhand = match->bolt_lhand; ci->bolt_rhand = match->bolt_rhand; ci->bolt_motion = match->bolt_motion; ci->bolt_llumbar = match->bolt_llumbar; ci->siegeIndex = match->siegeIndex; memcpy( ci->sounds, match->sounds, sizeof( ci->sounds ) ); memcpy( ci->siegeSounds, match->siegeSounds, sizeof( ci->siegeSounds ) ); memcpy( ci->duelSounds, match->duelSounds, sizeof( ci->duelSounds ) ); //We can share this pointer, because it already belongs to this client. //The pointer itself and the ghoul2 instance is never actually changed, just passed between //clientinfo structures. ci->ghoul2Model = match->ghoul2Model; //Don't need to do this I guess, whenever this function is called the saber stuff should //already be taken care of in the new info. /* while (k < MAX_SABERS) { if (match->ghoul2Weapons[k] && match->ghoul2Weapons[k] != ci->ghoul2Weapons[k]) { if (ci->ghoul2Weapons[k]) { trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]); } ci->ghoul2Weapons[k] = match->ghoul2Weapons[k]; } k++; } */ } } else { 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; // if someone else is already the same models and skins we // can just load the client info for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid || match->deferred ) { continue; } if ( Q_stricmp( ci->skinName, match->skinName ) || Q_stricmp( ci->modelName, match->modelName ) || // Q_stricmp( ci->headModelName, match->headModelName ) || // Q_stricmp( ci->headSkinName, match->headSkinName ) || (cgs.gametype >= GT_TEAM && ci->team != match->team && ci->team != TEAM_SPECTATOR) ) { continue; } /* if (Q_stricmp(ci->saberName, match->saberName) || Q_stricmp(ci->saber2Name, match->saber2Name)) { continue; } */ // just load the real info cause it uses the same models and skins CG_LoadClientInfo( ci ); return; } // if we are in teamplay, only grab a model if the skin is correct if ( cgs.gametype >= GT_TEAM ) { for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid || match->deferred ) { continue; } if ( ci->team != TEAM_SPECTATOR && (Q_stricmp( ci->skinName, match->skinName ) || (cgs.gametype >= GT_TEAM && ci->team != match->team)) ) { continue; } /* if (Q_stricmp(ci->saberName, match->saberName) || Q_stricmp(ci->saber2Name, match->saber2Name)) { continue; } */ ci->deferred = qtrue; CG_CopyClientInfoModel( match, ci ); return; } // load the full model, because we don't ever want to show // an improper team skin. This will cause a hitch for the first // player, when the second enters. Combat shouldn't be going on // yet, so it shouldn't matter CG_LoadClientInfo( ci ); return; } // find the first valid clientinfo and grab its stuff for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid ) { continue; } /* if (Q_stricmp(ci->saberName, match->saberName) || Q_stricmp(ci->saber2Name, match->saber2Name)) { continue; } */ if (match->deferred) { //no deferring off of deferred info. Because I said so. continue; } ci->deferred = qtrue; CG_CopyClientInfoModel( match, ci ); return; } // we should never get here... //CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); //Actually it is possible now because of the unique sabers. CG_LoadClientInfo( ci ); } /* ====================== CG_NewClientInfo ====================== */ #include "../namespace_begin.h" void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); #include "../namespace_end.h" void CG_NewClientInfo( int clientNum, qboolean entitiesInitialized ) { clientInfo_t *ci; clientInfo_t newInfo; const char *configstring; const char *v; char *slash; void *oldGhoul2; void *oldG2Weapons[MAX_SABERS]; int i = 0; int k = 0; qboolean saberUpdate[MAX_SABERS]; ci = &cgs.clientinfo[clientNum]; oldGhoul2 = ci->ghoul2Model; while (k < MAX_SABERS) { oldG2Weapons[k] = ci->ghoul2Weapons[k]; k++; } configstring = CG_ConfigString( clientNum + CS_PLAYERS ); if ( !configstring[0] ) { if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { //clean this stuff up first trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); } k = 0; while (k < MAX_SABERS) { if (ci->ghoul2Weapons[k] && trap_G2_HaveWeGhoul2Models(ci->ghoul2Weapons[k])) { trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]); } k++; } memset( ci, 0, sizeof( *ci ) ); return; // player just left } // 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 ) ); // colors v = Info_ValueForKey( configstring, "c1" ); CG_ColorFromString( v, newInfo.color1 ); newInfo.icolor1 = atoi(v); v = Info_ValueForKey( configstring, "c2" ); CG_ColorFromString( v, newInfo.color2 ); newInfo.icolor2 = atoi(v); // 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 ); //copy team info out to menu if ( clientNum == cg.clientNum) //this is me { trap_Cvar_Set("ui_team", v); } // team task v = Info_ValueForKey( configstring, "tt" ); newInfo.teamTask = atoi(v); // team leader v = Info_ValueForKey( configstring, "tl" ); newInfo.teamLeader = atoi(v); // v = Info_ValueForKey( configstring, "g_redteam" ); // Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); // v = Info_ValueForKey( configstring, "g_blueteam" ); // Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); // model v = Info_ValueForKey( configstring, "model" ); if ( cg_forceModel.integer ) { // forcemodel makes everyone use a single model // to prevent load hitches char modelStr[MAX_QPATH]; char *skin; trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { skin = "default"; } else { *skin++ = 0; } Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); if ( cgs.gametype >= GT_TEAM ) { // keep skin name slash = strchr( v, '/' ); if ( slash ) { Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); } } } else { Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); slash = strchr( newInfo.modelName, '/' ); if ( !slash ) { // modelName didn not include a skin name Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); } else { Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); // truncate modelName *slash = 0; } } if (cgs.gametype == GT_SIEGE) { //entries only sent in siege mode //siege desired team v = Info_ValueForKey( configstring, "sdt" ); if (v && v[0]) { newInfo.siegeDesiredTeam = atoi(v); } else { newInfo.siegeDesiredTeam = 0; } //siege classname v = Info_ValueForKey( configstring, "siegeclass" ); newInfo.siegeIndex = -1; if (v) { siegeClass_t *siegeClass = BG_SiegeFindClassByName(v); if (siegeClass) { //See if this class forces a model, if so, then use it. Same for skin. newInfo.siegeIndex = BG_SiegeFindClassIndexByName(v); if (siegeClass->forcedModel[0]) { Q_strncpyz( newInfo.modelName, siegeClass->forcedModel, sizeof( newInfo.modelName ) ); } if (siegeClass->forcedSkin[0]) { Q_strncpyz( newInfo.skinName, siegeClass->forcedSkin, sizeof( newInfo.skinName ) ); } if (siegeClass->hasForcedSaberColor) { newInfo.icolor1 = siegeClass->forcedSaberColor; CG_ColorFromInt( newInfo.icolor1, newInfo.color1 ); } if (siegeClass->hasForcedSaber2Color) { newInfo.icolor2 = siegeClass->forcedSaber2Color; CG_ColorFromInt( newInfo.icolor2, newInfo.color2 ); } } } } saberUpdate[0] = qfalse; saberUpdate[1] = qfalse; //saber being used v = Info_ValueForKey( configstring, "st" ); if (v && Q_stricmp(v, ci->saberName)) { Q_strncpyz( newInfo.saberName, v, 64 ); WP_SetSaber(clientNum, newInfo.saber, 0, newInfo.saberName); saberUpdate[0] = qtrue; } else { Q_strncpyz( newInfo.saberName, ci->saberName, 64 ); memcpy(&newInfo.saber[0], &ci->saber[0], sizeof(newInfo.saber[0])); newInfo.ghoul2Weapons[0] = ci->ghoul2Weapons[0]; } v = Info_ValueForKey( configstring, "st2" ); if (v && Q_stricmp(v, ci->saber2Name)) { Q_strncpyz( newInfo.saber2Name, v, 64 ); WP_SetSaber(clientNum, newInfo.saber, 1, newInfo.saber2Name); saberUpdate[1] = qtrue; } else { Q_strncpyz( newInfo.saber2Name, ci->saber2Name, 64 ); memcpy(&newInfo.saber[1], &ci->saber[1], sizeof(newInfo.saber[1])); newInfo.ghoul2Weapons[1] = ci->ghoul2Weapons[1]; } if (saberUpdate[0] || saberUpdate[1]) { int j = 0; while (j < MAX_SABERS) { if (saberUpdate[j]) { if (newInfo.saber[j].model[0]) { if (oldG2Weapons[j]) { //free the old instance(s) trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]); oldG2Weapons[j] = 0; } CG_InitG2SaberData(j, &newInfo); } else { if (oldG2Weapons[j]) { //free the old instance(s) trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]); oldG2Weapons[j] = 0; } } cg_entities[clientNum].weapon = 0; cg_entities[clientNum].ghoul2weapon = NULL; //force a refresh } j++; } } //Check for any sabers that didn't get set again, if they didn't, then reassign the pointers for the new ci k = 0; while (k < MAX_SABERS) { if (oldG2Weapons[k]) { newInfo.ghoul2Weapons[k] = oldG2Weapons[k]; } k++; } //duel team v = Info_ValueForKey( configstring, "dt" ); if (v) { newInfo.duelTeam = atoi(v); } else { newInfo.duelTeam = 0; } // force powers v = Info_ValueForKey( configstring, "forcepowers" ); Q_strncpyz( newInfo.forcePowers, v, sizeof( newInfo.forcePowers ) ); if (cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE ) { //We won't force colors for siege. BG_ValidateSkinForTeam( newInfo.modelName, newInfo.skinName, newInfo.team, newInfo.colorOverride ); } else { newInfo.colorOverride[0] = newInfo.colorOverride[1] = newInfo.colorOverride[2] = 0.0f; } // scan for an existing clientinfo that matches this modelname // so we can avoid loading checks if possible if ( !CG_ScanForExistingClientInfo( &newInfo, clientNum ) ) { // if we are defering loads, just have it pick the first valid if (cg.snap && cg.snap->ps.clientNum == clientNum) { //rww - don't defer your own client info ever CG_LoadClientInfo( &newInfo ); } else if ( cg_deferPlayers.integer && cgs.gametype != GT_SIEGE && !cg_buildScript.integer && !cg.loading ) { // keep whatever they had if it won't violate team skins CG_SetDeferredClientInfo( &newInfo ); } else { CG_LoadClientInfo( &newInfo ); } } // replace whatever was there with the new one newInfo.infoValid = qtrue; if (ci->ghoul2Model && ci->ghoul2Model != newInfo.ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { //We must kill this instance before we remove our only pointer to it from the cgame. //Otherwise we will end up with extra instances all over the place, I think. trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); } *ci = newInfo; //force a weapon change anyway, for all clients being rendered to the current client while (i < MAX_CLIENTS) { cg_entities[i].ghoul2weapon = NULL; i++; } if (clientNum != -1) { //don't want it using an invalid pointer to share trap_G2API_ClearAttachedInstance(clientNum); } // Check if the ghoul2 model changed in any way. This is safer than assuming we have a legal cent shile loading info. if (entitiesInitialized && ci->ghoul2Model && (oldGhoul2 != ci->ghoul2Model)) { // Copy the new ghoul2 model to the centity. animation_t *anim; centity_t *cent = &cg_entities[clientNum]; anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.legsAnim) ]; if (anim) { int flags = BONE_ANIM_OVERRIDE_FREEZE; int firstFrame = anim->firstFrame; int setFrame = -1; float animSpeed = 50.0f / anim->frameLerp; if (anim->loopFrames != -1) { flags = BONE_ANIM_OVERRIDE_LOOP; } if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames)) { setFrame = cent->pe.legs.frame; } //rww - Set the animation again because it just got reset due to the model change trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); cg_entities[clientNum].currentState.legsAnim = 0; } anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.torsoAnim) ]; if (anim) { int flags = BONE_ANIM_OVERRIDE_FREEZE; int firstFrame = anim->firstFrame; int setFrame = -1; float animSpeed = 50.0f / anim->frameLerp; if (anim->loopFrames != -1) { flags = BONE_ANIM_OVERRIDE_LOOP; } if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames)) { setFrame = cent->pe.torso.frame; } //rww - Set the animation again because it just got reset due to the model change trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); cg_entities[clientNum].currentState.torsoAnim = 0; } if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) { trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2); } trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); if (clientNum != -1) { //Attach the instance to this entity num so we can make use of client-server //shared operations if possible. trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse); } if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1) { //check now to see if we have this bone for setting anims and such cg_entities[clientNum].noFace = qtrue; } cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2); cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex); if (cg_entities[clientNum].currentState.number != cg.predictedPlayerState.clientNum && cg_entities[clientNum].currentState.weapon == WP_SABER) { cg_entities[clientNum].weapon = cg_entities[clientNum].currentState.weapon; if (cg_entities[clientNum].ghoul2 && ci->ghoul2Model) { CG_CopyG2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon, cg_entities[clientNum].ghoul2); cg_entities[clientNum].ghoul2weapon = CG_G2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon); } if (!cg_entities[clientNum].currentState.saberHolstered) { //if not holstered set length and desired length for both blades to full right now. int j; BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); i = 0; while (i < MAX_SABERS) { j = 0; while (j < ci->saber[i].numBlades) { ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax; j++; } i++; } } } } } qboolean cgQueueLoad = qfalse; /* ====================== CG_ActualLoadDeferredPlayers Called at the beginning of CG_Player if cgQueueLoad is set. ====================== */ void CG_ActualLoadDeferredPlayers( 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 ) { CG_LoadClientInfo( ci ); // break; } } } /* ====================== 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 ) { cgQueueLoad = qtrue; } /* ============================================================================= PLAYER ANIMATION ============================================================================= */ #define FOOTSTEP_DISTANCE 32 static void _PlayerFootStep( const vec3_t origin, const float orientation, const float radius, centity_t *const cent, footstepType_t footStepType ) { vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2}; trace_t trace; footstep_t soundType = FOOTSTEP_TOTAL; qboolean bMark = qfalse; qhandle_t footMarkShader; int effectID = -1; //float alpha; // send a trace down from the player to the ground VectorCopy( origin, end ); end[2] -= FOOTSTEP_DISTANCE; trap_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID ); // no shadow if too high if ( trace.fraction >= 1.0f ) { return; } //check for foot-steppable surface flag switch( trace.surfaceFlags & MATERIAL_MASK ) { case MATERIAL_MUD: bMark = qtrue; if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_MUDRUN; } else { soundType = FOOTSTEP_MUDWALK; } effectID = cgs.effects.footstepMud; break; case MATERIAL_DIRT: bMark = qtrue; if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_DIRTRUN; } else { soundType = FOOTSTEP_DIRTWALK; } effectID = cgs.effects.footstepSand; break; case MATERIAL_SAND: bMark = qtrue; if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_SANDRUN; } else { soundType = FOOTSTEP_SANDWALK; } effectID = cgs.effects.footstepSand; break; case MATERIAL_SNOW: bMark = qtrue; if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_SNOWRUN; } else { soundType = FOOTSTEP_SNOWWALK; } effectID = cgs.effects.footstepSnow; break; case MATERIAL_SHORTGRASS: case MATERIAL_LONGGRASS: if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_GRASSRUN; } else { soundType = FOOTSTEP_GRASSWALK; } break; case MATERIAL_SOLIDMETAL: if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_METALRUN; } else { soundType = FOOTSTEP_METALWALK; } break; case MATERIAL_HOLLOWMETAL: if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_PIPERUN; } else { soundType = FOOTSTEP_PIPEWALK; } break; case MATERIAL_GRAVEL: if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_GRAVELRUN; } else { soundType = FOOTSTEP_GRAVELWALK; } effectID = cgs.effects.footstepGravel; break; case MATERIAL_CARPET: case MATERIAL_FABRIC: case MATERIAL_CANVAS: case MATERIAL_RUBBER: case MATERIAL_PLASTIC: if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_RUGRUN; } else { soundType = FOOTSTEP_RUGWALK; } break; case MATERIAL_SOLIDWOOD: case MATERIAL_HOLLOWWOOD: if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_WOODRUN; } else { soundType = FOOTSTEP_WOODWALK; } break; default: //fall through case MATERIAL_GLASS: case MATERIAL_WATER: case MATERIAL_FLESH: case MATERIAL_BPGLASS: case MATERIAL_DRYLEAVES: case MATERIAL_GREENLEAVES: case MATERIAL_TILES: case MATERIAL_PLASTER: case MATERIAL_SHATTERGLASS: case MATERIAL_ARMOR: case MATERIAL_COMPUTER: case MATERIAL_CONCRETE: case MATERIAL_ROCK: case MATERIAL_ICE: case MATERIAL_MARBLE: if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { soundType = FOOTSTEP_STONERUN; } else { soundType = FOOTSTEP_STONEWALK; } break; } if (soundType < FOOTSTEP_TOTAL) { trap_S_StartSound( NULL, cent->currentState.clientNum, CHAN_BODY, cgs.media.footsteps[soundType][rand()&3] ); } if ( cg_footsteps.integer < 4 ) {//debugging - 4 always does footstep effect if ( cg_footsteps.integer < 2 ) //1 for sounds, 2 for effects, 3 for marks { return; } } if ( effectID != -1 ) { trap_FX_PlayEffectID( effectID, trace.endpos, trace.plane.normal, -1, -1 ); } if ( cg_footsteps.integer < 4 ) {//debugging - 4 always does footstep effect if ( !bMark || cg_footsteps.integer < 3 ) //1 for sounds, 2 for effects, 3 for marks { return; } } switch ( footStepType ) { case FOOTSTEP_HEAVY_R: footMarkShader = cgs.media.fshrMarkShader; break; case FOOTSTEP_HEAVY_L: footMarkShader = cgs.media.fshlMarkShader; break; case FOOTSTEP_R: footMarkShader = cgs.media.fsrMarkShader; break; default: case FOOTSTEP_L: footMarkShader = cgs.media.fslMarkShader; break; } // fade the shadow out with height // alpha = 1.0 - trace.fraction; // add the mark as a temporary, so it goes directly to the renderer // without taking a spot in the cg_marks array if (trace.plane.normal[0] || trace.plane.normal[1] || trace.plane.normal[2]) { CG_ImpactMark( footMarkShader, trace.endpos, trace.plane.normal, orientation, 1,1,1, 1.0f, qfalse, radius, qfalse ); } } static void CG_PlayerFootsteps( centity_t *cent, footstepType_t footStepType ) { if ( !cg_footsteps.integer ) { return; } //FIXME: make this a feature of NPCs in the NPCs.cfg? Specify a footstep shader, if any? if ( cent->currentState.NPC_class != CLASS_ATST && cent->currentState.NPC_class != CLASS_CLAW && cent->currentState.NPC_class != CLASS_FISH && cent->currentState.NPC_class != CLASS_FLIER2 && cent->currentState.NPC_class != CLASS_GLIDER && cent->currentState.NPC_class != CLASS_INTERROGATOR && cent->currentState.NPC_class != CLASS_MURJJ && cent->currentState.NPC_class != CLASS_PROBE && cent->currentState.NPC_class != CLASS_R2D2 && cent->currentState.NPC_class != CLASS_R5D2 && cent->currentState.NPC_class != CLASS_REMOTE && cent->currentState.NPC_class != CLASS_SEEKER && cent->currentState.NPC_class != CLASS_SENTRY && cent->currentState.NPC_class != CLASS_SWAMP ) { mdxaBone_t boltMatrix; vec3_t tempAngles, sideOrigin; int footBolt = -1; tempAngles[PITCH] = 0; tempAngles[YAW] = cent->pe.legs.yawAngle; tempAngles[ROLL] = 0; switch ( footStepType ) { case FOOTSTEP_R: case FOOTSTEP_HEAVY_R: footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot");//cent->gent->footRBolt; break; case FOOTSTEP_L: case FOOTSTEP_HEAVY_L: default: footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot");//cent->gent->footLBolt; break; } //FIXME: get yaw orientation of the foot and use on decal trap_G2API_GetBoltMatrix( cent->ghoul2, 0, footBolt, &boltMatrix, tempAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, sideOrigin ); sideOrigin[2] += 15; //fudge up a bit for coplanar _PlayerFootStep( sideOrigin, cent->pe.legs.yawAngle, 6, cent, footStepType ); } } void CG_PlayerAnimEventDo( centity_t *cent, animevent_t *animEvent ) { soundChannel_t channel = CHAN_AUTO; clientInfo_t *client = NULL; qhandle_t swingSound = 0; qhandle_t spinSound = 0; if ( !cent || !animEvent ) { return; } switch ( animEvent->eventType ) { case AEV_SOUNDCHAN: channel = (soundChannel_t)animEvent->eventData[AED_SOUNDCHANNEL]; case AEV_SOUND: { // are there variations on the sound? const int holdSnd = animEvent->eventData[ AED_SOUNDINDEX_START+Q_irand( 0, animEvent->eventData[AED_SOUND_NUMRANDOMSNDS] ) ]; if ( holdSnd > 0 ) { trap_S_StartSound( NULL, cent->currentState.number, channel, holdSnd ); } } break; case AEV_SABER_SWING: if (cent->currentState.eType == ET_NPC) { client = cent->npcClient; assert(client); } else { client = &cgs.clientinfo[cent->currentState.clientNum]; } if ( client && client->infoValid && client->saber[animEvent->eventData[AED_SABER_SWING_SABERNUM]].swingSound[0] ) {//custom swing sound swingSound = client->saber[0].swingSound[Q_irand(0,2)]; } else { int randomSwing = 1; switch ( animEvent->eventData[AED_SABER_SWING_TYPE] ) { default: case 0://SWING_FAST randomSwing = Q_irand( 1, 3 ); break; case 1://SWING_MEDIUM randomSwing = Q_irand( 4, 6 ); break; case 2://SWING_STRONG randomSwing = Q_irand( 7, 9 ); break; } swingSound = trap_S_RegisterSound(va("sound/weapons/saber/saberhup%i.wav", randomSwing)); } trap_S_StartSound(cent->currentState.pos.trBase, cent->currentState.number, CHAN_AUTO, swingSound ); break; case AEV_SABER_SPIN: if (cent->currentState.eType == ET_NPC) { client = cent->npcClient; assert(client); } else { client = &cgs.clientinfo[cent->currentState.clientNum]; } if ( client && client->infoValid && client->saber[AED_SABER_SPIN_SABERNUM].spinSound ) {//use override spinSound = client->saber[AED_SABER_SPIN_SABERNUM].spinSound; } else { switch ( animEvent->eventData[AED_SABER_SPIN_TYPE] ) { case 0://saberspinoff spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspinoff.wav" ); break; case 1://saberspin spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin.wav" ); break; case 2://saberspin1 spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" ); break; case 3://saberspin2 spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" ); break; case 4://saberspin3 spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" ); break; default://random saberspin1-3 spinSound = trap_S_RegisterSound( va( "sound/weapons/saber/saberspin%d.wav", Q_irand(1,3)) ); break; } } if ( spinSound ) { trap_S_StartSound( NULL, cent->currentState.clientNum, CHAN_AUTO, spinSound ); } break; case AEV_FOOTSTEP: CG_PlayerFootsteps( cent, (footstepType_t)animEvent->eventData[AED_FOOTSTEP_TYPE] ); break; case AEV_EFFECT: #if 0 //SP method //add bolt, play effect if ( animEvent->stringData != NULL && cent && cent->gent && cent->gent->ghoul2.size() ) {//have a bolt name we want to use animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData ); animEvent->stringData = NULL;//so we don't try to do this again } if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) {//have a bolt we want to play the effect on G_PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->gent->playerModel, animEvent->eventData[AED_BOLTINDEX], cent->currentState.clientNum ); } else {//play at origin? FIXME: maybe allow a fwd/rt/up offset? theFxScheduler.PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, qfalse ); } #else //my method if (animEvent->stringData && animEvent->stringData[0] && cent && cent->ghoul2) { animEvent->eventData[AED_MODELINDEX] = 0; if ( ( Q_stricmpn( "*blade", animEvent->stringData, 6 ) == 0 || Q_stricmp( "*flash", animEvent->stringData ) == 0 ) ) {//must be a weapon, try weapon 0? animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 1, animEvent->stringData ); if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) {//found it! animEvent->eventData[AED_MODELINDEX] = 1; } else {//hmm, just try on the player model, then? animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData ); } } else { animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData ); } animEvent->stringData[0] = 0; } if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) { vec3_t lAngles; vec3_t bPoint, bAngle; mdxaBone_t matrix; VectorSet(lAngles, 0, cent->lerpAngles[YAW], 0); trap_G2API_GetBoltMatrix(cent->ghoul2, animEvent->eventData[AED_MODELINDEX], animEvent->eventData[AED_BOLTINDEX], &matrix, lAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bPoint); VectorSet(bAngle, 0, 1, 0); trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], bPoint, bAngle, -1, -1); } else { vec3_t bAngle; VectorSet(bAngle, 0, 1, 0); trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, bAngle, -1, -1); } #endif break; //Would have to keep track of this on server to for these, it's not worth it. case AEV_FIRE: case AEV_MOVE: break; /* case AEV_FIRE: //add fire event if ( animEvent->eventData[AED_FIRE_ALT] ) { G_AddEvent( cent->gent, EV_ALT_FIRE, 0 ); } else { G_AddEvent( cent->gent, EV_FIRE_WEAPON, 0 ); } break; case AEV_MOVE: //make him jump if ( cent && cent->gent && cent->gent->client ) { if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE ) {//on something vec3_t fwd, rt, up, angles = {0, cent->gent->client->ps.viewangles[YAW], 0}; AngleVectors( angles, fwd, rt, up ); //FIXME: set or add to velocity? VectorScale( fwd, animEvent->eventData[AED_MOVE_FWD], cent->gent->client->ps.velocity ); VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_RT], rt, cent->gent->client->ps.velocity ); VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_UP], up, cent->gent->client->ps.velocity ); if ( animEvent->eventData[AED_MOVE_UP] > 0 ) {//a jump cent->gent->client->ps.pm_flags |= PMF_JUMPING; G_AddEvent( cent->gent, EV_JUMP, 0 ); //FIXME: if have force jump, do this? or specify sound in the event data? //cent->gent->client->ps.forceJumpZStart = cent->gent->client->ps.origin[2];//so we don't take damage if we land at same height //G_SoundOnEnt( cent->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); } } } break; */ default: return; break; } } /* void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, const vec3_t org, int entNum ) play any keyframed sounds - only when start a new frame This func is called once for legs and once for torso */ void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, int entNum ) { int i; int firstFrame = 0, lastFrame = 0; qboolean doEvent = qfalse, inSameAnim = qfalse, loopAnim = qfalse, match = qfalse, animBackward = qfalse; animevent_t *animEvents = NULL; if ( torso ) { animEvents = bgAllEvents[eventFileIndex].torsoAnimEvents; } else { animEvents = bgAllEvents[eventFileIndex].legsAnimEvents; } if ( fabs((float)(oldFrame-frame)) > 1 ) {//given a range, see if keyFrame falls in that range int oldAnim, anim; if ( torso ) { /* if ( cg_reliableAnimSounds.integer > 1 ) {//more precise, slower oldAnim = PM_TorsoAnimForFrame( &g_entities[entNum], oldFrame ); anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame ); } else */ {//less precise, but faster oldAnim = cg_entities[entNum].currentState.torsoAnim; anim = cg_entities[entNum].nextState.torsoAnim; } } else { /* if ( cg_reliableAnimSounds.integer > 1 ) {//more precise, slower oldAnim = PM_LegsAnimForFrame( &g_entities[entNum], oldFrame ); anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame ); } else */ {//less precise, but faster oldAnim = cg_entities[entNum].currentState.legsAnim; anim = cg_entities[entNum].nextState.legsAnim; } } if ( anim != oldAnim ) {//not in same anim inSameAnim = qfalse; //FIXME: we *could* see if the oldFrame was *just about* to play the keyframed sound... } else {//still in same anim, check for looping anim animation_t *animation; inSameAnim = qtrue; animation = &bgAllAnims[animFileIndex].anims[anim]; animBackward = (animation->frameLerp<0); if ( animation->loopFrames != -1 ) {//a looping anim! loopAnim = qtrue; firstFrame = animation->firstFrame; lastFrame = animation->firstFrame+animation->numFrames; } } } // Check for anim sound for (i=0;i 1 /*&& cg_reliableAnimSounds.integer*/ ) {//given a range, see if keyFrame falls in that range if ( inSameAnim ) {//if changed anims altogether, sorry, the sound is lost if ( fabs((float)(oldFrame-animEvents[i].keyFrame)) <= 3 || fabs((float)(frame-animEvents[i].keyFrame)) <= 3 ) {//must be at least close to the keyframe if ( animBackward ) {//animation plays backwards if ( oldFrame > animEvents[i].keyFrame && frame < animEvents[i].keyFrame ) {//old to new passed through keyframe match = qtrue; } else if ( loopAnim ) {//hmm, didn't pass through it linearally, see if we looped if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame ) {//keyframe is in this anim if ( oldFrame > animEvents[i].keyFrame && frame > oldFrame ) {//old to new passed through keyframe match = qtrue; } } } } else {//anim plays forwards if ( oldFrame < animEvents[i].keyFrame && frame > animEvents[i].keyFrame ) {//old to new passed through keyframe match = qtrue; } else if ( loopAnim ) {//hmm, didn't pass through it linearally, see if we looped if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame ) {//keyframe is in this anim if ( oldFrame < animEvents[i].keyFrame && frame < oldFrame ) {//old to new passed through keyframe match = qtrue; } } } } } } } if ( match ) { switch ( animEvents[i].eventType ) { case AEV_SOUND: case AEV_SOUNDCHAN: // Determine probability of playing sound if (!animEvents[i].eventData[AED_SOUND_PROBABILITY]) // 100% { doEvent = qtrue; } else if (animEvents[i].eventData[AED_SOUND_PROBABILITY] > Q_irand(0, 99) ) { doEvent = qtrue; } break; case AEV_SABER_SWING: // Determine probability of playing sound if (!animEvents[i].eventData[AED_SABER_SWING_PROBABILITY]) // 100% { doEvent = qtrue; } else if (animEvents[i].eventData[AED_SABER_SWING_PROBABILITY] > Q_irand(0, 99) ) { doEvent = qtrue; } break; case AEV_SABER_SPIN: // Determine probability of playing sound if (!animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY]) // 100% { doEvent = qtrue; } else if (animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY] > Q_irand(0, 99) ) { doEvent = qtrue; } break; case AEV_FOOTSTEP: // Determine probability of playing sound if (!animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY]) // 100% { doEvent = qtrue; } else if (animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY] > Q_irand(0, 99) ) { doEvent = qtrue; } break; case AEV_EFFECT: // Determine probability of playing sound if (!animEvents[i].eventData[AED_EFFECT_PROBABILITY]) // 100% { doEvent = qtrue; } else if (animEvents[i].eventData[AED_EFFECT_PROBABILITY] > Q_irand(0, 99) ) { doEvent = qtrue; } break; case AEV_FIRE: // Determine probability of playing sound if (!animEvents[i].eventData[AED_FIRE_PROBABILITY]) // 100% { doEvent = qtrue; } else if (animEvents[i].eventData[AED_FIRE_PROBABILITY] > Q_irand(0, 99) ) { doEvent = qtrue; } break; case AEV_MOVE: doEvent = qtrue; break; default: //doEvent = qfalse;//implicit break; } // do event if ( doEvent ) { CG_PlayerAnimEventDo( &cg_entities[entNum], &animEvents[i] ); } } } } void CG_TriggerAnimSounds( centity_t *cent ) { //this also sets the lerp frames, so I suggest you keep calling it regardless of if you want anim sounds. int curFrame = 0; float currentFrame = 0; int sFileIndex; assert(cent->localAnimIndex >= 0); sFileIndex = cent->eventAnimIndex; if (trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, ¤tFrame, cgs.gameModels, 0)) { // the above may have failed, not sure what to do about it, current frame will be zero in that case curFrame = floor( currentFrame ); } if ( curFrame != cent->pe.legs.frame ) { CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qfalse, cent->pe.legs.frame, curFrame, cent->currentState.number ); } cent->pe.legs.oldFrame = cent->pe.torso.frame; cent->pe.legs.frame = curFrame; if (cent->noLumbar) { //probably a droid or something. cent->pe.torso.oldFrame = cent->pe.legs.oldFrame; cent->pe.torso.frame = cent->pe.legs.frame; return; } if (trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, ¤tFrame, cgs.gameModels, 0)) { curFrame = floor( currentFrame ); } if ( curFrame != cent->pe.torso.frame ) { CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qtrue, cent->pe.torso.frame, curFrame, cent->currentState.number ); } cent->pe.torso.oldFrame = cent->pe.torso.frame; cent->pe.torso.frame = curFrame; cent->pe.torso.backlerp = 1.0f - (currentFrame - (float)curFrame); } static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale); qboolean CG_InRoll( centity_t *cent ) { switch ( (cent->currentState.legsAnim) ) { case BOTH_GETUP_BROLL_B: case BOTH_GETUP_BROLL_F: case BOTH_GETUP_BROLL_L: case BOTH_GETUP_BROLL_R: case BOTH_GETUP_FROLL_B: case BOTH_GETUP_FROLL_F: case BOTH_GETUP_FROLL_L: case BOTH_GETUP_FROLL_R: case BOTH_ROLL_F: case BOTH_ROLL_B: case BOTH_ROLL_R: case BOTH_ROLL_L: if ( cent->pe.legs.animationTime > cg.time ) { return qtrue; } break; } return qfalse; } qboolean CG_InRollAnim( centity_t *cent ) { switch ( (cent->currentState.legsAnim) ) { case BOTH_ROLL_F: case BOTH_ROLL_B: case BOTH_ROLL_R: case BOTH_ROLL_L: return qtrue; } return qfalse; } /* =============== CG_SetLerpFrameAnimation =============== */ #include "../namespace_begin.h" qboolean BG_SaberStanceAnim( int anim ); qboolean PM_RunningAnim( int anim ); #include "../namespace_end.h" static void CG_SetLerpFrameAnimation( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float animSpeedMult, qboolean torsoOnly, qboolean flipState) { animation_t *anim; float animSpeed; int flags=BONE_ANIM_OVERRIDE_FREEZE; int oldAnim = -1; int blendTime = 100; float oldSpeed = lf->animationSpeed; if (cent->localAnimIndex > 0) { //rockettroopers can't have broken arms, nor can anything else but humanoids ci->brokenLimbs = cent->currentState.brokenLimbs; } oldAnim = lf->animationNumber; lf->animationNumber = newAnimation; if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { CG_Error( "Bad animation number: %i", newAnimation ); } anim = &bgAllAnims[cent->localAnimIndex].anims[ newAnimation ]; lf->animation = anim; lf->animationTime = lf->frameTime + abs(anim->frameLerp); if (cent->localAnimIndex > 1 && anim->firstFrame == 0 && anim->numFrames == 0) { //We'll allow this for non-humanoids. return; } if ( cg_debugAnim.integer && (cg_debugAnim.integer < 0 || cg_debugAnim.integer == cent->currentState.clientNum) ) { if (lf == ¢->pe.legs) { CG_Printf( "%d: %d TORSO Anim: %i, '%s'\n", cg.time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation)); } else { CG_Printf( "%d: %d LEGS Anim: %i, '%s'\n", cg.time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation)); } } if (cent->ghoul2) { qboolean resumeFrame = qfalse; int beginFrame = -1; int firstFrame; int lastFrame; #if 0 //disabled for now float unused; #endif animSpeed = 50.0f / anim->frameLerp; if (lf->animation->loopFrames != -1) { flags = BONE_ANIM_OVERRIDE_LOOP; } if (animSpeed < 0) { lastFrame = anim->firstFrame; firstFrame = anim->firstFrame + anim->numFrames; } else { firstFrame = anim->firstFrame; lastFrame = anim->firstFrame + anim->numFrames; } if (cg_animBlend.integer) { flags |= BONE_ANIM_BLEND; } if (BG_InDeathAnim(newAnimation)) { flags &= ~BONE_ANIM_BLEND; } else if ( oldAnim != -1 && BG_InDeathAnim(oldAnim)) { flags &= ~BONE_ANIM_BLEND; } if (flags & BONE_ANIM_BLEND) { if (BG_FlippingAnim(newAnimation)) { blendTime = 200; } else if ( oldAnim != -1 && (BG_FlippingAnim(oldAnim)) ) { blendTime = 200; } } animSpeed *= animSpeedMult; BG_SaberStartTransAnim(cent->currentState.number, cent->currentState.fireflag, cent->currentState.weapon, newAnimation, &animSpeed, cent->currentState.brokenLimbs); if (torsoOnly) { if (lf->animationTorsoSpeed != animSpeedMult && newAnimation == oldAnim && flipState == lf->lastFlip) { //same animation, but changing speed, so we will want to resume off the frame we're on. resumeFrame = qtrue; } lf->animationTorsoSpeed = animSpeedMult; } else { if (lf->animationSpeed != animSpeedMult && newAnimation == oldAnim && flipState == lf->lastFlip) { //same animation, but changing speed, so we will want to resume off the frame we're on. resumeFrame = qtrue; } lf->animationSpeed = animSpeedMult; } //vehicles may have torso etc but we only want to animate the root bone if ( cent->currentState.NPC_class == CLASS_VEHICLE ) { trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed,cg.time, beginFrame, blendTime); return; } if (torsoOnly && !cent->noLumbar) { //rww - The guesswork based on the lerp frame figures is usually BS, so I've resorted to a call to get the frame of the bone directly. float GBAcFrame = 0; if (resumeFrame) { //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame. trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, &GBAcFrame, NULL, 0); beginFrame = GBAcFrame; } //even if resuming, also be sure to check if we are running the same frame on the legs. If so, we want to use their frame no matter what. trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, &GBAcFrame, NULL, 0); if ((cent->currentState.torsoAnim) == (cent->currentState.legsAnim) && GBAcFrame >= anim->firstFrame && GBAcFrame <= (anim->firstFrame + anim->numFrames)) { //if the legs are already running this anim, pick up on the exact same frame to avoid the "wobbly spine" problem. beginFrame = GBAcFrame; } if (firstFrame > lastFrame || ci->torsoAnim == newAnimation) { //don't resume on backwards playing animations.. I guess. beginFrame = -1; } trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, flags, animSpeed,cg.time, beginFrame, blendTime); // Update the torso frame with the new animation cent->pe.torso.frame = firstFrame; if (ci) { ci->torsoAnim = newAnimation; } } else { if (resumeFrame) { //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame. float GBAcFrame = 0; trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, &GBAcFrame, NULL, 0); beginFrame = GBAcFrame; } if ((beginFrame < firstFrame) || (beginFrame > lastFrame)) { //out of range, don't use it then. beginFrame = -1; } if (cent->currentState.torsoAnim == cent->currentState.legsAnim && (ci->legsAnim != newAnimation || oldSpeed != animSpeed)) { //alright, we are starting an anim on the legs, and that same anim is already playing on the toro, so pick up the frame. float GBAcFrame = 0; int oldBeginFrame = beginFrame; trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, &GBAcFrame, NULL, 0); beginFrame = GBAcFrame; if ((beginFrame < firstFrame) || (beginFrame > lastFrame)) { //out of range, don't use it then. beginFrame = oldBeginFrame; } } trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); if (ci) { ci->legsAnim = newAnimation; } } if (cent->localAnimIndex <= 1 && (cent->currentState.torsoAnim) == newAnimation && !cent->noLumbar) { //make sure we're humanoid before we access the motion bone trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); } #if 0 //disabled for now if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs && (cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM))) { //broken left arm char *brokenBone = "lhumerus"; animation_t *armAnim; int armFirstFrame; int armLastFrame; int armFlags = 0; float armAnimSpeed; armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_DEAD21 ]; ci->brokenLimbs = cent->currentState.brokenLimbs; armFirstFrame = armAnim->firstFrame; armLastFrame = armAnim->firstFrame+armAnim->numFrames; armAnimSpeed = 50.0f / armAnim->frameLerp; armFlags = BONE_ANIM_OVERRIDE_LOOP; if (cg_animBlend.integer) { armFlags |= BONE_ANIM_BLEND; } trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, blendTime); } else if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs && (cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM))) { //broken right arm char *brokenBone = "rhumerus"; char *supportBone = "lhumerus"; ci->brokenLimbs = cent->currentState.brokenLimbs; //Only put the arm in a broken pose if the anim is such that we //want to allow it. if ((//cent->currentState.weapon == WP_MELEE || cent->currentState.weapon != WP_SABER || BG_SaberStanceAnim(newAnimation) || PM_RunningAnim(newAnimation)) && cent->currentState.torsoAnim == newAnimation && (!ci->saber[1].model[0] || cent->currentState.weapon != WP_SABER)) { int armFirstFrame; int armLastFrame; int armFlags = 0; float armAnimSpeed; animation_t *armAnim; if (cent->currentState.weapon == WP_MELEE || cent->currentState.weapon == WP_SABER || cent->currentState.weapon == WP_BRYAR_PISTOL) { //don't affect this arm if holding a gun, just make the other arm support it armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_ATTACK2 ]; //armFirstFrame = armAnim->firstFrame; armFirstFrame = armAnim->firstFrame+armAnim->numFrames; armLastFrame = armAnim->firstFrame+armAnim->numFrames; armAnimSpeed = 50.0f / armAnim->frameLerp; armFlags = BONE_ANIM_OVERRIDE_LOOP; /* if (cg_animBlend.integer) { armFlags |= BONE_ANIM_BLEND; } */ //No blend on the broken arm trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, 0); } else { //we want to keep the broken bone updated for some cases trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); } if (newAnimation != BOTH_MELEE1 && newAnimation != BOTH_MELEE2 && (newAnimation == TORSO_WEAPONREADY2 || newAnimation == BOTH_ATTACK2 || cent->currentState.weapon < WP_BRYAR_PISTOL)) { //Now set the left arm to "support" the right one armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_STAND2 ]; armFirstFrame = armAnim->firstFrame; armLastFrame = armAnim->firstFrame+armAnim->numFrames; armAnimSpeed = 50.0f / armAnim->frameLerp; armFlags = BONE_ANIM_OVERRIDE_LOOP; if (cg_animBlend.integer) { armFlags |= BONE_ANIM_BLEND; } trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, 150); } else { //we want to keep the support bone updated for some cases trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); } } else if (cent->currentState.torsoAnim == newAnimation) { //otherwise, keep it set to the same as the torso trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); } } else if (ci && (ci->brokenLimbs || trap_G2API_GetBoneFrame(cent->ghoul2, "lhumerus", cg.time, &unused, cgs.gameModels, 0) || trap_G2API_GetBoneFrame(cent->ghoul2, "rhumerus", cg.time, &unused, cgs.gameModels, 0))) //rwwFIXMEFIXME: brokenLimbs gets stomped sometimes, but it shouldn't. { //remove the bone now so it can be set again char *brokenBone = NULL; int broken = 0; //Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index //(well, in theory, I haven't actually run into the problem) if (ci->brokenLimbs & (1<brokenLimbs & (1<ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg.time, -1, 0); if (!trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0)) { assert(0); Com_Printf("WARNING: Failed to remove lhumerus\n"); } } if (!brokenBone) { trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg.time, -1, 0); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "rhumerus", 0, 1, 0, 0, cg.time, -1, 0); trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0); trap_G2API_RemoveBone(cent->ghoul2, "rhumerus", 0); ci->brokenLimbs = 0; } else { //Set the flags and stuff to 0, so that the remove will succeed trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, 0, 1, 0, 0, cg.time, -1, 0); //Now remove it if (!trap_G2API_RemoveBone(cent->ghoul2, brokenBone, 0)) { assert(0); Com_Printf("WARNING: Failed to remove %s\n", brokenBone); } ci->brokenLimbs &= ~broken; } } #endif } } /* =============== CG_FirstAnimFrame Returns true if the lerpframe is on its first frame of animation. Otherwise false. This is used to scale an animation into higher-speed without restarting the animation before it completes at normal speed, in the case of a looping animation (such as the leg running anim). =============== */ static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale) { if (torsoOnly) { if (lf->animationTorsoSpeed == speedScale) { return qfalse; } } else { if (lf->animationSpeed == speedScale) { return qfalse; } } //I don't care where it is in the anim now, I am going to pick up from the same bone frame. /* if (lf->animation->numFrames < 2) { return qtrue; } if (lf->animation->firstFrame == lf->frame) { return qtrue; } */ return qtrue; } /* =============== CG_RunLerpFrame Sets cg.snap, cg.oldFrame, and cg.backlerp cg.time should be between oldFrameTime and frameTime after exit =============== */ static void CG_RunLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, qboolean flipState, int newAnimation, float speedScale, qboolean torsoOnly) { // debugging tool to get no animations if ( cg_animSpeed.integer == 0 ) { lf->oldFrame = lf->frame = lf->backlerp = 0; return; } // see if the animation sequence is switching if (cent->currentState.forceFrame) { if (lf->lastForcedFrame != cent->currentState.forceFrame) { int flags = BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND; float animSpeed = 1.0f; trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150); } lf->lastForcedFrame = cent->currentState.forceFrame; lf->animationNumber = 0; } else { lf->lastForcedFrame = -1; if ( (newAnimation != lf->animationNumber || cent->currentState.brokenLimbs != ci->brokenLimbs || lf->lastFlip != flipState || !lf->animation) || (CG_FirstAnimFrame(lf, torsoOnly, speedScale)) ) { CG_SetLerpFrameAnimation( cent, ci, lf, newAnimation, speedScale, torsoOnly, flipState); } } lf->lastFlip = flipState; if ( lf->frameTime > cg.time + 200 ) { lf->frameTime = cg.time; } if ( lf->oldFrameTime > cg.time ) { lf->oldFrameTime = cg.time; } // calculate current lerp value if ( lf->frameTime == lf->oldFrameTime ) { lf->backlerp = 0; } else { lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); } } /* =============== CG_ClearLerpFrame =============== */ static void CG_ClearLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int animationNumber, qboolean torsoOnly) { lf->frameTime = lf->oldFrameTime = cg.time; CG_SetLerpFrameAnimation( cent, ci, lf, animationNumber, 1, torsoOnly, qfalse ); if ( lf->animation->frameLerp < 0 ) {//Plays backwards lf->oldFrame = lf->frame = (lf->animation->firstFrame + lf->animation->numFrames); } else { lf->oldFrame = lf->frame = lf->animation->firstFrame; } } /* =============== CG_PlayerAnimation =============== */ #include "../namespace_begin.h" qboolean PM_WalkingAnim( int anim ); #include "../namespace_end.h" static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, int *torsoOld, int *torso, float *torsoBackLerp ) { clientInfo_t *ci; int clientNum; float speedScale; clientNum = cent->currentState.clientNum; if ( cg_noPlayerAnims.integer ) { *legsOld = *legs = *torsoOld = *torso = 0; return; } if (!PM_RunningAnim(cent->currentState.legsAnim) && !PM_WalkingAnim(cent->currentState.legsAnim)) { //if legs are not in a walking/running anim then just animate at standard speed speedScale = 1.0f; } else if (cent->currentState.forcePowersActive & (1 << FP_RAGE)) { speedScale = 1.3f; } else if (cent->currentState.forcePowersActive & (1 << FP_SPEED)) { speedScale = 1.7f; } else { speedScale = 1.0f; } if (cent->currentState.eType == ET_NPC) { ci = cent->npcClient; assert(ci); } else { ci = &cgs.clientinfo[ clientNum ]; } CG_RunLerpFrame( cent, ci, ¢->pe.legs, cent->currentState.legsFlip, cent->currentState.legsAnim, speedScale, qfalse); if (!(cent->currentState.forcePowersActive & (1 << FP_RAGE))) { //don't affect torso anim speed unless raged speedScale = 1.0f; } else { speedScale = 1.7f; } *legsOld = cent->pe.legs.oldFrame; *legs = cent->pe.legs.frame; *legsBackLerp = cent->pe.legs.backlerp; // If this is not a vehicle, you may lerm the frame (since vehicles never have a torso anim). -AReis if ( cent->currentState.NPC_class != CLASS_VEHICLE ) { CG_RunLerpFrame( cent, ci, ¢->pe.torso, cent->currentState.torsoFlip, cent->currentState.torsoAnim, speedScale, qtrue ); *torsoOld = cent->pe.torso.oldFrame; *torso = cent->pe.torso.frame; *torsoBackLerp = cent->pe.torso.backlerp; } } /* ============================================================================= PLAYER ANGLES ============================================================================= */ #if 0 typedef struct boneAngleParms_s { void *ghoul2; int modelIndex; char *boneName; vec3_t angles; int flags; int up; int right; int forward; qhandle_t *modelList; int blendTime; int currentTime; qboolean refreshSet; } boneAngleParms_t; boneAngleParms_t cgBoneAnglePostSet; #endif void CG_G2SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, const int up, const int right, const int forward, qhandle_t *modelList, int blendTime , int currentTime ) { //we want to hold off on setting the bone angles until the end of the frame, because every time we set //them the entire skeleton has to be reconstructed. #if 0 //This function should ONLY be called from CG_Player() or a function that is called only within CG_Player(). //At the end of the frame we will check to use this information to call SetBoneAngles memset(&cgBoneAnglePostSet, 0, sizeof(cgBoneAnglePostSet)); cgBoneAnglePostSet.ghoul2 = ghoul2; cgBoneAnglePostSet.modelIndex = modelIndex; cgBoneAnglePostSet.boneName = (char *)boneName; cgBoneAnglePostSet.angles[0] = angles[0]; cgBoneAnglePostSet.angles[1] = angles[1]; cgBoneAnglePostSet.angles[2] = angles[2]; cgBoneAnglePostSet.flags = flags; cgBoneAnglePostSet.up = up; cgBoneAnglePostSet.right = right; cgBoneAnglePostSet.forward = forward; cgBoneAnglePostSet.modelList = modelList; cgBoneAnglePostSet.blendTime = blendTime; cgBoneAnglePostSet.currentTime = currentTime; cgBoneAnglePostSet.refreshSet = qtrue; #endif //We don't want to go with the delayed approach, we want out bolt points and everything to be updated in realtime. //We'll just take the reconstructs and live with them. trap_G2API_SetBoneAngles(ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, blendTime, currentTime); } /* ================ CG_Rag_Trace Variant on CG_Trace. Doesn't trace for ents because ragdoll engine trace code has no entity trace access. Maybe correct this sometime, so bmodel col. at least works with ragdoll. But I don't want to slow it down.. ================ */ void CG_Rag_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int skipNumber, int mask ) { trap_CM_BoxTrace ( result, start, end, mins, maxs, 0, mask); result->entityNum = result->fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; } //#define _RAG_BOLT_TESTING #ifdef _RAG_BOLT_TESTING void CG_TempTestFunction(centity_t *cent, vec3_t forcedAngles) { mdxaBone_t boltMatrix; vec3_t tAngles; vec3_t bOrg; vec3_t bDir; vec3_t uOrg; VectorSet(tAngles, 0, cent->lerpAngles[YAW], 0); trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, tAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, bOrg); BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, bDir); VectorMA(bOrg, 40, bDir, uOrg); CG_TestLine(bOrg, uOrg, 50, 0x0000ff, 1); cent->turAngles[YAW] = forcedAngles[YAW]; } #endif //list of valid ragdoll effectors static const char *cg_effectorStringTable[] = { //commented out the ones I don't want dragging to affect // "thoracic", // "rhand", "lhand", "rtibia", "ltibia", "rtalus", "ltalus", // "rradiusX", "lradiusX", "rfemurX", "lfemurX", // "ceyebrow", NULL //always terminate }; //we want to see which way the pelvis is facing to get a relatively oriented base settling frame //this is to avoid the arms stretching in opposite directions on the body trying to reach the base //pose if the pelvis is flipped opposite of the base pose or something -rww static int CG_RagAnimForPositioning(centity_t *cent) { int bolt; vec3_t dir; mdxaBone_t matrix; assert(cent->ghoul2); bolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); assert(bolt > -1); trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Z, dir); if (dir[2] > 0.0f) { //facing up return BOTH_DEADFLOP2; } else { //facing down return BOTH_DEADFLOP1; } } //rww - cgame interface for the ragdoll stuff. //Returns qtrue if the entity is now in a ragdoll state, otherwise qfalse. qboolean CG_RagDoll(centity_t *cent, vec3_t forcedAngles) { vec3_t usedOrg; qboolean inSomething = qfalse; int ragAnim;//BOTH_DEAD1; //BOTH_DEATH1; if (!cg_ragDoll.integer) { return qfalse; } if (cent->localAnimIndex) { //don't rag non-humanoids return qfalse; } VectorCopy(cent->lerpOrigin, usedOrg); if (!cent->isRagging) { //If we're not in a ragdoll state, perform the checks. if (cent->currentState.eFlags & EF_RAG) { //want to go into it no matter what then inSomething = qtrue; } else if (cent->currentState.groundEntityNum == ENTITYNUM_NONE) { vec3_t cVel; VectorCopy(cent->currentState.pos.trDelta, cVel); if (VectorNormalize(cVel) > 400) { //if he's flying through the air at a good enough speed, switch into ragdoll inSomething = qtrue; } } if (cent->currentState.eType == ET_BODY) { //just rag bodies immediately if their own was ragging on respawn if (cent->ownerRagging) { cent->isRagging = qtrue; return qfalse; } } if (cg_ragDoll.integer > 1) { inSomething = qtrue; } if (!inSomething) { int anim = (cent->currentState.legsAnim); int dur = (bgAllAnims[cent->localAnimIndex].anims[anim].numFrames-1) * fabs((float)(bgAllAnims[cent->localAnimIndex].anims[anim].frameLerp)); int i = 0; int boltChecks[5]; vec3_t boltPoints[5]; vec3_t trStart, trEnd; vec3_t tAng; qboolean deathDone = qfalse; trace_t tr; mdxaBone_t boltMatrix; VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); if (cent->pe.legs.animationTime > 50 && (cg.time - cent->pe.legs.animationTime) > dur) { //Looks like the death anim is done playing deathDone = qtrue; } if (deathDone) { //only trace from the hands if the death anim is already done. boltChecks[0] = trap_G2API_AddBolt(cent->ghoul2, 0, "rhand"); boltChecks[1] = trap_G2API_AddBolt(cent->ghoul2, 0, "lhand"); } else { //otherwise start the trace loop at the cranium. i = 2; } boltChecks[2] = trap_G2API_AddBolt(cent->ghoul2, 0, "cranium"); //boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtarsal"); //boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltarsal"); boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtalus"); boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltalus"); //This may seem bad, but since we have a bone cache now it should manage to not be too disgustingly slow. //Do the head first, because the hands reference it anyway. trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[2], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[2]); while (i < 5) { if (i < 2) { //when doing hands, trace to the head instead of origin trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]); VectorCopy(boltPoints[i], trStart); VectorCopy(boltPoints[2], trEnd); } else { if (i > 2) { //2 is the head, which already has the bolt point. trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]); } VectorCopy(boltPoints[i], trStart); VectorCopy(cent->lerpOrigin, trEnd); } //Now that we have all that sorted out, trace between the two points we desire. CG_Rag_Trace(&tr, trStart, NULL, NULL, trEnd, cent->currentState.number, MASK_SOLID); if (tr.fraction != 1.0 || tr.startsolid || tr.allsolid) { //Hit something or start in solid, so flag it and break. //This is a slight hack, but if we aren't done with the death anim, we don't really want to //go into ragdoll unless our body has a relatively "flat" pitch. #if 0 vec3_t vSub; //Check the pitch from the head to the right foot (should be reasonable) VectorSubtract(boltPoints[2], boltPoints[3], vSub); VectorNormalize(vSub); vectoangles(vSub, vSub); if (deathDone || (vSub[PITCH] < 50 && vSub[PITCH] > -50)) { inSomething = qtrue; } #else inSomething = qtrue; #endif break; } i++; } } if (inSomething) { cent->isRagging = qtrue; #if 0 VectorClear(cent->lerpOriginOffset); #endif } } if (cent->isRagging) { //We're in a ragdoll state, so make the call to keep our positions updated and whatnot. sharedRagDollParams_t tParms; sharedRagDollUpdateParams_t tuParms; ragAnim = CG_RagAnimForPositioning(cent); if (cent->ikStatus) { //ik must be reset before ragdoll is started, or you'll get some interesting results. trap_G2API_SetBoneIKState(cent->ghoul2, cg.time, NULL, IKS_NONE, NULL); cent->ikStatus = qfalse; } //these will be used as "base" frames for the ragoll settling. tParms.startFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame;// + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames; tParms.endFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames; #if 0 { float animSpeed = 0; int blendTime = 600; int flags = 0;//BONE_ANIM_OVERRIDE_FREEZE; if (bgAllAnims[cent->localAnimIndex].anims[ragAnim].loopFrames != -1) { flags = BONE_ANIM_OVERRIDE_LOOP; } /* if (cg_animBlend.integer) { flags |= BONE_ANIM_BLEND; } */ animSpeed = 50.0f / bgAllAnims[cent->localAnimIndex].anims[ragAnim].frameLerp; trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", tParms.startFrame, tParms.endFrame, flags, animSpeed,cg.time, -1, blendTime); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg.time, -1, blendTime); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg.time, -1, blendTime); } #elif 1 //with my new method of doing things I want it to continue the anim { float currentFrame; int startFrame, endFrame; int flags; float animSpeed; if (trap_G2API_GetBoneAnim(cent->ghoul2, "model_root", cg.time, ¤tFrame, &startFrame, &endFrame, &flags, &animSpeed, cgs.gameModels, 0)) { //lock the anim on the current frame. int blendTime = 500; animation_t *curAnim = &bgAllAnims[cent->localAnimIndex].anims[cent->currentState.legsAnim]; if (currentFrame >= (curAnim->firstFrame + curAnim->numFrames-1)) { //this is sort of silly but it works for now. currentFrame = (curAnim->firstFrame + curAnim->numFrames-2); } trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", currentFrame, currentFrame+1, flags, animSpeed,cg.time, currentFrame, blendTime); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", currentFrame, currentFrame+1, flags, animSpeed, cg.time, currentFrame, blendTime); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", currentFrame, currentFrame+1, flags, animSpeed, cg.time, currentFrame, blendTime); } } #endif CG_G2SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); CG_G2SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); CG_G2SetBoneAngles(cent->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); CG_G2SetBoneAngles(cent->ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); VectorCopy(forcedAngles, tParms.angles); VectorCopy(usedOrg, tParms.position); VectorCopy(cent->modelScale, tParms.scale); tParms.me = cent->currentState.number; tParms.collisionType = 1; tParms.RagPhase = RP_DEATH_COLLISION; tParms.fShotStrength = 4; trap_G2API_SetRagDoll(cent->ghoul2, &tParms); VectorCopy(forcedAngles, tuParms.angles); VectorCopy(usedOrg, tuParms.position); VectorCopy(cent->modelScale, tuParms.scale); tuParms.me = cent->currentState.number; tuParms.settleFrame = tParms.endFrame-1; if (cent->currentState.groundEntityNum != ENTITYNUM_NONE) { VectorClear(tuParms.velocity); } else { VectorScale(cent->currentState.pos.trDelta, 2.0f, tuParms.velocity); } trap_G2API_AnimateG2Models(cent->ghoul2, cg.time, &tuParms); //So if we try to get a bolt point it's still correct cent->turAngles[YAW] = cent->lerpAngles[YAW] = cent->pe.torso.yawAngle = cent->pe.legs.yawAngle = forcedAngles[YAW]; if (cent->currentState.ragAttach && (cent->currentState.eType != ET_NPC || cent->currentState.NPC_class != CLASS_VEHICLE)) { centity_t *grabEnt; if (cent->currentState.ragAttach == ENTITYNUM_NONE) { //switch cl 0 and entitynum_none, so we can operate on the "if non-0" concept grabEnt = &cg_entities[0]; } else { grabEnt = &cg_entities[cent->currentState.ragAttach]; } if (grabEnt->ghoul2) { mdxaBone_t matrix; vec3_t bOrg; vec3_t thisHand; vec3_t hands; vec3_t pcjMin, pcjMax; vec3_t pDif; vec3_t thorPoint; float difLen; int thorBolt; //Get the person who is holding our hand's hand location trap_G2API_GetBoltMatrix(grabEnt->ghoul2, 0, 0, &matrix, grabEnt->turAngles, grabEnt->lerpOrigin, cg.time, cgs.gameModels, grabEnt->modelScale); BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bOrg); //Get our hand's location trap_G2API_GetBoltMatrix(cent->ghoul2, 0, 0, &matrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thisHand); //Get the position of the thoracic bone for hinting its velocity later on thorBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "thoracic"); trap_G2API_GetBoltMatrix(cent->ghoul2, 0, thorBolt, &matrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thorPoint); VectorSubtract(bOrg, thisHand, hands); if (VectorLength(hands) < 3.0f) { trap_G2API_RagForceSolve(cent->ghoul2, qfalse); } else { trap_G2API_RagForceSolve(cent->ghoul2, qtrue); } //got the hand pos of him, now we want to make our hand go to it trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", bOrg); trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", bOrg); trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", bOrg); trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", bOrg); trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", bOrg); //Make these two solve quickly so we can update decently trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 1.5f); trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 1.5f); //Break the constraints on them I suppose VectorSet(pcjMin, -999, -999, -999); VectorSet(pcjMax, 999, 999, 999); trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax); trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax); cent->overridingBones = cg.time + 2000; //hit the thoracic velocity to the hand point VectorSubtract(bOrg, thorPoint, hands); VectorNormalize(hands); VectorScale(hands, 2048.0f, hands); trap_G2API_RagEffectorKick(cent->ghoul2, "thoracic", hands); trap_G2API_RagEffectorKick(cent->ghoul2, "ceyebrow", hands); VectorSubtract(cent->ragLastOrigin, cent->lerpOrigin, pDif); VectorCopy(cent->lerpOrigin, cent->ragLastOrigin); if (cent->ragLastOriginTime >= cg.time && cent->currentState.groundEntityNum != ENTITYNUM_NONE) { //make sure it's reasonably updated difLen = VectorLength(pDif); if (difLen > 0.0f) { //if we're being dragged, then kick all the bones around a bit vec3_t dVel; vec3_t rVel; int i = 0; if (difLen < 12.0f) { VectorScale(pDif, 12.0f/difLen, pDif); difLen = 12.0f; } while (cg_effectorStringTable[i]) { VectorCopy(pDif, dVel); dVel[2] = 0; //Factor in a random velocity VectorSet(rVel, flrand(-0.1f, 0.1f), flrand(-0.1f, 0.1f), flrand(0.1f, 0.5)); VectorScale(rVel, 8.0f, rVel); VectorAdd(dVel, rVel, dVel); VectorScale(dVel, 10.0f, dVel); trap_G2API_RagEffectorKick(cent->ghoul2, cg_effectorStringTable[i], dVel); #if 0 { mdxaBone_t bm; vec3_t borg; vec3_t vorg; int b = trap_G2API_AddBolt(cent->ghoul2, 0, cg_effectorStringTable[i]); trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &bm, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&bm, ORIGIN, borg); VectorMA(borg, 1.0f, dVel, vorg); CG_TestLine(borg, vorg, 50, 0x0000ff, 1); } #endif i++; } } } cent->ragLastOriginTime = cg.time + 1000; } } else if (cent->overridingBones) { //reset things to their normal rag state vec3_t pcjMin, pcjMax; vec3_t dVel; //got the hand pos of him, now we want to make our hand go to it trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", NULL); trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", NULL); trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", NULL); trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", NULL); trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", NULL); VectorSet(dVel, 0.0f, 0.0f, -64.0f); trap_G2API_RagEffectorKick(cent->ghoul2, "rhand", dVel); trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 0.0f); trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 0.0f); VectorSet(pcjMin,-100.0f,-40.0f,-15.0f); VectorSet(pcjMax,-15.0f,80.0f,15.0f); trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax); VectorSet(pcjMin,-25.0f,-20.0f,-20.0f); VectorSet(pcjMax,90.0f,20.0f,-20.0f); trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax); if (cent->overridingBones < cg.time) { trap_G2API_RagForceSolve(cent->ghoul2, qfalse); cent->overridingBones = 0; } else { trap_G2API_RagForceSolve(cent->ghoul2, qtrue); } } return qtrue; } return qfalse; } //set the bone angles of this client entity based on data from the server -rww void CG_G2ServerBoneAngles(centity_t *cent) { int i = 0; int bone = cent->currentState.boneIndex1; int flags, up, right, forward; vec3_t boneAngles; VectorCopy(cent->currentState.boneAngles1, boneAngles); while (i < 4) { //cycle through the 4 bone index values on the entstate if (bone) { //if it's non-0 then it could have something in it. const char *boneName = CG_ConfigString(CS_G2BONES+bone); if (boneName && boneName[0]) { //got the bone, now set the angles from the corresponding entitystate boneangles value. flags = BONE_ANGLES_POSTMULT; //get the orientation out of our bit field forward = (cent->currentState.boneOrient)&7; //3 bits from bit 0 right = (cent->currentState.boneOrient>>3)&7; //3 bits from bit 3 up = (cent->currentState.boneOrient>>6)&7; //3 bits from bit 6 trap_G2API_SetBoneAngles(cent->ghoul2, 0, boneName, boneAngles, flags, up, right, forward, cgs.gameModels, 100, cg.time); } } switch (i) { case 0: bone = cent->currentState.boneIndex2; VectorCopy(cent->currentState.boneAngles2, boneAngles); break; case 1: bone = cent->currentState.boneIndex3; VectorCopy(cent->currentState.boneAngles3, boneAngles); break; case 2: bone = cent->currentState.boneIndex4; VectorCopy(cent->currentState.boneAngles4, boneAngles); break; default: break; } i++; } } /* ------------------------- CG_G2SetHeadBlink ------------------------- */ static void CG_G2SetHeadBlink( centity_t *cent, qboolean bStart ) { vec3_t desiredAngles; int blendTime = 80; qboolean bWink = qfalse; const int hReye = trap_G2API_AddBolt( cent->ghoul2, 0, "reye" ); const int hLeye = trap_G2API_AddBolt( cent->ghoul2, 0, "leye" ); if (hLeye == -1) { return; } VectorClear(desiredAngles); if (bStart) { desiredAngles[YAW] = -50; if ( random() > 0.95f ) { bWink = qtrue; blendTime /=3; } } trap_G2API_SetBoneAngles( cent->ghoul2, 0, "leye", desiredAngles, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time ); if (hReye == -1) { return; } if (!bWink) { trap_G2API_SetBoneAngles( cent->ghoul2, 0, "reye", desiredAngles, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time ); } } /* ------------------------- CG_G2SetHeadAnims ------------------------- */ static void CG_G2SetHeadAnim( centity_t *cent, int anim ) { const int blendTime = 50; const animation_t *animations = bgAllAnims[cent->localAnimIndex].anims; int animFlags = BONE_ANIM_OVERRIDE ;//| BONE_ANIM_BLEND; // animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps). // float timeScaleMod = (cg_timescale.value&&gent&&gent->s.clientNum==0&&!player_locked&&!MatrixMode&&gent->client->ps.forcePowersActive&(1<ghoul2[gent->playerModel], cent->gent->faceBone, &startFrame, &endFrame); // if (!animatingHead || ( animations[anim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again { // gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone, // firstFrame, lastFrame, animFlags, animSpeed, cg.time, -1, blendTime); trap_G2API_SetBoneAnim(cent->ghoul2, 0, "face", firstFrame, lastFrame, animFlags, animSpeed, cg.time, -1, blendTime); } } qboolean CG_G2PlayerHeadAnims( centity_t *cent ) { clientInfo_t *ci = NULL; int anim = -1; int voiceVolume = 0; if(cent->localAnimIndex > 1) { //only do this for humanoids return qfalse; } if (cent->noFace) { // i don't have a face return qfalse; } if (cent->currentState.number < MAX_CLIENTS) { ci = &cgs.clientinfo[cent->currentState.number]; } else { ci = cent->npcClient; } if (!ci) { return qfalse; } if ( cent->currentState.eFlags & EF_DEAD ) {//Dead people close their eyes and don't make faces! anim = FACE_DEAD; ci->facial_blink = -1; } else { if (!ci->facial_blink) { // set the timers ci->facial_blink = cg.time + flrand(4000.0, 8000.0); ci->facial_frown = cg.time + flrand(6000.0, 10000.0); ci->facial_aux = cg.time + flrand(6000.0, 10000.0); } //are we blinking? if (ci->facial_blink < 0) { // yes, check if we are we done blinking ? if (-(ci->facial_blink) < cg.time) { // yes, so reset blink timer ci->facial_blink = cg.time + flrand(4000.0, 8000.0); CG_G2SetHeadBlink( cent, qfalse ); //stop the blink } } else // no we aren't blinking { if (ci->facial_blink < cg.time)// but should we start ? { CG_G2SetHeadBlink( cent, qtrue ); if (ci->facial_blink == 1) {//requested to stay shut by SET_FACEEYESCLOSED ci->facial_blink = -(cg.time + 99999999.0f);// set blink timer } else { ci->facial_blink = -(cg.time + 300.0f);// set blink timer } } } voiceVolume = trap_S_GetVoiceVolume(cent->currentState.number); if (voiceVolume > 0) // if we aren't talking, then it will be 0, -1 for talking but paused { anim = FACE_TALK1 + voiceVolume -1; } else if (voiceVolume == 0) //don't do aux if in a slient part of speech {//not talking if (ci->facial_aux < 0) // are we auxing ? { //yes if (-(ci->facial_aux) < cg.time)// are we done auxing ? { // yes, reset aux timer ci->facial_aux = cg.time + flrand(7000.0, 10000.0); } else { // not yet, so choose aux anim = FACE_ALERT; } } else // no we aren't auxing { // but should we start ? if (ci->facial_aux < cg.time) {//yes anim = FACE_ALERT; // set aux timer ci->facial_aux = -(cg.time + 2000.0); } } if (anim != -1) //we we are auxing, see if we should override with a frown { if (ci->facial_frown < 0)// are we frowning ? { // yes, if (-(ci->facial_frown) < cg.time)//are we done frowning ? { // yes, reset frown timer ci->facial_frown = cg.time + flrand(7000.0, 10000.0); } else { // not yet, so choose frown anim = FACE_FROWN; } } else// no we aren't frowning { // but should we start ? if (ci->facial_frown < cg.time) { anim = FACE_FROWN; // set frown timer ci->facial_frown = -(cg.time + 2000.0); } } } }//talking }//dead if (anim != -1) { CG_G2SetHeadAnim( cent, anim ); return qtrue; } return qfalse; } static void CG_G2PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t legsAngles) { clientInfo_t *ci; //rww - now do ragdoll stuff if ((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG)) { vec3_t forcedAngles; VectorClear(forcedAngles); forcedAngles[YAW] = cent->lerpAngles[YAW]; if (CG_RagDoll(cent, forcedAngles)) { //if we managed to go into the rag state, give our ent axis the forced angles and return. AnglesToAxis( forcedAngles, legs ); VectorCopy(forcedAngles, legsAngles); return; } } else if (cent->isRagging) { cent->isRagging = qfalse; trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. } if (cent->currentState.eType == ET_NPC) { ci = cent->npcClient; assert(ci); } else { ci = &cgs.clientinfo[cent->currentState.number]; } //rww - Quite possibly the most arguments for a function ever. if (cent->localAnimIndex <= 1) { //don't do these things on non-humanoids vec3_t lookAngles; entityState_t *emplaced = NULL; if (cent->currentState.hasLookTarget) { VectorSubtract(cg_entities[cent->currentState.lookTarget].lerpOrigin, cent->lerpOrigin, lookAngles); vectoangles(lookAngles, lookAngles); ci->lookTime = cg.time + 1000; } else { VectorCopy(cent->lerpAngles, lookAngles); } lookAngles[PITCH] = 0; if (cent->currentState.otherEntityNum2) { emplaced = &cg_entities[cent->currentState.otherEntityNum2].currentState; } BG_G2PlayerAngles(cent->ghoul2, ci->bolt_motion, ¢->currentState, cg.time, cent->lerpOrigin, cent->lerpAngles, legs, legsAngles, ¢->pe.torso.yawing, ¢->pe.torso.pitching, ¢->pe.legs.yawing, ¢->pe.torso.yawAngle, ¢->pe.torso.pitchAngle, ¢->pe.legs.yawAngle, cg.frametime, cent->turAngles, cent->modelScale, ci->legsAnim, ci->torsoAnim, &ci->corrTime, lookAngles, ci->lastHeadAngles, ci->lookTime, emplaced, &ci->superSmoothTime); if (cent->currentState.heldByClient && cent->currentState.heldByClient <= MAX_CLIENTS) { //then put our arm in this client's hand //is index+1 because index 0 is valid. int heldByIndex = cent->currentState.heldByClient-1; centity_t *other = &cg_entities[heldByIndex]; if (other && other->ghoul2 && ci->bolt_lhand) { mdxaBone_t boltMatrix; vec3_t boltOrg; trap_G2API_GetBoltMatrix(other->ghoul2, 0, ci->bolt_lhand, &boltMatrix, other->turAngles, other->lerpOrigin, cg.time, cgs.gameModels, other->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg.time, ¢->currentState, cent->currentState.torsoAnim/*BOTH_DEAD1*/, boltOrg, ¢->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qfalse); } } else if (cent->ikStatus) { //make sure we aren't IKing if we don't have anyone to hold onto us. BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg.time, ¢->currentState, cent->currentState.torsoAnim/*BOTH_DEAD1*/, vec3_origin, ¢->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qtrue); } } else if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) { vec3_t lookAngles; VectorCopy(cent->lerpAngles, legsAngles); legsAngles[PITCH] = 0; AnglesToAxis( legsAngles, legs ); VectorCopy(cent->lerpAngles, lookAngles); lookAngles[YAW] = lookAngles[ROLL] = 0; BG_G2ATSTAngles( cent->ghoul2, cg.time, lookAngles ); } else { if (cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) { //fighters actually want to take pitch and roll into account for the axial angles VectorCopy(cent->lerpAngles, legsAngles); AnglesToAxis( legsAngles, legs ); } else if (cent->currentState.eType == ET_NPC && cent->currentState.m_iVehicleNum && cent->currentState.NPC_class != CLASS_VEHICLE ) { //an NPC bolted to a vehicle should use the full angles VectorCopy(cent->lerpAngles, legsAngles); AnglesToAxis( legsAngles, legs ); } else { vec3_t nhAngles; if (cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER) { //yeah, a hack, sorry. VectorSet(nhAngles, 0, cent->lerpAngles[YAW], cent->lerpAngles[ROLL]); } else { VectorSet(nhAngles, 0, cent->lerpAngles[YAW], 0); } AnglesToAxis( nhAngles, legs ); } } //See if we have any bone angles sent from the server CG_G2ServerBoneAngles(cent); } //========================================================================== /* =============== CG_TrailItem =============== */ #if 0 static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { refEntity_t ent; vec3_t angles; vec3_t axis[3]; VectorCopy( cent->lerpAngles, angles ); angles[PITCH] = 0; angles[ROLL] = 0; AnglesToAxis( angles, axis ); memset( &ent, 0, sizeof( ent ) ); VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); ent.origin[2] += 16; angles[YAW] += 90; AnglesToAxis( angles, ent.axis ); ent.hModel = hModel; trap_R_AddRefEntityToScene( &ent ); } #endif /* =============== CG_PlayerFlag =============== */ static void CG_PlayerFlag( centity_t *cent, qhandle_t hModel ) { refEntity_t ent; vec3_t angles; vec3_t axis[3]; vec3_t boltOrg, tAng, getAng, right; mdxaBone_t boltMatrix; clientInfo_t *ci; if (cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { return; } if (!cent->ghoul2) { return; } if (cent->currentState.eType == ET_NPC) { ci = cent->npcClient; assert(ci); } else { ci = &cgs.clientinfo[cent->currentState.number]; } VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_llumbar, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, tAng); vectoangles(tAng, tAng); VectorCopy(cent->lerpAngles, angles); boltOrg[2] -= 12; VectorSet(getAng, 0, cent->lerpAngles[1], 0); AngleVectors(getAng, 0, right, 0); boltOrg[0] += right[0]*8; boltOrg[1] += right[1]*8; boltOrg[2] += right[2]*8; angles[PITCH] = -cent->lerpAngles[PITCH]/2-30; angles[YAW] = tAng[YAW]+270; AnglesToAxis(angles, axis); memset( &ent, 0, sizeof( ent ) ); VectorMA( boltOrg, 24, axis[0], ent.origin ); angles[ROLL] += 20; AnglesToAxis( angles, ent.axis ); ent.hModel = hModel; ent.modelScale[0] = 0.5; ent.modelScale[1] = 0.5; ent.modelScale[2] = 0.5; ScaleModelAxis(&ent); /* if (cent->currentState.number == cg.snap->ps.clientNum) { //If we're the current client (in third person), render the flag on our back transparently ent.renderfx |= RF_FORCE_ENT_ALPHA; ent.shaderRGBA[3] = 100; } */ //FIXME: Not doing this at the moment because sorting totally messes up trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_PlayerPowerups =============== */ static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { int powerups; clientInfo_t *ci; powerups = cent->currentState.powerups; if ( !powerups ) { return; } // quad gives a dlight if ( powerups & ( 1 << PW_QUAD ) ) { trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); } if (cent->currentState.eType == ET_NPC) { ci = cent->npcClient; assert(ci); } else { ci = &cgs.clientinfo[ cent->currentState.clientNum ]; } // redflag if ( powerups & ( 1 << PW_REDFLAG ) ) { CG_PlayerFlag( cent, cgs.media.redFlagModel ); trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); } // blueflag if ( powerups & ( 1 << PW_BLUEFLAG ) ) { CG_PlayerFlag( cent, cgs.media.blueFlagModel ); trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); } // neutralflag if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); } // haste leaves smoke trails /* if ( powerups & ( 1 << PW_HASTE ) ) { CG_HasteTrail( cent ); } */ } /* =============== 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 ) { rf = RF_THIRD_PERSON; // only show in mirrors } else { rf = 0; } memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); ent.origin[2] += 48; 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_PlayerFloatSprite Same as above but allows custom RGBA values =============== */ #if 0 static void CG_PlayerFloatSpriteRGBA( centity_t *cent, qhandle_t shader, vec4_t rgba ) { int rf; refEntity_t ent; if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { rf = RF_THIRD_PERSON; // only show in mirrors } else { rf = 0; } memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); ent.origin[2] += 48; ent.reType = RT_SPRITE; ent.customShader = shader; ent.radius = 10; ent.renderfx = rf; ent.shaderRGBA[0] = rgba[0]; ent.shaderRGBA[1] = rgba[1]; ent.shaderRGBA[2] = rgba[2]; ent.shaderRGBA[3] = rgba[3]; trap_R_AddRefEntityToScene( &ent ); } #endif /* =============== CG_PlayerSprites Float sprites over the player's head =============== */ static void CG_PlayerSprites( centity_t *cent ) { // int team; if (cg.snap && CG_IsMindTricked(cent->currentState.trickedentindex, cent->currentState.trickedentindex2, cent->currentState.trickedentindex3, cent->currentState.trickedentindex4, cg.snap->ps.clientNum)) { return; //this entity is mind-tricking the current client, so don't render it } if ( cent->currentState.eFlags & EF_CONNECTION ) { CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); return; } if (cent->vChatTime > cg.time) { CG_PlayerFloatSprite( cent, cgs.media.vchatShader ); } else if ( cent->currentState.eType != ET_NPC && //don't draw talk balloons on NPCs (cent->currentState.eFlags & EF_TALK) ) { CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); 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 128 static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; trace_t trace; float alpha; float radius = 24.0f; *shadowPlane = 0; if ( cg_shadows.integer == 0 ) { return qfalse; } // no shadows when cloaked if ( cent->currentState.powerups & ( 1 << PW_CLOAKED )) { return qfalse; } if (cent->currentState.eFlags & EF_DEAD) { return qfalse; } if (CG_IsMindTricked(cent->currentState.trickedentindex, cent->currentState.trickedentindex2, cent->currentState.trickedentindex3, cent->currentState.trickedentindex4, cg.snap->ps.clientNum)) { return qfalse; //this entity is mind-tricking the current client, so don't render it } if ( cg_shadows.integer == 1 ) {//dropshadow if (cent->currentState.m_iVehicleNum && cent->currentState.NPC_class != CLASS_VEHICLE ) {//riding a vehicle, no dropshadow return qfalse; } } // send a trace down from the player to the ground VectorCopy( cent->lerpOrigin, end ); if (cg_shadows.integer == 2) { //stencil end[2] -= 4096.0f; trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { trace.endpos[2] = cent->lerpOrigin[2]-25.0f; } } else { 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; } } if (cg_shadows.integer == 2) { //stencil shadows need plane to be on ground *shadowPlane = trace.endpos[2]; } else { *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; // bk0101022 - hack / FPE - bogus planes? //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) // add the mark as a temporary, so it goes directly to the renderer // without taking a spot in the cg_marks array if ( cent->currentState.NPC_class == CLASS_REMOTE || cent->currentState.NPC_class == CLASS_SEEKER ) { radius = 8.0f; } CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, radius, 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_SLIME | 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_SLIME | CONTENTS_LAVA ) ) { return; } // trace down to find the surface trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | 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 ); } #define REFRACT_EFFECT_DURATION 500 static void CG_ForcePushBlur( vec3_t org, centity_t *cent ) { if (!cent || !cg_renderToTextureFX.integer) { localEntity_t *ex; ex = CG_AllocLocalEntity(); ex->leType = LE_PUFF; ex->refEntity.reType = RT_SPRITE; ex->radius = 2.0f; ex->startTime = cg.time; ex->endTime = ex->startTime + 120; VectorCopy( org, ex->pos.trBase ); ex->pos.trTime = cg.time; ex->pos.trType = TR_LINEAR; VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta ); ex->color[0] = 24; ex->color[1] = 32; ex->color[2] = 40; ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); ex = CG_AllocLocalEntity(); ex->leType = LE_PUFF; ex->refEntity.reType = RT_SPRITE; ex->refEntity.rotation = 180.0f; ex->radius = 2.0f; ex->startTime = cg.time; ex->endTime = ex->startTime + 120; VectorCopy( org, ex->pos.trBase ); ex->pos.trTime = cg.time; ex->pos.trType = TR_LINEAR; VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta ); ex->color[0] = 24; ex->color[1] = 32; ex->color[2] = 40; ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); } else { //superkewl "refraction" (well sort of) effect -rww refEntity_t ent; vec3_t ang; float scale; float vLen; float alpha; int tDif; if (!cent->bodyFadeTime) { //the duration for the expansion and fade cent->bodyFadeTime = cg.time + REFRACT_EFFECT_DURATION; } //closer tDif is to 0, the closer we are to //being "done" tDif = (cent->bodyFadeTime - cg.time); if ((REFRACT_EFFECT_DURATION-tDif) < 200) { //stop following the hand after a little and stay in a fixed spot //save the initial spot of the effect VectorCopy(org, cent->pushEffectOrigin); } //scale from 1.0f to 0.1f then hold at 0.1 for the rest of the duration if (cent->currentState.powerups & (1 << PW_PULL)) { scale = (float)(REFRACT_EFFECT_DURATION-tDif)*0.003f; } else { scale = (float)(tDif)*0.003f; } if (scale > 1.0f) { scale = 1.0f; } else if (scale < 0.2f) { scale = 0.2f; } //start alpha at 244, fade to 10 alpha = (float)tDif*0.488f; if (alpha > 244.0f) { alpha = 244.0f; } else if (alpha < 10.0f) { alpha = 10.0f; } memset( &ent, 0, sizeof( ent ) ); ent.shaderTime = (cent->bodyFadeTime-REFRACT_EFFECT_DURATION) / 1000.0f; VectorCopy( cent->pushEffectOrigin, ent.origin ); VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); vLen = VectorLength(ent.axis[0]); if (vLen <= 0.1f) { // Entity is right on vieworg. quit. return; } vectoangles(ent.axis[0], ang); ang[ROLL] += 180.0f; AnglesToAxis(ang, ent.axis); //radius must be a power of 2, and is the actual captured texture size if (vLen < 128) { ent.radius = 256; } else if (vLen < 256) { ent.radius = 128; } else if (vLen < 512) { ent.radius = 64; } else { ent.radius = 32; } VectorScale(ent.axis[0], scale, ent.axis[0]); VectorScale(ent.axis[1], scale, ent.axis[1]); VectorScale(ent.axis[2], scale, ent.axis[2]); ent.hModel = cgs.media.halfShieldModel; ent.customShader = cgs.media.refractionShader; //cgs.media.cloakedShader; ent.nonNormalizedAxes = qtrue; //make it partially transparent so it blends with the background ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); ent.shaderRGBA[0] = 255.0f; ent.shaderRGBA[1] = 255.0f; ent.shaderRGBA[2] = 255.0f; ent.shaderRGBA[3] = alpha; trap_R_AddRefEntityToScene( &ent ); } } static const char *cg_pushBoneNames[] = { "cranium", "lower_lumbar", "rhand", "lhand", "ltibia", "rtibia", "lradius", "rradius", NULL }; static void CG_ForcePushBodyBlur( centity_t *cent ) { vec3_t fxOrg; mdxaBone_t boltMatrix; int bolt; int i; if (cent->localAnimIndex > 1) { //Sorry, the humanoid IS IN ANOTHER CASTLE. return; } if (cg.snap && CG_IsMindTricked(cent->currentState.trickedentindex, cent->currentState.trickedentindex2, cent->currentState.trickedentindex3, cent->currentState.trickedentindex4, cg.snap->ps.clientNum)) { return; //this entity is mind-tricking the current client, so don't render it } assert(cent->ghoul2); for (i = 0; cg_pushBoneNames[i]; i++) { //go through all the bones we want to put a blur effect on bolt = trap_G2API_AddBolt(cent->ghoul2, 0, cg_pushBoneNames[i]); if (bolt == -1) { assert(!"You've got an invalid bone/bolt name in cg_pushBoneNames"); continue; } trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &boltMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, fxOrg); //standard effect, don't be refractive (for now) CG_ForcePushBlur(fxOrg, NULL); } } static void CG_ForceGripEffect( vec3_t org ) { localEntity_t *ex; float wv = sin( cg.time * 0.004f ) * 0.08f + 0.1f; ex = CG_AllocLocalEntity(); ex->leType = LE_PUFF; ex->refEntity.reType = RT_SPRITE; ex->radius = 2.0f; ex->startTime = cg.time; ex->endTime = ex->startTime + 120; VectorCopy( org, ex->pos.trBase ); ex->pos.trTime = cg.time; ex->pos.trType = TR_LINEAR; VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta ); ex->color[0] = 200+((wv*255)); if (ex->color[0] > 255) { ex->color[0] = 255; } ex->color[1] = 0; ex->color[2] = 0; ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); ex = CG_AllocLocalEntity(); ex->leType = LE_PUFF; ex->refEntity.reType = RT_SPRITE; ex->refEntity.rotation = 180.0f; ex->radius = 2.0f; ex->startTime = cg.time; ex->endTime = ex->startTime + 120; VectorCopy( org, ex->pos.trBase ); ex->pos.trTime = cg.time; ex->pos.trType = TR_LINEAR; VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta ); /* ex->color[0] = 200+((wv*255)); if (ex->color[0] > 255) { ex->color[0] = 255; } */ ex->color[0] = 255; ex->color[1] = 255; ex->color[2] = 255; ex->refEntity.customShader = cgs.media.redSaberGlowShader;//trap_R_RegisterShader( "gfx/effects/forcePush" ); } /* =============== CG_AddRefEntityWithPowerups Adds a piece with modifications or duplications for powerups Also called by CG_Missile for quad rockets, but nobody can tell... =============== */ void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { if (CG_IsMindTricked(state->trickedentindex, state->trickedentindex2, state->trickedentindex3, state->trickedentindex4, cg.snap->ps.clientNum)) { return; //this entity is mind-tricking the current client, so don't render it } trap_R_AddRefEntityToScene( ent ); } #define MAX_SHIELD_TIME 2000.0 #define MIN_SHIELD_TIME 2000.0 void CG_PlayerShieldHit(int entitynum, vec3_t dir, int amount) { centity_t *cent; int time; if (entitynum<0 || entitynum >= MAX_ENTITIES) { return; } cent = &cg_entities[entitynum]; if (amount > 100) { time = cg.time + MAX_SHIELD_TIME; // 2 sec. } else { time = cg.time + 500 + amount*15; } if (time > cent->damageTime) { cent->damageTime = time; VectorScale(dir, -1, dir); vectoangles(dir, cent->damageAngles); } } void CG_DrawPlayerShield(centity_t *cent, vec3_t origin) { refEntity_t ent; int alpha; float scale; // Don't draw the shield when the player is dead. if (cent->currentState.eFlags & EF_DEAD) { return; } memset( &ent, 0, sizeof( ent ) ); VectorCopy( origin, ent.origin ); ent.origin[2] += 10.0; AnglesToAxis( cent->damageAngles, ent.axis ); alpha = 255.0 * ((cent->damageTime - cg.time) / MIN_SHIELD_TIME) + random()*16; if (alpha>255) alpha=255; // Make it bigger, but tighter if more solid scale = 1.4 - ((float)alpha*(0.4/255.0)); // Range from 1.0 to 1.4 VectorScale( ent.axis[0], scale, ent.axis[0] ); VectorScale( ent.axis[1], scale, ent.axis[1] ); VectorScale( ent.axis[2], scale, ent.axis[2] ); ent.hModel = cgs.media.halfShieldModel; ent.customShader = cgs.media.halfShieldShader; ent.shaderRGBA[0] = alpha; ent.shaderRGBA[1] = alpha; ent.shaderRGBA[2] = alpha; ent.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene( &ent ); } void CG_PlayerHitFX(centity_t *cent) { // only do the below fx if the cent in question is...uh...me, and it's first person. if (cent->currentState.clientNum != cg.predictedPlayerState.clientNum || cg.renderingThirdPerson) { if (cent->damageTime > cg.time && cent->currentState.NPC_class != CLASS_VEHICLE ) { CG_DrawPlayerShield(cent, cent->lerpOrigin); } return; } } /* ================= CG_LightVerts ================= */ int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) { int i, j; float incoming; vec3_t ambientLight; vec3_t lightDir; vec3_t directedLight; trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); for (i = 0; i < numVerts; i++) { incoming = DotProduct (normal, lightDir); if ( incoming <= 0 ) { verts[i].modulate[0] = ambientLight[0]; verts[i].modulate[1] = ambientLight[1]; verts[i].modulate[2] = ambientLight[2]; verts[i].modulate[3] = 255; continue; } j = ( ambientLight[0] + incoming * directedLight[0] ); if ( j > 255 ) { j = 255; } verts[i].modulate[0] = j; j = ( ambientLight[1] + incoming * directedLight[1] ); if ( j > 255 ) { j = 255; } verts[i].modulate[1] = j; j = ( ambientLight[2] + incoming * directedLight[2] ); if ( j > 255 ) { j = 255; } verts[i].modulate[2] = j; verts[i].modulate[3] = 255; } return qtrue; } static void CG_RGBForSaberColor( saber_colors_t color, vec3_t rgb ) { switch( color ) { case SABER_RED: VectorSet( rgb, 1.0f, 0.2f, 0.2f ); break; case SABER_ORANGE: VectorSet( rgb, 1.0f, 0.5f, 0.1f ); break; case SABER_YELLOW: VectorSet( rgb, 1.0f, 1.0f, 0.2f ); break; case SABER_GREEN: VectorSet( rgb, 0.2f, 1.0f, 0.2f ); break; case SABER_BLUE: VectorSet( rgb, 0.2f, 0.4f, 1.0f ); break; case SABER_PURPLE: VectorSet( rgb, 0.9f, 0.2f, 1.0f ); break; } } static void CG_DoSaberLight( saberInfo_t *saber ) { vec3_t positions[MAX_BLADES*2], mid={0}, rgbs[MAX_BLADES*2], rgb={0}; float lengths[MAX_BLADES*2]={0}, totallength = 0, numpositions = 0, dist, diameter = 0; int i, j; //RGB combine all the colors of the sabers you're using into one averaged color! if ( !saber ) { return; } if ( (saber->saberFlags2&SFL2_NO_DLIGHT) ) {//no dlight! return; } for ( i = 0; i < saber->numBlades; i++ ) { if ( saber->blade[i].length >= 0.5f ) { //FIXME: make RGB sabers CG_RGBForSaberColor( saber->blade[i].color, rgbs[i] ); lengths[i] = saber->blade[i].length; if ( saber->blade[i].length*2.0f > diameter ) { diameter = saber->blade[i].length*2.0f; } totallength += saber->blade[i].length; VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length, saber->blade[i].muzzleDir, positions[i] ); if ( !numpositions ) {//first blade, store middle of that as midpoint VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length*0.5, saber->blade[i].muzzleDir, mid ); VectorCopy( rgbs[i], rgb ); } numpositions++; } } if ( totallength ) {//actually have something to do if ( numpositions == 1 ) {//only 1 blade, midpoint is already set (halfway between the start and end of that blade), rgb is already set, so it diameter } else {//multiple blades, calc averages VectorClear( mid ); VectorClear( rgb ); //now go through all the data and get the average RGB and middle position and the radius for ( i = 0; i < MAX_BLADES*2; i++ ) { if ( lengths[i] ) { VectorMA( rgb, lengths[i], rgbs[i], rgb ); VectorAdd( mid, positions[i], mid ); } } //get middle rgb VectorScale( rgb, 1/totallength, rgb );//get the average, normalized RGB //get mid position VectorScale( mid, 1/numpositions, mid ); //find the farthest distance between the blade tips, this will be our diameter for ( i = 0; i < MAX_BLADES*2; i++ ) { if ( lengths[i] ) { for ( j = 0; j < MAX_BLADES*2; j++ ) { if ( lengths[j] ) { dist = Distance( positions[i], positions[j] ); if ( dist > diameter ) { diameter = dist; } } } } } } trap_R_AddLightToScene( mid, diameter + (random()*8.0f), rgb[0], rgb[1], rgb[2] ); } } void CG_DoSaber( vec3_t origin, vec3_t dir, float length, float lengthMax, float radius, saber_colors_t color, int rfx, qboolean doLight ) { vec3_t mid; qhandle_t blade = 0, glow = 0; refEntity_t saber; float radiusmult; float radiusRange; float radiusStart; if ( length < 0.5f ) { // if the thing is so short, just forget even adding me. return; } // Find the midpoint of the saber for lighting purposes VectorMA( origin, length * 0.5f, dir, mid ); switch( color ) { case SABER_RED: glow = cgs.media.redSaberGlowShader; blade = cgs.media.redSaberCoreShader; break; case SABER_ORANGE: glow = cgs.media.orangeSaberGlowShader; blade = cgs.media.orangeSaberCoreShader; break; case SABER_YELLOW: glow = cgs.media.yellowSaberGlowShader; blade = cgs.media.yellowSaberCoreShader; break; case SABER_GREEN: glow = cgs.media.greenSaberGlowShader; blade = cgs.media.greenSaberCoreShader; break; case SABER_BLUE: glow = cgs.media.blueSaberGlowShader; blade = cgs.media.blueSaberCoreShader; break; case SABER_PURPLE: glow = cgs.media.purpleSaberGlowShader; blade = cgs.media.purpleSaberCoreShader; break; default: glow = cgs.media.blueSaberGlowShader; blade = cgs.media.blueSaberCoreShader; break; } if (doLight) { // always add a light because sabers cast a nice glow before they slice you in half!! or something... vec3_t rgb={1,1,1}; CG_RGBForSaberColor( color, rgb ); trap_R_AddLightToScene( mid, (length*1.4f) + (random()*3.0f), rgb[0], rgb[1], rgb[2] ); } memset( &saber, 0, sizeof( refEntity_t )); // Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many // refEnts to do each glow blob individually saber.saberLength = length; // Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed. // It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat if (length < lengthMax) { radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5. } else { radiusmult = 1.0; } if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) { //draw the blade as a post-render so it doesn't get in the cap... rfx |= RF_FORCEPOST; } radiusRange = radius * 0.075f; radiusStart = radius-radiusRange; saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; //saber.radius = (2.8f + crandom() * 0.2f)*radiusmult; VectorCopy( origin, saber.origin ); VectorCopy( dir, saber.axis[0] ); saber.reType = RT_SABER_GLOW; saber.customShader = glow; saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff; saber.renderfx = rfx; trap_R_AddRefEntityToScene( &saber ); // Do the hot core VectorMA( origin, length, dir, saber.origin ); VectorMA( origin, -1, dir, saber.oldorigin ); // CG_TestLine(saber.origin, saber.oldorigin, 50, 0x000000ff, 3); saber.customShader = blade; saber.reType = RT_LINE; radiusStart = radius/3.0f; saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; // saber.radius = (1.0 + crandom() * 0.2f)*radiusmult; saber.shaderTexCoord[0] = saber.shaderTexCoord[1] = 1.0f; saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff; trap_R_AddRefEntityToScene( &saber ); } //-------------------------------------------------------------- // CG_GetTagWorldPosition // // Can pass in NULL for the axis //-------------------------------------------------------------- void CG_GetTagWorldPosition( refEntity_t *model, char *tag, vec3_t pos, vec3_t axis[3] ) { orientation_t orientation; int i = 0; // Get the requested tag trap_R_LerpTag( &orientation, model->hModel, model->oldframe, model->frame, 1.0f - model->backlerp, tag ); VectorCopy( model->origin, pos ); for ( i = 0 ; i < 3 ; i++ ) { VectorMA( pos, orientation.origin[i], model->axis[i], pos ); } if ( axis ) { MatrixMultiply( orientation.axis, model->axis, axis ); } } #define MAX_MARK_FRAGMENTS 128 #define MAX_MARK_POINTS 384 extern markPoly_t *CG_AllocMark(); void CG_CreateSaberMarks( vec3_t start, vec3_t end, vec3_t normal ) { // byte colors[4]; int i, j; int numFragments; vec3_t axis[3], originalPoints[4], mid; vec3_t markPoints[MAX_MARK_POINTS], projection; polyVert_t *v, verts[MAX_VERTS_ON_POLY]; markPoly_t *mark; markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; float radius = 0.65f; if ( !cg_addMarks.integer ) { return; } VectorSubtract( end, start, axis[1] ); VectorNormalize( axis[1] ); // create the texture axis VectorCopy( normal, axis[0] ); CrossProduct( axis[1], axis[0], axis[2] ); // create the full polygon that we'll project for ( i = 0 ; i < 3 ; i++ ) { // stretch a bit more in the direction that we are traveling in... debateable as to whether this makes things better or worse originalPoints[0][i] = start[i] - radius * axis[1][i] - radius * axis[2][i]; originalPoints[1][i] = end[i] + radius * axis[1][i] - radius * axis[2][i]; originalPoints[2][i] = end[i] + radius * axis[1][i] + radius * axis[2][i]; originalPoints[3][i] = start[i] - radius * axis[1][i] + radius * axis[2][i]; } VectorScale( normal, -1, projection ); // get the fragments numFragments = trap_CM_MarkFragments( 4, (const float (*)[3])originalPoints, projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments ); for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { // we have an upper limit on the complexity of polygons that we store persistantly if ( mf->numPoints > MAX_VERTS_ON_POLY ) { mf->numPoints = MAX_VERTS_ON_POLY; } for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { vec3_t delta; // Set up our texture coords, this may need some work VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); VectorAdd( end, start, mid ); VectorScale( mid, 0.5f, mid ); VectorSubtract( v->xyz, mid, delta ); v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * (0.05f + random() * 0.03f); v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * (0.15f + random() * 0.05f); } if (cg_saberDynamicMarks.integer) { int i = 0; int i_2 = 0; addpolyArgStruct_t apArgs; vec3_t x; memset (&apArgs, 0, sizeof(apArgs)); while (i < 4) { while (i_2 < 3) { apArgs.p[i][i_2] = verts[i].xyz[i_2]; i_2++; } i_2 = 0; i++; } i = 0; i_2 = 0; while (i < 4) { while (i_2 < 2) { apArgs.ev[i][i_2] = verts[i].st[i_2]; i_2++; } i_2 = 0; i++; } //When using addpoly, having a situation like this tends to cause bad results. //(I assume it doesn't like trying to draw a polygon over two planes and extends //the vertex out to some odd value) VectorSubtract(apArgs.p[0], apArgs.p[3], x); if (VectorLength(x) > 3.0f) { return; } apArgs.numVerts = mf->numPoints; VectorCopy(vec3_origin, apArgs.vel); VectorCopy(vec3_origin, apArgs.accel); apArgs.alpha1 = 1.0f; apArgs.alpha2 = 0.0f; apArgs.alphaParm = 255.0f; VectorSet(apArgs.rgb1, 0.0f, 0.0f, 0.0f); VectorSet(apArgs.rgb2, 0.0f, 0.0f, 0.0f); apArgs.rgbParm = 0.0f; apArgs.bounce = 0; apArgs.motionDelay = 0; apArgs.killTime = cg_saberDynamicMarkTime.integer; apArgs.shader = cgs.media.rivetMarkShader; apArgs.flags = 0x08000000|0x00000004; trap_FX_AddPoly(&apArgs); apArgs.shader = cgs.media.mSaberDamageGlow; apArgs.rgb1[0] = 215 + random() * 40.0f; apArgs.rgb1[1] = 96 + random() * 32.0f; apArgs.rgb1[2] = apArgs.alphaParm = random()*15.0f; apArgs.rgb1[0] /= 255; apArgs.rgb1[1] /= 255; apArgs.rgb1[2] /= 255; VectorCopy(apArgs.rgb1, apArgs.rgb2); apArgs.killTime = 100; trap_FX_AddPoly(&apArgs); } else { // save it persistantly, do burn first mark = CG_AllocMark(); mark->time = cg.time; mark->alphaFade = qtrue; mark->markShader = cgs.media.rivetMarkShader; mark->poly.numVerts = mf->numPoints; mark->color[0] = mark->color[1] = mark->color[2] = mark->color[3] = 255; memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); // And now do a glow pass // by moving the start time back, we can hack it to fade out way before the burn does mark = CG_AllocMark(); mark->time = cg.time - 8500; mark->alphaFade = qfalse; mark->markShader = cgs.media.mSaberDamageGlow; mark->poly.numVerts = mf->numPoints; mark->color[0] = 215 + random() * 40.0f; mark->color[1] = 96 + random() * 32.0f; mark->color[2] = mark->color[3] = random()*15.0f; memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); } } } qboolean CG_G2TraceCollide(trace_t *tr, vec3_t const mins, vec3_t const maxs, const vec3_t lastValidStart, const vec3_t lastValidEnd) { G2Trace_t G2Trace; centity_t *g2Hit; vec3_t angles; int tN = 0; float fRadius = 0.0f; if (mins && maxs && (mins[0] || maxs[0])) { fRadius=(maxs[0]-mins[0])/2.0f; } memset (&G2Trace, 0, sizeof(G2Trace)); while (tN < MAX_G2_COLLISIONS) { G2Trace[tN].mEntityNum = -1; tN++; } g2Hit = &cg_entities[tr->entityNum]; if (g2Hit && g2Hit->ghoul2) { angles[ROLL] = angles[PITCH] = 0; angles[YAW] = g2Hit->lerpAngles[YAW]; if (cg_optvehtrace.integer && g2Hit->currentState.eType == ET_NPC && g2Hit->currentState.NPC_class == CLASS_VEHICLE && g2Hit->m_pVehicle) { trap_G2API_CollisionDetectCache ( G2Trace, g2Hit->ghoul2, angles, g2Hit->lerpOrigin, cg.time, g2Hit->currentState.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, cg_g2TraceLod.integer, fRadius ); } else { trap_G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2Hit->lerpOrigin, cg.time, g2Hit->currentState.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, cg_g2TraceLod.integer, fRadius ); } if (G2Trace[0].mEntityNum != g2Hit->currentState.number) { tr->fraction = 1.0f; tr->entityNum = ENTITYNUM_NONE; tr->startsolid = 0; tr->allsolid = 0; return qfalse; } else { //Yay! VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos); VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal); return qtrue; } } return qfalse; } void CG_G2SaberEffects(vec3_t start, vec3_t end, centity_t *owner) { trace_t trace; vec3_t startTr; vec3_t endTr; qboolean backWards = qfalse; qboolean doneWithTraces = qfalse; while (!doneWithTraces) { if (!backWards) { VectorCopy(start, startTr); VectorCopy(end, endTr); } else { VectorCopy(end, startTr); VectorCopy(start, endTr); } CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID ); if (trace.entityNum < MAX_CLIENTS) { //hit a client.. CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr); if (trace.entityNum != ENTITYNUM_NONE) { //it succeeded with the ghoul2 trace trap_FX_PlayEffectID( cgs.effects.mSaberBloodSparks, trace.endpos, trace.plane.normal, -1, -1 ); trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3)))); } } if (!backWards) { backWards = qtrue; } else { doneWithTraces = qtrue; } } } #define CG_MAX_SABER_COMP_TIME 400 //last registered saber entity hit must match within this many ms for the client effect to take place. void CG_AddGhoul2Mark(int shader, float size, vec3_t start, vec3_t end, int entnum, vec3_t entposition, float entangle, void *ghoul2, vec3_t scale, int lifeTime) { SSkinGoreData goreSkin; assert(ghoul2); memset ( &goreSkin, 0, sizeof(goreSkin) ); if (trap_G2API_GetNumGoreMarks(ghoul2, 0) >= cg_ghoul2Marks.integer) { //you've got too many marks already return; } goreSkin.growDuration = -1; // default expandy time goreSkin.goreScaleStartFraction = 1.0; // default start scale goreSkin.frontFaces = qtrue; goreSkin.backFaces = qtrue; goreSkin.lifeTime = lifeTime; //last randomly 10-20 seconds /* if (lifeTime) { goreSkin.fadeOutTime = lifeTime*0.1; //default fade duration is relative to lifetime. } goreSkin.fadeRGB = qtrue; //fade on RGB instead of alpha (this depends on the shader really, modify if needed) */ //rwwFIXMEFIXME: fade has sorting issues with other non-fading decals, disabled until fixed goreSkin.baseModelOnly = qfalse; goreSkin.currentTime = cg.time; goreSkin.entNum = entnum; goreSkin.SSize = size; goreSkin.TSize = size; goreSkin.theta = flrand(0.0f,6.28f); goreSkin.shader = shader; if (!scale[0] && !scale[1] && !scale[2]) { VectorSet(goreSkin.scale, 1.0f, 1.0f, 1.0f); } else { VectorCopy(goreSkin.scale, scale); } VectorCopy (start, goreSkin.hitLocation); VectorSubtract(end, start, goreSkin.rayDirection); if (VectorNormalize(goreSkin.rayDirection)<.1f) { return; } VectorCopy ( entposition, goreSkin.position ); goreSkin.angles[YAW] = entangle; trap_G2API_AddSkinGore(ghoul2, &goreSkin); } void CG_SaberCompWork(vec3_t start, vec3_t end, centity_t *owner, int saberNum, int bladeNum) { trace_t trace; vec3_t startTr; vec3_t endTr; qboolean backWards = qfalse; qboolean doneWithTraces = qfalse; qboolean doEffect = qfalse; clientInfo_t *client = NULL; if ((cg.time - owner->serverSaberHitTime) > CG_MAX_SABER_COMP_TIME) { return; } if (cg.time == owner->serverSaberHitTime) { //don't want to do it the same frame as the server hit, to avoid burst effect concentrations every x ms. return; } while (!doneWithTraces) { if (!backWards) { VectorCopy(start, startTr); VectorCopy(end, endTr); } else { VectorCopy(end, startTr); VectorCopy(start, endTr); } CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID ); if (trace.entityNum == owner->serverSaberHitIndex) { //this is the guy the server says we last hit, so continue. if (cg_entities[trace.entityNum].ghoul2) { //If it has a g2 instance, do the proper ghoul2 checks CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr); if (trace.entityNum != ENTITYNUM_NONE) { //it succeeded with the ghoul2 trace doEffect = qtrue; if (cg_ghoul2Marks.integer) { vec3_t ePos; centity_t *trEnt = &cg_entities[trace.entityNum]; if (trEnt->ghoul2) { if (trEnt->currentState.eType != ET_NPC || trEnt->currentState.NPC_class != CLASS_VEHICLE || !trEnt->m_pVehicle || trEnt->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) { //don't do on fighters cause they have crazy full axial angles int weaponMarkShader = 0, markShader = cgs.media.bdecal_saberglow; VectorSubtract(endTr, trace.endpos, ePos); VectorNormalize(ePos); VectorMA(trace.endpos, 4.0f, ePos, ePos); if (owner->currentState.eType == ET_NPC) { client = owner->npcClient; } else { client = &cgs.clientinfo[owner->currentState.clientNum]; } if ( client && client->infoValid ) { if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) ) { if ( client->saber[saberNum].g2MarksShader2 ) {//we have a shader to use instead of the standard mark shader markShader = client->saber[saberNum].g2MarksShader2; } if ( client->saber[saberNum].g2WeaponMarkShader2 ) {//we have a shader to use as a splashback onto the weapon model weaponMarkShader = client->saber[saberNum].g2WeaponMarkShader2; } } else { if ( client->saber[saberNum].g2MarksShader ) {//we have a shader to use instead of the standard mark shader markShader = client->saber[saberNum].g2MarksShader; } if ( client->saber[saberNum].g2WeaponMarkShader ) {//we have a shader to use as a splashback onto the weapon model weaponMarkShader = client->saber[saberNum].g2WeaponMarkShader; } } } CG_AddGhoul2Mark(markShader, flrand(3.0f, 4.0f), trace.endpos, ePos, trace.entityNum, trEnt->lerpOrigin, trEnt->lerpAngles[YAW], trEnt->ghoul2, trEnt->modelScale, Q_irand(5000, 10000)); if ( weaponMarkShader ) { vec3_t splashBackDir; VectorScale( ePos, -1 , splashBackDir ); CG_AddGhoul2Mark(weaponMarkShader, flrand(0.5f, 2.0f), trace.endpos, splashBackDir, owner->currentState.clientNum, owner->lerpOrigin, owner->lerpAngles[YAW], owner->ghoul2, owner->modelScale, Q_irand(5000, 10000)); } } } } } } else { //otherwise, we're all set. doEffect = qtrue; } if (doEffect) { int hitPersonFxID = cgs.effects.mSaberBloodSparks; int hitOtherFxID = cgs.effects.mSaberCut; if (owner->currentState.eType == ET_NPC) { client = owner->npcClient; } else { client = &cgs.clientinfo[owner->currentState.clientNum]; } if ( client && client->infoValid ) { if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) ) {//use second blade style values if ( client->saber[saberNum].hitPersonEffect2 ) { hitPersonFxID = client->saber[saberNum].hitPersonEffect2; } if ( client->saber[saberNum].hitOtherEffect2 ) {//custom hit other effect hitOtherFxID = client->saber[saberNum].hitOtherEffect2; } } else {//use first blade style values if ( client->saber[saberNum].hitPersonEffect ) { hitPersonFxID = client->saber[saberNum].hitPersonEffect; } if ( client->saber[saberNum].hitOtherEffect ) {//custom hit other effect hitOtherFxID = client->saber[saberNum].hitOtherEffect; } } } if (!trace.plane.normal[0] && !trace.plane.normal[1] && !trace.plane.normal[2]) { //who cares, just shoot it somewhere. trace.plane.normal[1] = 1; } if (owner->serverSaberFleshImpact) { //do standard player/live ent hit sparks trap_FX_PlayEffectID( hitPersonFxID, trace.endpos, trace.plane.normal, -1, -1 ); //trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3)))); } else { //do the cut effect trap_FX_PlayEffectID( hitOtherFxID, trace.endpos, trace.plane.normal, -1, -1 ); } doEffect = qfalse; } } /* if (!backWards) { backWards = qtrue; } else { doneWithTraces = qtrue; } */ doneWithTraces = qtrue; //disabling backwards tr for now, sometimes it just makes too many effects. } } #define SABER_TRAIL_TIME 40.0f #define FX_USE_ALPHA 0x08000000 #include "../namespace_begin.h" qboolean BG_SuperBreakWinAnim( int anim ); #include "../namespace_end.h" void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, int saberNum, int bladeNum, vec3_t origin, vec3_t angles, qboolean fromSaber, qboolean dontDraw) { vec3_t org_, end, v, axis_[3] = {0,0,0, 0,0,0, 0,0,0}; // shut the compiler up trace_t trace; int i = 0; int trailDur; float saberLen; float diff; clientInfo_t *client; centity_t *saberEnt; saberTrail_t *saberTrail; mdxaBone_t boltMatrix; vec3_t futureAngles; effectTrailArgStruct_t fx; int scolor = 0; int useModelIndex = 0; if (cent->currentState.eType == ET_NPC) { client = cent->npcClient; assert(client); } else { client = &cgs.clientinfo[cent->currentState.number]; } saberEnt = &cg_entities[cent->currentState.saberEntityNum]; saberLen = client->saber[saberNum].blade[bladeNum].length; if (saberLen <= 0 && !dontDraw) { //don't bother then. return; } futureAngles[YAW] = angles[YAW]; futureAngles[PITCH] = angles[PITCH]; futureAngles[ROLL] = angles[ROLL]; if ( fromSaber ) { useModelIndex = 0; } else { useModelIndex = saberNum+1; } //Assume bladeNum is equal to the bolt index because bolts should be added in order of the blades. //if there is an effect on this blade, play it if ( !WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].bladeEffect ) { trap_FX_PlayBoltedEffectID(client->saber[saberNum].bladeEffect, scent->lerpOrigin, scent->ghoul2, bladeNum, scent->currentState.number, useModelIndex, -1, qfalse); } else if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].bladeEffect2 ) { trap_FX_PlayBoltedEffectID(client->saber[saberNum].bladeEffect2, scent->lerpOrigin, scent->ghoul2, bladeNum, scent->currentState.number, useModelIndex, -1, qfalse); } //get the boltMatrix trap_G2API_GetBoltMatrix(scent->ghoul2, useModelIndex, bladeNum, &boltMatrix, futureAngles, origin, cg.time, cgs.gameModels, scent->modelScale); // work the matrix axis stuff into the original axis and origins used. BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, org_); BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, axis_[0]); if (!fromSaber && saberEnt && !cent->currentState.saberInFlight) { VectorCopy(org_, saberEnt->currentState.pos.trBase); VectorCopy(axis_[0], saberEnt->currentState.apos.trBase); } VectorMA( org_, saberLen, axis_[0], end ); VectorAdd( end, axis_[0], end ); if (cent->currentState.eType == ET_NPC) { scolor = client->saber[saberNum].blade[bladeNum].color; } else { if (saberNum == 0) { scolor = client->icolor1; } else { scolor = client->icolor2; } } if (cgs.gametype >= GT_TEAM && cgs.gametype != GT_SIEGE && !cgs.jediVmerc && cent->currentState.eType != ET_NPC) { if (client->team == TEAM_RED) { scolor = SABER_RED; } else if (client->team == TEAM_BLUE) { scolor = SABER_BLUE; } } if (!cg_saberContact.integer) { //if we don't have saber contact enabled, just add the blade and don't care what it's touching goto CheckTrail; } if (!dontDraw) { if (cg_saberModelTraceEffect.integer) { CG_G2SaberEffects(org_, end, cent); } else if (cg_saberClientVisualCompensation.integer) { CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID ); if (trace.fraction != 1) { //nudge the endpos a very small amount from the beginning to the end, so the comp trace hits at the end. //I'm only bothering with this because I want to do a backwards trace too in the comp trace, so if the //blade is sticking through a player or something the standard trace doesn't it, it will make sparks //on each side. vec3_t seDif; VectorSubtract(trace.endpos, org_, seDif); VectorNormalize(seDif); trace.endpos[0] += seDif[0]*0.1f; trace.endpos[1] += seDif[1]*0.1f; trace.endpos[2] += seDif[2]*0.1f; } if (client->saber[saberNum].blade[bladeNum].storageTime < cg.time) { //debounce it in case our framerate is absurdly high. Using storageTime since it's not used for anything else in the client. CG_SaberCompWork(org_, trace.endpos, cent, saberNum, bladeNum); client->saber[saberNum].blade[bladeNum].storageTime = cg.time + 5; } } for ( i = 0; i < 1; i++ )//was 2 because it would go through architecture and leave saber trails on either side of the brush - but still looks bad if we hit a corner, blade is still 8 longer than hit { if ( i ) {//tracing from end to base CG_Trace( &trace, end, NULL, NULL, org_, ENTITYNUM_NONE, MASK_SOLID ); } else {//tracing from base to end CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID ); } if ( trace.fraction < 1.0f ) { vec3_t trDir; VectorCopy(trace.plane.normal, trDir); if (!trDir[0] && !trDir[1] && !trDir[2]) { trDir[1] = 1; } if ( (client->saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS) ) {//don't actually draw the marks/impact effects } else { if (!(trace.surfaceFlags & SURF_NOIMPACT) ) // never spark on sky { trap_FX_PlayEffectID( cgs.effects.mSparks, trace.endpos, trDir, -1, -1 ); } } //Stop saber? (it wouldn't look right if it was stuck through a thin wall and unable to hurt players on the other side) VectorSubtract(org_, trace.endpos, v); saberLen = VectorLength(v); VectorCopy(trace.endpos, end); if ( (client->saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS) ) {//don't actually draw the marks } else {//draw marks if we hit a wall // All I need is a bool to mark whether I have a previous point to work with. //....come up with something better.. if ( client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] ) { if ( trace.entityNum == ENTITYNUM_WORLD || cg_entities[trace.entityNum].currentState.eType == ET_TERRAIN || (cg_entities[trace.entityNum].currentState.eFlags & EF_PERMANENT) ) {//only put marks on architecture // Let's do some cool burn/glowing mark bits!!! CG_CreateSaberMarks( client->saber[saberNum].blade[bladeNum].trail.oldPos[i], trace.endpos, trace.plane.normal ); //make a sound if ( cg.time - client->saber[saberNum].blade[bladeNum].hitWallDebounceTime >= 100 ) {//ugh, need to have a real sound debouncer... or do this game-side client->saber[saberNum].blade[bladeNum].hitWallDebounceTime = cg.time; trap_S_StartSound ( trace.endpos, -1, CHAN_WEAPON, trap_S_RegisterSound( va("sound/weapons/saber/saberhitwall%i", Q_irand(1, 3)) ) ); } } } else { // if we impact next frame, we'll mark a slash mark client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qtrue; // CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i], // 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse ); } } // stash point so we can connect-the-dots later VectorCopy( trace.endpos, client->saber[saberNum].blade[bladeNum].trail.oldPos[i] ); VectorCopy( trace.plane.normal, client->saber[saberNum].blade[bladeNum].trail.oldNormal[i] ); } else { if ( client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] ) { // Hmmm, no impact this frame, but we have an old point // Let's put the mark there, we should use an endcap mark to close the line, but we // can probably just get away with a round mark // CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i], // 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse ); } // we aren't impacting, so turn off our mark tracking mechanism client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qfalse; } } } CheckTrail: if (!cg_saberTrail.integer) { //don't do the trail in this case goto JustDoIt; } if ( (!WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle > 1 ) || ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle2 > 1 ) ) {//don't actually draw the trail at all goto JustDoIt; } //FIXME: if trailStyle is 1, use the motion blur instead saberTrail = &client->saber[saberNum].blade[bladeNum].trail; saberTrail->duration = saberMoveData[cent->currentState.saberMove].trailLength; trailDur = (saberTrail->duration/5.0f); if (!trailDur) { //hmm.. ok, default if ( BG_SuperBreakWinAnim(cent->currentState.torsoAnim) ) { trailDur = 150; } else { trailDur = SABER_TRAIL_TIME; } } // if we happen to be timescaled or running in a high framerate situation, we don't want to flood // the system with very small trail slices...but perhaps doing it by distance would yield better results? if ( cg.time > saberTrail->lastTime + 2 || cg_saberTrail.integer == 2 ) // 2ms { if (!dontDraw) { if ( (BG_SuperBreakWinAnim(cent->currentState.torsoAnim) || saberMoveData[cent->currentState.saberMove].trailLength > 0 || ((cent->currentState.powerups & (1 << PW_SPEED) && cg_speedTrail.integer)) || (cent->currentState.saberInFlight && saberNum == 0)) && cg.time < saberTrail->lastTime + 2000 ) // if we have a stale segment, don't draw until we have a fresh one { #if 0 if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) { polyVert_t verts[4]; VectorCopy( org_, verts[0].xyz ); VectorMA( end, 3.0f, axis_[0], verts[1].xyz ); VectorCopy( saberTrail->tip, verts[2].xyz ); VectorCopy( saberTrail->base, verts[3].xyz ); //tc doesn't even matter since we're just gonna stencil an outline, but whatever. 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; 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; 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; 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; //don't capture postrender objects (now we'll postrender the saber so it doesn't get in the capture) trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue); //shader 2 is always the crazy refractive shader. trap_R_AddPolyToScene( 2, 4, verts ); } else #endif { vec3_t rgb1={255.0f,255.0f,255.0f}; switch( scolor ) { case SABER_RED: VectorSet( rgb1, 255.0f, 0.0f, 0.0f ); break; case SABER_ORANGE: VectorSet( rgb1, 255.0f, 64.0f, 0.0f ); break; case SABER_YELLOW: VectorSet( rgb1, 255.0f, 255.0f, 0.0f ); break; case SABER_GREEN: VectorSet( rgb1, 0.0f, 255.0f, 0.0f ); break; case SABER_BLUE: VectorSet( rgb1, 0.0f, 64.0f, 255.0f ); break; case SABER_PURPLE: VectorSet( rgb1, 220.0f, 0.0f, 255.0f ); break; default: VectorSet( rgb1, 0.0f, 64.0f, 255.0f ); break; } //Here we will use the happy process of filling a struct in with arguments and passing it to a trap function //so that we can take the struct and fill in an actual CTrail type using the data within it once we get it //into the effects area // Go from new muzzle to new end...then to old end...back down to old muzzle...finally // connect back to the new muzzle...this is our trail quad VectorCopy( org_, fx.mVerts[0].origin ); VectorMA( end, 3.0f, axis_[0], fx.mVerts[1].origin ); VectorCopy( saberTrail->tip, fx.mVerts[2].origin ); VectorCopy( saberTrail->base, fx.mVerts[3].origin ); diff = cg.time - saberTrail->lastTime; // I'm not sure that clipping this is really the best idea //This prevents the trail from showing at all in low framerate situations. //if ( diff <= SABER_TRAIL_TIME * 2 ) if ( diff <= 10000 ) { //don't draw it if the last time is way out of date float oldAlpha = 1.0f - ( diff / trailDur ); if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) {//does other stuff below } else { if ( (!WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle == 1 ) || ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle2 == 1 ) ) {//motion trail fx.mShader = cgs.media.swordTrailShader; VectorSet( rgb1, 32.0f, 32.0f, 32.0f ); // make the sith sword trail pretty faint trailDur *= 2.0f; // stay around twice as long? } else { fx.mShader = cgs.media.saberBlurShader; } fx.mKillTime = trailDur; fx.mSetFlags = FX_USE_ALPHA; } // New muzzle VectorCopy( rgb1, fx.mVerts[0].rgb ); fx.mVerts[0].alpha = 255.0f; fx.mVerts[0].ST[0] = 0.0f; fx.mVerts[0].ST[1] = 1.0f; fx.mVerts[0].destST[0] = 1.0f; fx.mVerts[0].destST[1] = 1.0f; // new tip VectorCopy( rgb1, fx.mVerts[1].rgb ); fx.mVerts[1].alpha = 255.0f; fx.mVerts[1].ST[0] = 0.0f; fx.mVerts[1].ST[1] = 0.0f; fx.mVerts[1].destST[0] = 1.0f; fx.mVerts[1].destST[1] = 0.0f; // old tip VectorCopy( rgb1, fx.mVerts[2].rgb ); fx.mVerts[2].alpha = 255.0f; fx.mVerts[2].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want fx.mVerts[2].ST[1] = 0.0f; fx.mVerts[2].destST[0] = 1.0f + fx.mVerts[2].ST[0]; fx.mVerts[2].destST[1] = 0.0f; // old muzzle VectorCopy( rgb1, fx.mVerts[3].rgb ); fx.mVerts[3].alpha = 255.0f; fx.mVerts[3].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want fx.mVerts[3].ST[1] = 1.0f; fx.mVerts[3].destST[0] = 1.0f + fx.mVerts[2].ST[0]; fx.mVerts[3].destST[1] = 1.0f; if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) { trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue); //don't need to do this every frame.. but.. if (BG_SaberInAttack(cent->currentState.saberMove) ||BG_SuperBreakWinAnim(cent->currentState.torsoAnim)) { //in attack, strong trail fx.mKillTime = 300; } else { //faded trail fx.mKillTime = 40; } fx.mShader = 2; //2 is always refractive shader fx.mSetFlags = FX_USE_ALPHA; } /* else { fx.mShader = cgs.media.saberBlurShader; fx.mKillTime = trailDur; fx.mSetFlags = FX_USE_ALPHA; } */ trap_FX_AddPrimitive(&fx); } } } } // we must always do this, even if we aren't active..otherwise we won't know where to pick up from VectorCopy( org_, saberTrail->base ); VectorMA( end, 3.0f, axis_[0], saberTrail->tip ); saberTrail->lastTime = cg.time; } JustDoIt: if (dontDraw) { return; } if ( (client->saber[saberNum].saberFlags2&SFL2_NO_BLADE) ) {//don't actually draw the blade at all if ( client->saber[saberNum].numBlades < 3 && !(client->saber[saberNum].saberFlags2&SFL2_NO_DLIGHT) ) {//hmm, but still add the dlight CG_DoSaberLight( &client->saber[saberNum] ); } return; } // Pass in the renderfx flags attached to the saber weapon model...this is done so that saber glows // will get rendered properly in a mirror...not sure if this is necessary?? //CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius, // scolor, renderfx, (qboolean)(saberNum==0&&bladeNum==0) ); CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius, scolor, renderfx, (qboolean)(client->saber[saberNum].numBlades < 3 && !(client->saber[saberNum].saberFlags2&SFL2_NO_DLIGHT)) ); } int CG_IsMindTricked(int trickIndex1, int trickIndex2, int trickIndex3, int trickIndex4, int client) { int checkIn; int sub = 0; if (cg_entities[client].currentState.forcePowersActive & (1 << FP_SEE)) { return 0; } if (client > 47) { checkIn = trickIndex4; sub = 48; } else if (client > 31) { checkIn = trickIndex3; sub = 32; } else if (client > 15) { checkIn = trickIndex2; sub = 16; } else { checkIn = trickIndex1; } if (checkIn & (1 << (client-sub))) { return 1; } return 0; } #define SPEED_TRAIL_DISTANCE 6 void CG_DrawPlayerSphere(centity_t *cent, vec3_t origin, float scale, int shader) { refEntity_t ent; vec3_t ang; float vLen; vec3_t viewDir; // Don't draw the shield when the player is dead. if (cent->currentState.eFlags & EF_DEAD) { return; } memset( &ent, 0, sizeof( ent ) ); VectorCopy( origin, ent.origin ); ent.origin[2] += 9.0; VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); vLen = VectorLength(ent.axis[0]); if (vLen <= 0.1f) { // Entity is right on vieworg. quit. return; } VectorCopy(ent.axis[0], viewDir); VectorInverse(viewDir); VectorNormalize(viewDir); vectoangles(ent.axis[0], ang); ang[ROLL] += 180.0f; ang[PITCH] += 180.0f; AnglesToAxis(ang, ent.axis); VectorScale(ent.axis[0], scale, ent.axis[0]); VectorScale(ent.axis[1], scale, ent.axis[1]); VectorScale(ent.axis[2], scale, ent.axis[2]); ent.nonNormalizedAxes = qtrue; ent.hModel = cgs.media.halfShieldModel; ent.customShader = shader; trap_R_AddRefEntityToScene( &ent ); if (!cg.renderingThirdPerson && cent->currentState.number == cg.predictedPlayerState.clientNum) { //don't do the rest then return; } if (!cg_renderToTextureFX.integer) { return; } ang[PITCH] -= 180.0f; AnglesToAxis(ang, ent.axis); VectorScale(ent.axis[0], scale*0.5f, ent.axis[0]); VectorScale(ent.axis[1], scale*0.5f, ent.axis[1]); VectorScale(ent.axis[2], scale*0.5f, ent.axis[2]); ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); if (shader == cgs.media.invulnerabilityShader) { //ok, ok, this is a little hacky. sorry! ent.shaderRGBA[0] = 0; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 0; ent.shaderRGBA[3] = 100; } else if (shader == cgs.media.ysalimariShader) { ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 0; ent.shaderRGBA[3] = 100; } else if (shader == cgs.media.endarkenmentShader) { ent.shaderRGBA[0] = 100; ent.shaderRGBA[1] = 0; ent.shaderRGBA[2] = 0; ent.shaderRGBA[3] = 20; } else if (shader == cgs.media.enlightenmentShader) { ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; ent.shaderRGBA[3] = 20; } else { //ysal red/blue, boon ent.shaderRGBA[0] = 255.0f; ent.shaderRGBA[1] = 255.0f; ent.shaderRGBA[2] = 255.0f; ent.shaderRGBA[3] = 20; } ent.radius = 256; VectorMA(ent.origin, 40.0f, viewDir, ent.origin); ent.customShader = trap_R_RegisterShader("effects/refract_2"); trap_R_AddRefEntityToScene( &ent ); } void CG_AddLightningBeam(vec3_t start, vec3_t end) { vec3_t dir, chaos, c1, c2, v1, v2; float len, s1, s2, s3; addbezierArgStruct_t b; VectorCopy(start, b.start); VectorCopy(end, b.end); VectorSubtract( b.end, b.start, dir ); len = VectorNormalize( dir ); // Get the base control points, we'll work from there VectorMA( b.start, 0.3333f * len, dir, c1 ); VectorMA( b.start, 0.6666f * len, dir, c2 ); // get some chaos values that really aren't very chaotic :) s1 = sin( cg.time * 0.005f ) * 2 + crandom() * 0.2f; s2 = sin( cg.time * 0.001f ); s3 = sin( cg.time * 0.011f ); VectorSet( chaos, len * 0.01f * s1, len * 0.02f * s2, len * 0.04f * (s1 + s2 + s3)); VectorAdd( c1, chaos, c1 ); VectorScale( chaos, 4.0f, v1 ); VectorSet( chaos, -len * 0.02f * s3, len * 0.01f * (s1 * s2), -len * 0.02f * (s1 + s2 * s3)); VectorAdd( c2, chaos, c2 ); VectorScale( chaos, 2.0f, v2 ); VectorSet( chaos, 1.0f, 1.0f, 1.0f ); VectorCopy(c1, b.control1); VectorCopy(vec3_origin, b.control1Vel); VectorCopy(c2, b.control2); VectorCopy(vec3_origin, b.control2Vel); b.size1 = 6.0f; b.size2 = 6.0f; b.sizeParm = 0.0f; b.alpha1 = 0.0f; b.alpha2 = 0.2f; b.alphaParm = 0.5f; /* VectorCopy(WHITE, b.sRGB); VectorCopy(WHITE, b.eRGB); */ b.sRGB[0] = 255; b.sRGB[1] = 255; b.sRGB[2] = 255; VectorCopy(b.sRGB, b.eRGB); b.rgbParm = 0.0f; b.killTime = 50; b.shader = trap_R_RegisterShader( "gfx/misc/electric2" ); b.flags = 0x00000001; //FX_ALPHA_LINEAR trap_FX_AddBezier(&b); } void CG_AddRandomLightning(vec3_t start, vec3_t end) { vec3_t inOrg, outOrg; VectorCopy(start, inOrg); VectorCopy(end, outOrg); if ( rand() & 1 ) { outOrg[0] += Q_irand(0, 24); inOrg[0] += Q_irand(0, 8); } else { outOrg[0] -= Q_irand(0, 24); inOrg[0] -= Q_irand(0, 8); } if ( rand() & 1 ) { outOrg[1] += Q_irand(0, 24); inOrg[1] += Q_irand(0, 8); } else { outOrg[1] -= Q_irand(0, 24); inOrg[1] -= Q_irand(0, 8); } if ( rand() & 1 ) { outOrg[2] += Q_irand(0, 50); inOrg[2] += Q_irand(0, 40); } else { outOrg[2] -= Q_irand(0, 64); inOrg[2] -= Q_irand(0, 40); } CG_AddLightningBeam(inOrg, outOrg); } extern char *forceHolocronModels[]; qboolean CG_ThereIsAMaster(void) { int i = 0; centity_t *cent; while (i < MAX_CLIENTS) { cent = &cg_entities[i]; if (cent && cent->currentState.isJediMaster) { return qtrue; } i++; } return qfalse; } #if 0 void CG_DrawNoForceSphere(centity_t *cent, vec3_t origin, float scale, int shader) { refEntity_t ent; // Don't draw the shield when the player is dead. if (cent->currentState.eFlags & EF_DEAD) { return; } memset( &ent, 0, sizeof( ent ) ); VectorCopy( origin, ent.origin ); ent.origin[2] += 9.0; VectorSubtract(cg.refdef.vieworg, ent.origin, ent.axis[0]); if (VectorNormalize(ent.axis[0]) <= 0.1f) { // Entity is right on vieworg. quit. return; } VectorCopy(cg.refdef.viewaxis[2], ent.axis[2]); CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); VectorScale(ent.axis[0], scale, ent.axis[0]); VectorScale(ent.axis[1], scale, ent.axis[1]); VectorScale(ent.axis[2], -scale, ent.axis[2]); ent.shaderRGBA[3] = (cent->currentState.genericenemyindex - cg.time)/8; ent.renderfx |= RF_RGB_TINT; if (ent.shaderRGBA[3] > 200) { ent.shaderRGBA[3] = 200; } if (ent.shaderRGBA[3] < 1) { ent.shaderRGBA[3] = 1; } ent.shaderRGBA[2] = 0; ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[3]; ent.hModel = cgs.media.halfShieldModel; ent.customShader = shader; trap_R_AddRefEntityToScene( &ent ); } #endif //Checks to see if the model string has a * appended with a custom skin name after. //If so, it terminates the model string correctly, parses the skin name out, and returns //the handle of the registered skin. int CG_HandleAppendedSkin(char *modelName) { char skinName[MAX_QPATH]; char *p; qhandle_t skinID = 0; int i = 0; //see if it has a skin name p = Q_strrchr(modelName, '*'); if (p) { //found a *, we should have a model name before it and a skin name after it. *p = 0; //terminate the modelName string at this point, then go ahead and parse to the next 0 for the skin. p++; while (p && *p) { skinName[i] = *p; i++; p++; } skinName[i] = 0; if (skinName[0]) { //got it, register the skin under the model path. char baseFolder[MAX_QPATH]; strcpy(baseFolder, modelName); p = Q_strrchr(baseFolder, '/'); //go back to the first /, should be the path point if (p) { //got it.. terminate at the slash and register. char *useSkinName; *p = 0; if (strchr(skinName, '|')) {//three part skin useSkinName = va("%s/|%s", baseFolder, skinName); } else { useSkinName = va("%s/model_%s.skin", baseFolder, skinName); } skinID = trap_R_RegisterSkin(useSkinName); } } } return skinID; } //Create a temporary ghoul2 instance and get the gla name so we can try loading animation data and sounds. #include "../namespace_begin.h" void BG_GetVehicleModelName(char *modelname); void BG_GetVehicleSkinName(char *skinname); #include "../namespace_end.h" void CG_CacheG2AnimInfo(char *modelName) { void *g2 = NULL; char *slash; char useModel[MAX_QPATH]; char useSkin[MAX_QPATH]; int animIndex; strcpy(useModel, modelName); strcpy(useSkin, modelName); if (modelName[0] == '$') { //it's a vehicle name actually, let's precache the whole vehicle BG_GetVehicleModelName(useModel); BG_GetVehicleSkinName(useSkin); if ( useSkin[0] ) { //use a custom skin trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", useModel, useSkin)); } else { trap_R_RegisterSkin(va("models/players/%s/model_default.skin", useModel)); } strcpy(useModel, va("models/players/%s/model.glm", useModel)); } trap_G2API_InitGhoul2Model(&g2, useModel, 0, 0, 0, 0, 0); if (g2) { char GLAName[MAX_QPATH]; char originalModelName[MAX_QPATH]; animIndex = -1; GLAName[0] = 0; trap_G2API_GetGLAName(g2, 0, GLAName); strcpy(originalModelName, useModel); slash = Q_strrchr( GLAName, '/' ); if ( slash ) { strcpy(slash, "/animation.cfg"); animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); } if (animIndex != -1) { slash = Q_strrchr( originalModelName, '/' ); if ( slash ) { slash++; *slash = 0; } BG_ParseAnimationEvtFile(originalModelName, animIndex, bgNumAnimEvents); } //Now free the temp instance trap_G2API_CleanGhoul2Models(&g2); } } static void CG_RegisterVehicleAssets( Vehicle_t *pVeh ) { /* if ( pVeh->m_pVehicleInfo->exhaustFX ) { pVeh->m_pVehicleInfo->iExhaustFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->exhaustFX ); } if ( pVeh->m_pVehicleInfo->trailFX ) { pVeh->m_pVehicleInfo->iTrailFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->trailFX ); } if ( pVeh->m_pVehicleInfo->impactFX ) { pVeh->m_pVehicleInfo->iImpactFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->impactFX ); } if ( pVeh->m_pVehicleInfo->explodeFX ) { pVeh->m_pVehicleInfo->iExplodeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->explodeFX ); } if ( pVeh->m_pVehicleInfo->wakeFX ) { pVeh->m_pVehicleInfo->iWakeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wakeFX ); } if ( pVeh->m_pVehicleInfo->dmgFX ) { pVeh->m_pVehicleInfo->iDmgFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->dmgFX ); } if ( pVeh->m_pVehicleInfo->wpn1FX ) { pVeh->m_pVehicleInfo->iWpn1FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FX ); } if ( pVeh->m_pVehicleInfo->wpn2FX ) { pVeh->m_pVehicleInfo->iWpn2FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FX ); } if ( pVeh->m_pVehicleInfo->wpn1FireFX ) { pVeh->m_pVehicleInfo->iWpn1FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FireFX ); } if ( pVeh->m_pVehicleInfo->wpn2FireFX ) { pVeh->m_pVehicleInfo->iWpn2FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FireFX ); } */ } extern void CG_HandleNPCSounds(centity_t *cent); #include "../namespace_begin.h" extern void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ); extern void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ); extern void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ); extern void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ); #include "../namespace_end.h" extern playerState_t *cgSendPS[MAX_GENTITIES]; void CG_G2AnimEntModelLoad(centity_t *cent) { const char *cModelName = CG_ConfigString( CS_MODELS+cent->currentState.modelindex ); if (!cent->npcClient) { //have not init'd client yet return; } if (cModelName && cModelName[0]) { char modelName[MAX_QPATH]; int skinID; char *slash; strcpy(modelName, cModelName); if (cent->currentState.NPC_class == CLASS_VEHICLE && modelName[0] == '$') { //vehicles pass their veh names over as model names, then we get the model name from the veh type //create a vehicle object clientside for this type char *vehType = &modelName[1]; int iVehIndex = BG_VehicleGetIndex( vehType ); switch( g_vehicleInfo[iVehIndex].type ) { case VH_ANIMAL: // Create the animal (making sure all it's data is initialized). G_CreateAnimalNPC( ¢->m_pVehicle, vehType ); break; case VH_SPEEDER: // Create the speeder (making sure all it's data is initialized). G_CreateSpeederNPC( ¢->m_pVehicle, vehType ); break; case VH_FIGHTER: // Create the fighter (making sure all it's data is initialized). G_CreateFighterNPC( ¢->m_pVehicle, vehType ); break; case VH_WALKER: // Create the walker (making sure all it's data is initialized). G_CreateWalkerNPC( ¢->m_pVehicle, vehType ); break; default: assert(!"vehicle with an unknown type - couldn't create vehicle_t"); break; } //set up my happy prediction hack cent->m_pVehicle->m_vOrientation = &cgSendPS[cent->currentState.number]->vehOrientation[0]; cent->m_pVehicle->m_pParentEntity = (bgEntity_t *)cent; //attach the handles for fx cgame-side CG_RegisterVehicleAssets(cent->m_pVehicle); BG_GetVehicleModelName(modelName); if (cent->m_pVehicle->m_pVehicleInfo->skin && cent->m_pVehicle->m_pVehicleInfo->skin[0]) { //use a custom skin skinID = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelName, cent->m_pVehicle->m_pVehicleInfo->skin)); } else { skinID = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName)); } strcpy(modelName, va("models/players/%s/model.glm", modelName)); //this sound is *only* used for vehicles now cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav" ); } else { skinID = CG_HandleAppendedSkin(modelName); //get the skin if there is one. } if (cent->ghoul2) { //clean it first! trap_G2API_CleanGhoul2Models(¢->ghoul2); } trap_G2API_InitGhoul2Model(¢->ghoul2, modelName, 0, skinID, 0, 0, 0); if (cent->ghoul2) { char GLAName[MAX_QPATH]; char originalModelName[MAX_QPATH]; char *saber; int j = 0; if (cent->currentState.NPC_class == CLASS_VEHICLE && cent->m_pVehicle) { //do special vehicle stuff char strTemp[128]; int i; // Setup the default first bolt i = trap_G2API_AddBolt( cent->ghoul2, 0, "model_root" ); // Setup the droid unit. cent->m_pVehicle->m_iDroidUnitTag = trap_G2API_AddBolt( cent->ghoul2, 0, "*droidunit" ); // Setup the Exhausts. for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) { Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 ); cent->m_pVehicle->m_iExhaustTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); } // Setup the Muzzles. for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) { Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 ); cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); if ( cent->m_pVehicle->m_iMuzzleTag[i] == -1 ) {//ergh, try *flash? Com_sprintf( strTemp, 128, "*flash%i", i + 1 ); cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); } } // Setup the Turrets. for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) { if ( cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ) { cent->m_pVehicle->m_iGunnerViewTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ); } else { cent->m_pVehicle->m_iGunnerViewTag[i] = -1; } } } if (cent->currentState.npcSaber1) { saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber1); assert(!saber || !saber[0] || saber[0] == '@'); //valid saber names should always start with '@' for NPCs if (saber && saber[0]) { saber++; //skip over the @ WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 0, saber); } } if (cent->currentState.npcSaber2) { saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber2); assert(!saber || !saber[0] || saber[0] == '@'); //valid saber names should always start with '@' for NPCs if (saber && saber[0]) { saber++; //skip over the @ WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 1, saber); } } // If this is a not vehicle, give it saber stuff... if ( cent->currentState.NPC_class != CLASS_VEHICLE ) { while (j < MAX_SABERS) { if (cent->npcClient->saber[j].model[0]) { if (cent->npcClient->ghoul2Weapons[j]) { //free the old instance(s) trap_G2API_CleanGhoul2Models(¢->npcClient->ghoul2Weapons[j]); cent->npcClient->ghoul2Weapons[j] = 0; } CG_InitG2SaberData(j, cent->npcClient); } j++; } } trap_G2API_SetSkin(cent->ghoul2, 0, skinID, skinID); cent->localAnimIndex = -1; GLAName[0] = 0; trap_G2API_GetGLAName(cent->ghoul2, 0, GLAName); strcpy(originalModelName, modelName); if (GLAName[0] && !strstr(GLAName, "players/_humanoid/") /*&& !strstr(GLAName, "players/rockettrooper/")*/) { //it doesn't use humanoid anims. slash = Q_strrchr( GLAName, '/' ); if ( slash ) { strcpy(slash, "/animation.cfg"); cent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); } } else { //humanoid index. trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand"); trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand"); //rhand must always be first bolt. lhand always second. Whichever you want the //jetpack bolted to must always be third. trap_G2API_AddBolt(cent->ghoul2, 0, "*chestg"); //claw bolts trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand_cap_r_arm"); trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand_cap_l_arm"); if (strstr(GLAName, "players/rockettrooper/")) { cent->localAnimIndex = 1; } else { cent->localAnimIndex = 0; } if (trap_G2API_AddBolt(cent->ghoul2, 0, "*head_top") == -1) { trap_G2API_AddBolt(cent->ghoul2, 0, "ceyebrow"); } trap_G2API_AddBolt(cent->ghoul2, 0, "Motion"); } // If this is a not vehicle... if ( cent->currentState.NPC_class != CLASS_VEHICLE ) { if (trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar") == -1) { //check now to see if we have this bone for setting anims and such cent->noLumbar = qtrue; } if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) { //check now to see if we have this bone for setting anims and such cent->noFace = qtrue; } } else { cent->noLumbar = qtrue; cent->noFace = qtrue; } if (cent->localAnimIndex != -1) { slash = Q_strrchr( originalModelName, '/' ); if ( slash ) { slash++; *slash = 0; } cent->eventAnimIndex = BG_ParseAnimationEvtFile(originalModelName, cent->localAnimIndex, bgNumAnimEvents); } } } trap_S_ShutUp(qtrue); CG_HandleNPCSounds(cent); //handle sound loading here as well. trap_S_ShutUp(qfalse); } //for now this is just gonna create a big explosion on the area of the surface, //because I am lazy. static void CG_CreateSurfaceDebris(centity_t *cent, int surfNum, int fxID, qboolean throwPart) { int lostPartFX = 0; int b = -1; vec3_t v, d; mdxaBone_t boltMatrix; const char *surfName = NULL; if ( surfNum > 0 ) { surfName = bgToggleableSurfaces[surfNum]; } if (!cent->ghoul2) { //oh no return; } //let's add the surface as a bolt so we can get the base point of it if (bgToggleableSurfaceDebris[surfNum] == 3) { //right wing flame b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage"); if ( throwPart && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo ) { lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX; } } else if (bgToggleableSurfaceDebris[surfNum] == 4) { //left wing flame b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage"); if ( throwPart && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo ) { lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX; } } else if (bgToggleableSurfaceDebris[surfNum] == 5) { //right wing flame 2 b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage"); if ( throwPart && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo ) { lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX; } } else if (bgToggleableSurfaceDebris[surfNum] == 6) { //left wing flame 2 b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage"); if ( throwPart && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo ) { lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX; } } else if (bgToggleableSurfaceDebris[surfNum] == 7) { //nose flame b = trap_G2API_AddBolt(cent->ghoul2, 0, "*nosedamage"); if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo ) { lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iNoseFX; } } else if ( surfName ) { b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName); } if (b == -1 || surfNum == -1) { //couldn't find this surface apparently, so play on origin? VectorCopy( cent->lerpOrigin, v ); AngleVectors( cent->lerpAngles, d, NULL, NULL ); VectorNormalize( d ); } else { //now let's get the position and direction of this surface and make a big explosion trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v); BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d); } trap_FX_PlayEffectID(fxID, v, d, -1, -1); if ( throwPart && lostPartFX ) {//throw off a ship part, too vec3_t fxFwd; AngleVectors( cent->lerpAngles, fxFwd, NULL, NULL ); trap_FX_PlayEffectID(lostPartFX, v, fxFwd, -1, -1); } } //for now this is just gonna create a big explosion on the area of the surface, //because I am lazy. static void CG_CreateSurfaceSmoke(centity_t *cent, int shipSurf, int fxID) { int b = -1; vec3_t v, d; mdxaBone_t boltMatrix; const char *surfName = NULL; if (!cent->ghoul2) { //oh no return; } //let's add the surface as a bolt so we can get the base point of it if ( shipSurf == SHIPSURF_FRONT ) { //front flame/smoke surfName = "*nosedamage"; } else if (shipSurf == SHIPSURF_BACK ) { //back flame/smoke surfName = "*exhaust1";//FIXME: random? Some point in-between? } else if (shipSurf == SHIPSURF_RIGHT ) { //right wing flame/smoke surfName = "*r_wingdamage"; } else if (shipSurf == SHIPSURF_LEFT ) { //left wing flame/smoke surfName = "*l_wingdamage"; } else {//unknown surf! return; } b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName); if (b == -1) { //couldn't find this surface apparently return; } //now let's get the position and direction of this surface and make a big explosion trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v); BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d); trap_FX_PlayEffectID(fxID, v, d, -1, -1); } #define SMOOTH_G2ANIM_LERPANGLES qboolean CG_VehicleShouldDrawShields( centity_t *vehCent ) { if ( vehCent->damageTime > cg.time //ship shields currently taking damage && vehCent->currentState.NPC_class == CLASS_VEHICLE && vehCent->m_pVehicle && vehCent->m_pVehicle->m_pVehicleInfo ) { return qtrue; } return qfalse; } /* extern vmCvar_t cg_showVehBounds; extern void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs, int clientNum, int tracemask, void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)); // bg_pmove.c */ qboolean CG_VehicleAttachDroidUnit( centity_t *droidCent, refEntity_t *legs ) { if ( droidCent && droidCent->currentState.owner && droidCent->currentState.clientNum >= MAX_CLIENTS ) {//the only NPCs that can ride a vehicle are droids...??? centity_t *vehCent = &cg_entities[droidCent->currentState.owner]; if ( vehCent && vehCent->m_pVehicle && vehCent->ghoul2 && vehCent->m_pVehicle->m_iDroidUnitTag != -1 ) { mdxaBone_t boltMatrix; vec3_t fwd, rt, tempAng; trap_G2API_GetBoltMatrix(vehCent->ghoul2, 0, vehCent->m_pVehicle->m_iDroidUnitTag, &boltMatrix, vehCent->lerpAngles, vehCent->lerpOrigin, cg.time, cgs.gameModels, vehCent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, droidCent->lerpOrigin); BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, fwd);//WTF??? BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, rt);//WTF??? vectoangles( fwd, droidCent->lerpAngles ); vectoangles( rt, tempAng ); droidCent->lerpAngles[ROLL] = tempAng[PITCH]; return qtrue; } } return qfalse; } void CG_G2Animated( centity_t *cent ) { #ifdef SMOOTH_G2ANIM_LERPANGLES float angSmoothFactor = 0.7f; #endif if (!cent->ghoul2) { //Initialize this g2 anim ent, then return (will start rendering next frame) CG_G2AnimEntModelLoad(cent); cent->npcLocalSurfOff = 0; cent->npcLocalSurfOn = 0; return; } if (cent->npcLocalSurfOff != cent->currentState.surfacesOff || cent->npcLocalSurfOn != cent->currentState.surfacesOn) { //looks like it's time for an update. int i = 0; while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i]) { if (!(cent->npcLocalSurfOff & (1 << i)) && (cent->currentState.surfacesOff & (1 << i))) { //it wasn't off before but it's off now, so reflect this change in the g2 instance. if (bgToggleableSurfaceDebris[i] > 0) { //make some local debris of this thing? //FIXME: throw off the proper model effect, too CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestDestroyed, qtrue); } trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_OFF); } if (!(cent->npcLocalSurfOn & (1 << i)) && (cent->currentState.surfacesOn & (1 << i))) { //same as above, but on instead of off. trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_ON); } i++; } cent->npcLocalSurfOff = cent->currentState.surfacesOff; cent->npcLocalSurfOn = cent->currentState.surfacesOn; } /* if (cent->currentState.weapon && !trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1) && !(cent->currentState.eFlags & EF_DEAD)) { //if the server says we have a weapon and we haven't copied one onto ourselves yet, then do so. trap_G2API_CopySpecificGhoul2Model(g2WeaponInstances[cent->currentState.weapon], 0, cent->ghoul2, 1); if (cent->currentState.weapon == WP_SABER) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); } } */ if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD)) { //he's alive and has a limb missing still, reattach it and reset the weapon CG_ReattachLimb(cent); } if (((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG)) && !cent->localAnimIndex) { vec3_t forcedAngles; VectorClear(forcedAngles); forcedAngles[YAW] = cent->lerpAngles[YAW]; CG_RagDoll(cent, forcedAngles); } #ifdef SMOOTH_G2ANIM_LERPANGLES if ((cent->lerpAngles[YAW] > 0 && cent->smoothYaw < 0) || (cent->lerpAngles[YAW] < 0 && cent->smoothYaw > 0)) { //keep it from snapping around on the threshold cent->smoothYaw = -cent->smoothYaw; } cent->lerpAngles[YAW] = cent->smoothYaw+(cent->lerpAngles[YAW]-cent->smoothYaw)*angSmoothFactor; cent->smoothYaw = cent->lerpAngles[YAW]; #endif //now just render as a player CG_Player(cent); /* if ( cg_showVehBounds.integer ) {//show vehicle bboxes if ( cent->currentState.clientNum >= MAX_CLIENTS && cent->currentState.NPC_class == CLASS_VEHICLE && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo && cent->currentState.clientNum != cg.predictedVehicleState.clientNum ) {//not the predicted vehicle vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; vec3_t absmin, absmax; vec3_t bmins, bmaxs; float *old = cent->m_pVehicle->m_vOrientation; cent->m_pVehicle->m_vOrientation = ¢->lerpAngles[0]; BG_VehicleAdjustBBoxForOrientation( cent->m_pVehicle, cent->lerpOrigin, bmins, bmaxs, cent->currentState.number, MASK_PLAYERSOLID, NULL ); cent->m_pVehicle->m_vOrientation = old; VectorAdd( cent->lerpOrigin, bmins, absmin ); VectorAdd( cent->lerpOrigin, bmaxs, absmax ); CG_Cube( absmin, absmax, NPCDEBUG_RED, 0.25 ); } } */ } //rww - here ends the majority of my g2animent stuff. //Disabled for now, I'm too lazy to keep it working with all the stuff changing around. #if 0 int cgFPLSState = 0; void CG_ForceFPLSPlayerModel(centity_t *cent, clientInfo_t *ci) { animation_t *anim; if (cg_fpls.integer && !cg.renderingThirdPerson) { int skinHandle; skinHandle = trap_R_RegisterSkin("models/players/kyle/model_fpls2.skin"); trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model)); ci->torsoSkin = skinHandle; trap_G2API_InitGhoul2Model(&ci->ghoul2Model, "models/players/kyle/model.glm", 0, ci->torsoSkin, 0, 0, 0); ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand"); trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg.time, -1, -1); trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg.time); trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg.time); ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand"); //rhand must always be first bolt. lhand always second. Whichever you want the //jetpack bolted to must always be third. trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg"); //claw bolts trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand_cap_r_arm"); trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand_cap_l_arm"); ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top"); if (ci->bolt_head == -1) { ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow"); } ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion"); //We need a lower lumbar bolt for footsteps ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar"); CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, ci->ghoul2Model); } else { CG_RegisterClientModelname(ci, ci->modelName, ci->skinName, ci->teamName, cent->currentState.number); } anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.legsAnim ]; if (anim) { int flags = BONE_ANIM_OVERRIDE_FREEZE; int firstFrame = anim->firstFrame; int setFrame = -1; float animSpeed = 50.0f / anim->frameLerp; if (anim->loopFrames != -1) { flags = BONE_ANIM_OVERRIDE_LOOP; } if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames)) { setFrame = cent->pe.legs.frame; } trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); cent->currentState.legsAnim = 0; } anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.torsoAnim ]; if (anim) { int flags = BONE_ANIM_OVERRIDE_FREEZE; int firstFrame = anim->firstFrame; int setFrame = -1; float animSpeed = 50.0f / anim->frameLerp; if (anim->loopFrames != -1) { flags = BONE_ANIM_OVERRIDE_LOOP; } if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames)) { setFrame = cent->pe.torso.frame; } trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); cent->currentState.torsoAnim = 0; } trap_G2API_CleanGhoul2Models(&(cent->ghoul2)); trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); //Attach the instance to this entity num so we can make use of client-server //shared operations if possible. trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); } #endif //for allocating and freeing npc clientinfo structures. //Remember to free this before game shutdown no matter what //and don't stomp over it, as it is dynamic memory from the //exe. void CG_CreateNPCClient(clientInfo_t **ci) { //trap_TrueMalloc((void **)ci, sizeof(clientInfo_t)); *ci = (clientInfo_t *) BG_Alloc(sizeof(clientInfo_t)); } void CG_DestroyNPCClient(clientInfo_t **ci) { memset(*ci, 0, sizeof(clientInfo_t)); //trap_TrueFree((void **)ci); } static void CG_ForceElectrocution( centity_t *cent, const vec3_t origin, vec3_t tempAngles, qhandle_t shader, qboolean alwaysDo ) { // Undoing for now, at least this code should compile if I ( or anyone else ) decides to work on this effect qboolean found = qfalse; vec3_t fxOrg, fxOrg2, dir; vec3_t rgb; mdxaBone_t boltMatrix; trace_t tr; int bolt=-1; int iter=0; int torsoBolt = -1; int crotchBolt = -1; int elbowLBolt = -1; int elbowRBolt = -1; int handLBolt = -1; int handRBolt = -1; int kneeLBolt = -1; int kneeRBolt = -1; int footLBolt = -1; int footRBolt = -1; VectorSet(rgb, 1, 1, 1); if (cent->localAnimIndex <= 1) { //humanoid torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_arm_elbow"); elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_arm_elbow"); handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand"); handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand"); kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_l_knee"); kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_r_knee"); footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot"); footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot"); } else if (cent->currentState.NPC_class == CLASS_PROTOCOL) { //any others that can use these bolts too? torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_lg"); elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_rg"); handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hand_l"); handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*weapon"); kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_lg"); kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_rg"); footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_lg"); footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_rg"); } // Pick a random start point while (bolt<0) { int test; if (iter>5) { test=iter-5; } else { test=Q_irand(0,6); } switch(test) { case 0: // Right Elbow bolt=elbowRBolt; break; case 1: // Left Hand bolt=handLBolt; break; case 2: // Right hand bolt=handRBolt; break; case 3: // Left Foot bolt=footLBolt; break; case 4: // Right foot bolt=footRBolt; break; case 5: // Torso bolt=torsoBolt; break; case 6: default: // Left Elbow bolt=elbowLBolt; break; } if (++iter==20) break; } if (bolt>=0) { found = trap_G2API_GetBoltMatrix( cent->ghoul2, 0, bolt, &boltMatrix, tempAngles, origin, cg.time, cgs.gameModels, cent->modelScale); } // Make sure that it's safe to even try and get these values out of the Matrix, otherwise the values could be garbage if ( found ) { BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, fxOrg ); if ( random() > 0.5f ) { BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, dir ); } else { BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); } // Add some fudge, makes us not normalized, but that isn't really important dir[0] += crandom() * 0.4f; dir[1] += crandom() * 0.4f; dir[2] += crandom() * 0.4f; } else { // Just use the lerp Origin and a random direction VectorCopy( cent->lerpOrigin, fxOrg ); VectorSet( dir, crandom(), crandom(), crandom() ); // Not normalized, but who cares. switch ( cent->currentState.NPC_class ) { case CLASS_PROBE: fxOrg[2] += 50; break; case CLASS_MARK1: fxOrg[2] += 50; break; case CLASS_ATST: fxOrg[2] += 120; break; default: break; } } VectorMA( fxOrg, random() * 40 + 40, dir, fxOrg2 ); CG_Trace( &tr, fxOrg, NULL, NULL, fxOrg2, -1, CONTENTS_SOLID ); if ( tr.fraction < 1.0f || random() > 0.94f || alwaysDo ) { addElectricityArgStruct_t p; VectorCopy(fxOrg, p.start); VectorCopy(tr.endpos, p.end); p.size1 = 1.5f; p.size2 = 4.0f; p.sizeParm = 0.0f; p.alpha1 = 1.0f; p.alpha2 = 0.5f; p.alphaParm = 0.0f; VectorCopy(rgb, p.sRGB); VectorCopy(rgb, p.eRGB); p.rgbParm = 0.0f; p.chaos = 5.0f; p.killTime = (random() * 50 + 100); p.shader = shader; p.flags = (0x00000001 | 0x00000100 | 0x02000000 | 0x04000000 | 0x01000000); trap_FX_AddElectricity(&p); //In other words: /* FX_AddElectricity( fxOrg, tr.endpos, 1.5f, 4.0f, 0.0f, 1.0f, 0.5f, 0.0f, rgb, rgb, 0.0f, 5.5f, random() * 50 + 100, shader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR | FX_BRANCH | FX_GROW | FX_TAPER ); */ } } void *cg_g2JetpackInstance = NULL; #define JETPACK_MODEL "models/weapons2/jetpack/model.glm" void CG_InitJetpackGhoul2(void) { if (cg_g2JetpackInstance) { assert(!"Tried to init jetpack inst, already init'd"); return; } trap_G2API_InitGhoul2Model(&cg_g2JetpackInstance, JETPACK_MODEL, 0, 0, 0, 0, 0); assert(cg_g2JetpackInstance); //Indicate which bolt on the player we will be attached to //In this case bolt 0 is rhand, 1 is lhand, and 2 is the bolt //for the jetpack (*chestg) trap_G2API_SetBoltInfo(cg_g2JetpackInstance, 0, 2); //Add the bolts jet effects will be played from trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_ljet"); trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_rjet"); } void CG_CleanJetpackGhoul2(void) { if (cg_g2JetpackInstance) { trap_G2API_CleanGhoul2Models(&cg_g2JetpackInstance); cg_g2JetpackInstance = NULL; } } #define RARMBIT (1 << (G2_MODELPART_RARM-10)) #define RHANDBIT (1 << (G2_MODELPART_RHAND-10)) #define WAISTBIT (1 << (G2_MODELPART_WAIST-10)) #if 0 static void CG_VehicleHeatEffect( vec3_t org, centity_t *cent ) { refEntity_t ent; vec3_t ang; float scale; float vLen; float alpha; if (!cg_renderToTextureFX.integer) { return; } scale = 0.1f; alpha = 200.0f; memset( &ent, 0, sizeof( ent ) ); VectorCopy( org, ent.origin ); VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); vLen = VectorLength(ent.axis[0]); if (VectorNormalize(ent.axis[0]) <= 0.1f) { // Entity is right on vieworg. quit. return; } vectoangles(ent.axis[0], ang); AnglesToAxis(ang, ent.axis); //radius must be a power of 2, and is the actual captured texture size ent.radius = 32; VectorScale(ent.axis[0], scale, ent.axis[0]); VectorScale(ent.axis[1], scale, ent.axis[1]); VectorScale(ent.axis[2], -scale, ent.axis[2]); ent.hModel = cgs.media.halfShieldModel; ent.customShader = cgs.media.cloakedShader; //make it partially transparent so it blends with the background ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); ent.shaderRGBA[0] = 255.0f; ent.shaderRGBA[1] = 255.0f; ent.shaderRGBA[2] = 255.0f; ent.shaderRGBA[3] = alpha; trap_R_AddRefEntityToScene( &ent ); } #endif static int lastFlyBySound[MAX_GENTITIES] = {0}; #define FLYBYSOUNDTIME 2000 int cg_lastHyperSpaceEffectTime = 0; static CGAME_INLINE void CG_VehicleEffects(centity_t *cent) { Vehicle_t *pVehNPC; if (cent->currentState.eType != ET_NPC || cent->currentState.NPC_class != CLASS_VEHICLE || !cent->m_pVehicle) { return; } pVehNPC = cent->m_pVehicle; if ( cent->currentState.clientNum == cg.predictedPlayerState.m_iVehicleNum//my vehicle && (cent->currentState.eFlags2&EF2_HYPERSPACE) )//hyperspacing {//in hyperspace! if ( cg.predictedVehicleState.hyperSpaceTime && (cg.time-cg.predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) { if ( !cg_lastHyperSpaceEffectTime || (cg.time - cg_lastHyperSpaceEffectTime) > HYPERSPACE_TIME+500 ) {//can't be from the last time we were in hyperspace, so play the effect! trap_FX_PlayBoltedEffectID( cgs.effects.mHyperspaceStars, cent->lerpOrigin, cent->ghoul2, 0, cent->currentState.number, 0, 0, qtrue ); cg_lastHyperSpaceEffectTime = cg.time; } } } //FLYBY sound if ( cent->currentState.clientNum != cg.predictedPlayerState.m_iVehicleNum && (pVehNPC->m_pVehicleInfo->soundFlyBy||pVehNPC->m_pVehicleInfo->soundFlyBy2) ) {//not my vehicle if ( cent->currentState.speed && cg.predictedPlayerState.speed+cent->currentState.speed > 500 ) {//he's moving and between the two of us, we're moving fast vec3_t diff; VectorSubtract( cent->lerpOrigin, cg.predictedPlayerState.origin, diff ); if ( VectorLength( diff ) < 2048 ) {//close vec3_t myFwd, theirFwd; AngleVectors( cg.predictedPlayerState.viewangles, myFwd, NULL, NULL ); VectorScale( myFwd, cg.predictedPlayerState.speed, myFwd ); AngleVectors( cent->lerpAngles, theirFwd, NULL, NULL ); VectorScale( theirFwd, cent->currentState.speed, theirFwd ); if ( lastFlyBySound[cent->currentState.clientNum]+FLYBYSOUNDTIME < cg.time ) {//okay to do a flyby sound on this vehicle if ( DotProduct( myFwd, theirFwd ) < 500 ) { int flyBySound = 0; if ( pVehNPC->m_pVehicleInfo->soundFlyBy && pVehNPC->m_pVehicleInfo->soundFlyBy2 ) { flyBySound = Q_irand(0,1)?pVehNPC->m_pVehicleInfo->soundFlyBy:pVehNPC->m_pVehicleInfo->soundFlyBy2; } else if ( pVehNPC->m_pVehicleInfo->soundFlyBy ) { flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy; } else //if ( pVehNPC->m_pVehicleInfo->soundFlyBy2 ) { flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy2; } trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, flyBySound ); lastFlyBySound[cent->currentState.clientNum] = cg.time; } } } } } if ( !cent->currentState.speed//was stopped && cent->nextState.speed > 0//now moving forward && cent->m_pVehicle->m_pVehicleInfo->soundEngineStart ) {//engines rev up for the first time trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, cent->m_pVehicle->m_pVehicleInfo->soundEngineStart ); } // Animals don't exude any effects... if ( pVehNPC->m_pVehicleInfo->type != VH_ANIMAL ) { qboolean didFireTrail = qfalse; if (pVehNPC->m_pVehicleInfo->surfDestruction && cent->ghoul2) { //see if anything has been blown off int i = 0; qboolean surfDmg = qfalse; while (i < BG_NUM_TOGGLEABLE_SURFACES) { if (bgToggleableSurfaceDebris[i] > 1) { //this is decidedly a destroyable surface, let's check its status int surfTest = trap_G2API_GetSurfaceRenderStatus(cent->ghoul2, 0, bgToggleableSurfaces[i]); if ( surfTest != -1 && (surfTest&TURN_OFF) ) { //it exists, but it's off... surfDmg = qtrue; //create some flames CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestBurning, qfalse); didFireTrail = qtrue; } } i++; } if (surfDmg) { //if any surface are damaged, neglect exhaust etc effects (so we don't have exhaust trails coming out of invisible surfaces) return; } } if ( !didFireTrail && (cent->currentState.eFlags&EF_DEAD) ) {//spiralling out of control anyway CG_CreateSurfaceDebris(cent, -1, cgs.effects.mShipDestBurning, qfalse); } if ( pVehNPC->m_iLastFXTime <= cg.time ) {//until we attach it, we need to debounce this vec3_t fwd, rt, up; vec3_t flat; float nextFXDelay = 50; VectorSet(flat, 0, cent->lerpAngles[1], cent->lerpAngles[2]); AngleVectors( flat, fwd, rt, up ); if ( cent->currentState.speed > 0 ) {//FIXME: only do this when accelerator is being pressed! (must have a driver?) vec3_t org; qboolean doExhaust = qfalse; VectorMA( cent->lerpOrigin, -16, up, org ); VectorMA( org, -42, fwd, org ); // Play damage effects. //if ( pVehNPC->m_iArmor <= 75 ) if (0) {//hurt trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 ); } else if ( pVehNPC->m_pVehicleInfo->iTrailFX ) {//okay, do normal trail trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, org, fwd, -1, -1 ); } //===================================================================== //EXHAUST FX //===================================================================== //do exhaust if ( (cent->currentState.eFlags&EF_JETPACK_ACTIVE) ) {//cheap way of telling us the vehicle is in "turbo" mode doExhaust = (pVehNPC->m_pVehicleInfo->iTurboFX!=0); } else { doExhaust = (pVehNPC->m_pVehicleInfo->iExhaustFX!=0); } if ( doExhaust && cent->ghoul2 ) { int i; int fx; for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) { // We hit an invalid tag, we quit (they should be created in order so tough luck if not). if ( pVehNPC->m_iExhaustTag[i] == -1 ) { break; } if ( (cent->currentState.brokenLimbs&(1<currentState.brokenLimbs&(1<currentState.eFlags&EF_JETPACK_ACTIVE) //cheap way of telling us the vehicle is in "turbo" mode && pVehNPC->m_pVehicleInfo->iTurboFX )//they have a valid turbo exhaust effect to play { fx = pVehNPC->m_pVehicleInfo->iTurboFX; } else {//play the normal one fx = pVehNPC->m_pVehicleInfo->iExhaustFX; } if (pVehNPC->m_pVehicleInfo->type == VH_FIGHTER) { trap_FX_PlayBoltedEffectID(fx, cent->lerpOrigin, cent->ghoul2, pVehNPC->m_iExhaustTag[i], cent->currentState.number, 0, 0, qtrue); } else { //fixme: bolt these too mdxaBone_t boltMatrix; vec3_t boltOrg, boltDir; trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iExhaustTag[i], &boltMatrix, flat, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); VectorCopy(fwd, boltDir); //fixme? trap_FX_PlayEffectID( fx, boltOrg, boltDir, -1, -1 ); } } } //===================================================================== //WING TRAIL FX //===================================================================== //do trail //FIXME: not in space!!! if ( pVehNPC->m_pVehicleInfo->iTrailFX != 0 && cent->ghoul2 ) { int i; vec3_t boltOrg, boltDir; mdxaBone_t boltMatrix; vec3_t getBoltAngles; VectorCopy(cent->lerpAngles, getBoltAngles); if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER) { //only fighters use pitch/roll in refent axis getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f; } for ( i = 1; i < 5; i++ ) { int trailBolt = trap_G2API_AddBolt(cent->ghoul2, 0, va("*trail%d",i) ); // We hit an invalid tag, we quit (they should be created in order so tough luck if not). if ( trailBolt == -1 ) { break; } trap_G2API_GetBoltMatrix(cent->ghoul2, 0, trailBolt, &boltMatrix, getBoltAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); VectorCopy(fwd, boltDir); //fixme? trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, boltOrg, boltDir, -1, -1 ); } } } //FIXME armor needs to be sent over network { if ( (cent->currentState.eFlags&EF_DEAD) ) {//just plain dead, use flames vec3_t up ={0,0,1}; vec3_t boltOrg; //if ( pVehNPC->m_iDriverTag == -1 ) {//doh! no tag VectorCopy( cent->lerpOrigin, boltOrg ); } //else //{ // mdxaBone_t boltMatrix; // vec3_t getBoltAngles; // VectorCopy(cent->lerpAngles, getBoltAngles); // if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER) // { //only fighters use pitch/roll in refent axis // getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f; // } // trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iDriverTag, &boltMatrix, getBoltAngles, // cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); // BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); //} trap_FX_PlayEffectID( cgs.effects.mShipDestBurning, boltOrg, up, -1, -1 ); } } if ( cent->currentState.brokenLimbs ) { int i; if ( !Q_irand( 0, 5 ) ) { for ( i = SHIPSURF_FRONT; i <= SHIPSURF_LEFT; i++ ) { if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_HEAVY))) ) {//heavy damage, do both effects if ( pVehNPC->m_pVehicleInfo->iInjureFX ) { CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX ); } if ( pVehNPC->m_pVehicleInfo->iDmgFX ) { CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iDmgFX ); } } else if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_LIGHT))) ) {//only light damage if ( pVehNPC->m_pVehicleInfo->iInjureFX ) { CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX ); } } } } } /* if ( pVehNPC->m_iArmor <= 50 ) {//FIXME: use as a proportion of max armor? VectorMA( cent->lerpOrigin, 64, fwd, org ); VectorScale( fwd, -1, fwd ); trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 ); } if ( pVehNPC->m_iArmor <= 0 ) {//FIXME: should use something attached.. but want it to build up over time, so... if ( flrand( 0, cg.time - pVehNPC->m_iDieTime ) < 1000 ) {//flaming! VectorMA( cent->lerpOrigin, flrand(-64, 64), fwd, org ); VectorScale( fwd, -1, fwd ); trap_FX_PlayEffectID( trap_FX_RegisterEffect("ships/fire"), org, fwd, -1, -1 ); nextFXDelay = 50; } } */ pVehNPC->m_iLastFXTime = cg.time + nextFXDelay; } } } /* =============== CG_Player =============== */ #include "../namespace_begin.h" int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); #include "../namespace_end.h" float CG_RadiusForCent( centity_t *cent ) { if ( cent->currentState.eType == ET_NPC ) { if (cent->currentState.NPC_class == CLASS_VEHICLE && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->g2radius) { //has override return cent->m_pVehicle->m_pVehicleInfo->g2radius; } else if ( cent->currentState.g2radius ) { return cent->currentState.g2radius; } } else if ( cent->currentState.g2radius ) { return cent->currentState.g2radius; } return 64.0f; } static float cg_vehThirdPersonAlpha = 1.0f; extern vec3_t cg_crosshairPos; extern vec3_t cameraCurLoc; void CG_CheckThirdPersonAlpha( centity_t *cent, refEntity_t *legs ) { float alpha = 1.0f; int setFlags = 0; if ( cent->m_pVehicle ) {//a vehicle if ( cg.predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum//not mine && cent->m_pVehicle->m_pVehicleInfo && cent->m_pVehicle->m_pVehicleInfo->cameraOverride && cent->m_pVehicle->m_pVehicleInfo->cameraAlpha )//it has alpha {//make sure it's not using any alpha legs->renderfx |= RF_FORCE_ENT_ALPHA; legs->shaderRGBA[3] = 255; return; } } if ( !cg.renderingThirdPerson ) { return; } if ( cg.predictedPlayerState.m_iVehicleNum ) {//in a vehicle if ( cg.predictedPlayerState.m_iVehicleNum == cent->currentState.clientNum ) {//this is my vehicle if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo && cent->m_pVehicle->m_pVehicleInfo->cameraOverride && cent->m_pVehicle->m_pVehicleInfo->cameraAlpha ) {//vehicle has auto third-person alpha on trace_t trace; vec3_t dir2Crosshair, end; VectorSubtract( cg_crosshairPos, cameraCurLoc, dir2Crosshair ); VectorNormalize( dir2Crosshair ); VectorMA( cameraCurLoc, cent->m_pVehicle->m_pVehicleInfo->cameraRange*2.0f, dir2Crosshair, end ); CG_G2Trace( &trace, cameraCurLoc, vec3_origin, vec3_origin, end, ENTITYNUM_NONE, CONTENTS_BODY ); if ( trace.entityNum == cent->currentState.clientNum || trace.entityNum == cg.predictedPlayerState.clientNum) {//hit me or the vehicle I'm in cg_vehThirdPersonAlpha -= 0.1f*cg.frametime/50.0f; if ( cg_vehThirdPersonAlpha < cent->m_pVehicle->m_pVehicleInfo->cameraAlpha ) { cg_vehThirdPersonAlpha = cent->m_pVehicle->m_pVehicleInfo->cameraAlpha; } } else { cg_vehThirdPersonAlpha += 0.1f*cg.frametime/50.0f; if ( cg_vehThirdPersonAlpha > 1.0f ) { cg_vehThirdPersonAlpha = 1.0f; } } alpha = cg_vehThirdPersonAlpha; } else {//use the cvar //reset this cg_vehThirdPersonAlpha = 1.0f; //use the cvar alpha = cg_thirdPersonAlpha.value; } } } else if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum ) {//it's me //reset this cg_vehThirdPersonAlpha = 1.0f; //use the cvar setFlags = RF_FORCE_ENT_ALPHA; alpha = cg_thirdPersonAlpha.value; } if ( alpha < 1.0f ) { legs->renderfx |= setFlags; legs->shaderRGBA[3] = (unsigned char)(alpha * 255.0f); } } void CG_Player( centity_t *cent ) { clientInfo_t *ci; refEntity_t legs; refEntity_t torso; int clientNum; int renderfx; qboolean shadow = qfalse; float shadowPlane = 0; qboolean dead = qfalse; vec3_t rootAngles; float angle; vec3_t angles, dir, elevated, enang, seekorg; int iwantout = 0, successchange = 0; int team; mdxaBone_t boltMatrix, lHandMatrix; int doAlpha = 0; qboolean gotLHandMatrix = qfalse; qboolean g2HasWeapon = qfalse; qboolean drawPlayerSaber = qfalse; qboolean checkDroidShields = qfalse; //first if we are not an npc and we are using an emplaced gun then make sure our //angles are visually capped to the constraints (otherwise it's possible to lerp //a little outside and look kind of twitchy) if (cent->currentState.weapon == WP_EMPLACED_GUN && cent->currentState.otherEntityNum2) { float empYaw; if (BG_EmplacedView(cent->lerpAngles, cg_entities[cent->currentState.otherEntityNum2].currentState.angles, &empYaw, cg_entities[cent->currentState.otherEntityNum2].currentState.origin2[0])) { cent->lerpAngles[YAW] = empYaw; } } if (cent->currentState.iModelScale) { //if the server says we have a custom scale then set it now. cent->modelScale[0] = cent->modelScale[1] = cent->modelScale[2] = cent->currentState.iModelScale/100.0f; if ( cent->currentState.NPC_class != CLASS_VEHICLE ) { if (cent->modelScale[2] && cent->modelScale[2] != 1.0f) { cent->lerpOrigin[2] += 24 * (cent->modelScale[2] - 1); } } } else { VectorClear(cent->modelScale); } if ((cg_smoothClients.integer || cent->currentState.heldByClient) && (cent->currentState.groundEntityNum >= ENTITYNUM_WORLD || cent->currentState.eType == ET_TERRAIN) && !(cent->currentState.eFlags2 & EF2_HYPERSPACE) && cg.predictedPlayerState.m_iVehicleNum != cent->currentState.number) { //always smooth when being thrown vec3_t posDif; float smoothFactor; int k = 0; float fTolerance = 20000.0f; if (cent->currentState.heldByClient) { //smooth the origin more when in this state, because movement is origin-based on server. smoothFactor = 0.2f; } else if ( (cent->currentState.powerups & (1 << PW_SPEED)) || (cent->currentState.forcePowersActive & (1 << FP_RAGE)) ) { //we're moving fast so don't smooth as much smoothFactor = 0.6f; } else if (cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) { //greater smoothing for flying vehicles, since they move so fast fTolerance = 6000000.0f;//500000.0f; //yeah, this is so wrong..but.. smoothFactor = 0.5f; } else { smoothFactor = 0.5f; } if (DistanceSquared(cent->beamEnd,cent->lerpOrigin) > smoothFactor*fTolerance) //10000 { VectorCopy(cent->lerpOrigin, cent->beamEnd); } VectorSubtract(cent->lerpOrigin, cent->beamEnd, posDif); for (k=0;k<3;k++) { cent->beamEnd[k]=(cent->beamEnd[k]+posDif[k]*smoothFactor); cent->lerpOrigin[k]=cent->beamEnd[k]; } } else { VectorCopy(cent->lerpOrigin, cent->beamEnd); } if (cent->currentState.m_iVehicleNum && cent->currentState.NPC_class != CLASS_VEHICLE) { //this player is riding a vehicle centity_t *veh = &cg_entities[cent->currentState.m_iVehicleNum]; cent->lerpAngles[YAW] = veh->lerpAngles[YAW]; //Attach ourself to the vehicle if (veh->m_pVehicle && cent->playerState && veh->playerState && cent->ghoul2 && veh->ghoul2 ) { if ( veh->currentState.owner != cent->currentState.clientNum ) {//FIXME: what about visible passengers? if ( CG_VehicleAttachDroidUnit( cent, &legs ) ) { checkDroidShields = qtrue; } } else if ( veh->currentState.owner != ENTITYNUM_NONE) {//has a pilot...??? vec3_t oldPSOrg; //make sure it has its pilot and parent set veh->m_pVehicle->m_pPilot = (bgEntity_t *)&cg_entities[veh->currentState.owner]; veh->m_pVehicle->m_pParentEntity = (bgEntity_t *)veh; VectorCopy(veh->playerState->origin, oldPSOrg); //update the veh's playerstate org for getting the bolt VectorCopy(veh->lerpOrigin, veh->playerState->origin); VectorCopy(cent->lerpOrigin, cent->playerState->origin); //Now do the attach VectorCopy(veh->lerpAngles, veh->playerState->viewangles); veh->m_pVehicle->m_pVehicleInfo->AttachRiders(veh->m_pVehicle); //copy the "playerstate origin" to the lerpOrigin since that's what we use to display VectorCopy(cent->playerState->origin, cent->lerpOrigin); VectorCopy(oldPSOrg, veh->playerState->origin); } } } // 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 if (cent->currentState.eType != ET_NPC) { clientNum = cent->currentState.clientNum; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { CG_Error( "Bad clientNum on player entity"); } ci = &cgs.clientinfo[ clientNum ]; } else { if (!cent->npcClient) { CG_CreateNPCClient(¢->npcClient); //allocate memory for it if (!cent->npcClient) { assert(0); return; } memset(cent->npcClient, 0, sizeof(clientInfo_t)); cent->npcClient->ghoul2Model = NULL; } assert(cent->npcClient); if (cent->npcClient->ghoul2Model != cent->ghoul2 && cent->ghoul2) { cent->npcClient->ghoul2Model = cent->ghoul2; if (cent->localAnimIndex <= 1) { cent->npcClient->bolt_rhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*r_hand"); cent->npcClient->bolt_lhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*l_hand"); //rhand must always be first bolt. lhand always second. Whichever you want the //jetpack bolted to must always be third. trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*chestg"); //claw bolts trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*r_hand_cap_r_arm"); trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*l_hand_cap_l_arm"); cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*head_top"); if (cent->npcClient->bolt_head == -1) { cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "ceyebrow"); } cent->npcClient->bolt_motion = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "Motion"); cent->npcClient->bolt_llumbar = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "lower_lumbar"); } else { cent->npcClient->bolt_rhand = -1; cent->npcClient->bolt_lhand = -1; cent->npcClient->bolt_head = -1; cent->npcClient->bolt_motion = -1; cent->npcClient->bolt_llumbar = -1; } cent->npcClient->team = TEAM_FREE; cent->npcClient->infoValid = qtrue; } ci = cent->npcClient; } // it is possible to see corpses from disconnected players that may // not have valid clientinfo if ( !ci->infoValid ) { return; } // Add the player to the radar if on the same team and its a team game if (cgs.gametype >= GT_TEAM) { if ( cent->currentState.eType != ET_NPC && cg.snap->ps.clientNum != cent->currentState.number && ci->team == cg.snap->ps.persistant[PERS_TEAM] ) { CG_AddRadarEnt(cent); } } if (cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE) { //add vehicles CG_AddRadarEnt(cent); if ( CG_InFighter() ) {//this is a vehicle, bracket it if ( cg.predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum ) {//don't add the vehicle I'm in... :) CG_AddBracketedEnt(cent); } } } if (!cent->ghoul2) { //not ready yet? #ifdef _DEBUG Com_Printf("WARNING: Client %i has a null ghoul2 instance\n", cent->currentState.number); #endif trap_G2API_ClearAttachedInstance(cent->currentState.number); if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { #ifdef _DEBUG Com_Printf("Clientinfo instance was valid, duplicating for cent\n"); #endif trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); //Attach the instance to this entity num so we can make use of client-server //shared operations if possible. trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) { //check now to see if we have this bone for setting anims and such cent->noFace = qtrue; } cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2); cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex); } return; } if (ci->superSmoothTime) { //do crazy smoothing if (ci->superSmoothTime > cg.time) { //do it trap_G2API_AbsurdSmoothing(cent->ghoul2, qtrue); } else { //turn it off ci->superSmoothTime = 0; trap_G2API_AbsurdSmoothing(cent->ghoul2, qfalse); } } if (cg.predictedPlayerState.pm_type == PM_INTERMISSION) { //don't show all this shit during intermission if ( cent->currentState.eType == ET_NPC && cent->currentState.NPC_class != CLASS_VEHICLE ) {//NPC in intermission } else {//don't render players or vehicles in intermissions, allow other NPCs for scripts return; } } CG_VehicleEffects(cent); if ((cent->currentState.eFlags & EF_JETPACK) && !(cent->currentState.eFlags & EF_DEAD) && cg_g2JetpackInstance) { //should have a jetpack attached //1 is rhand weap, 2 is lhand weap (akimbo sabs), 3 is jetpack if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3)) { trap_G2API_CopySpecificGhoul2Model(cg_g2JetpackInstance, 0, cent->ghoul2, 3); } if (cent->currentState.eFlags & EF_JETPACK_ACTIVE) { mdxaBone_t mat; vec3_t flamePos, flameDir; int n = 0; while (n < 2) { //Get the position/dir of the flame bolt on the jetpack model bolted to the player trap_G2API_GetBoltMatrix(cent->ghoul2, 3, n, &mat, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&mat, ORIGIN, flamePos); if (n == 0) { BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir); VectorMA(flamePos, -9.5f, flameDir, flamePos); BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir); VectorMA(flamePos, -13.5f, flameDir, flamePos); } else { BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir); VectorMA(flamePos, -9.5f, flameDir, flamePos); BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir); VectorMA(flamePos, -13.5f, flameDir, flamePos); } if (cent->currentState.eFlags & EF_JETPACK_FLAMING) { //create effects //FIXME: Just one big effect //Play the effect trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); //Keep the jet fire sound looping trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, trap_S_RegisterSound( "sound/effects/fire_lp" ) ); } else { //just idling //FIXME: Different smaller effect for idle //Play the effect trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); } n++; } trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, trap_S_RegisterSound( "sound/boba/JETHOVER" ) ); } } else if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3)) { //fixme: would be good if this could be done not every frame trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 3); } g2HasWeapon = trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1); if (!g2HasWeapon) { //force a redup of the weapon instance onto the client instance cent->ghoul2weapon = NULL; cent->weapon = 0; } if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD)) { //he's alive and has a limb missing still, reattach it and reset the weapon CG_ReattachLimb(cent); } if (cent->isRagging && !(cent->currentState.eFlags & EF_DEAD) && !(cent->currentState.eFlags & EF_RAG)) { //make sure we don't ragdoll ever while alive unless directly told to with eFlags cent->isRagging = qfalse; trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. } if (cent->ghoul2 && cent->torsoBolt && ((cent->torsoBolt & RARMBIT) || (cent->torsoBolt & RHANDBIT) || (cent->torsoBolt & WAISTBIT)) && g2HasWeapon) { //kill the weapon if the limb holding it is no longer on the model trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); g2HasWeapon = qfalse; } if (!cent->trickAlphaTime || (cg.time - cent->trickAlphaTime) > 1000) { //things got out of sync, perhaps a new client is trying to fill in this slot cent->trickAlpha = 255; cent->trickAlphaTime = cg.time; } if (cent->currentState.eFlags & EF_NODRAW) { //If nodraw, return here return; } else if (cent->currentState.eFlags2 & EF2_SHIP_DEATH) { //died in ship, don't draw, we were "obliterated" return; } //If this client has tricked you. if (CG_IsMindTricked(cent->currentState.trickedentindex, cent->currentState.trickedentindex2, cent->currentState.trickedentindex3, cent->currentState.trickedentindex4, cg.snap->ps.clientNum)) { if (cent->trickAlpha > 1) { cent->trickAlpha -= (cg.time - cent->trickAlphaTime)*0.5; cent->trickAlphaTime = cg.time; if (cent->trickAlpha < 0) { cent->trickAlpha = 0; } doAlpha = 1; } else { doAlpha = 1; cent->trickAlpha = 1; cent->trickAlphaTime = cg.time; iwantout = 1; } } else { if (cent->trickAlpha < 255) { cent->trickAlpha += (cg.time - cent->trickAlphaTime); cent->trickAlphaTime = cg.time; if (cent->trickAlpha > 255) { cent->trickAlpha = 255; } doAlpha = 1; } else { cent->trickAlpha = 255; cent->trickAlphaTime = cg.time; } } // get the player model information renderfx = 0; if ( cent->currentState.number == cg.snap->ps.clientNum) { if (!cg.renderingThirdPerson) { #if 0 if (!cg_fpls.integer || cent->currentState.weapon != WP_SABER) #else if (cent->currentState.weapon != WP_SABER) #endif { renderfx = RF_THIRD_PERSON; // only draw in mirrors } } else { if (cg_cameraMode.integer) { iwantout = 1; // goto minimal_add; // NOTENOTE Temporary return; } } } // Update the player's client entity information regarding weapons. // Explanation: The entitystate has a weapond defined on it. The cliententity does as well. // The cliententity's weapon tells us what the ghoul2 instance on the cliententity has bolted to it. // If the entitystate and cliententity weapons differ, then the state's needs to be copied to the client. // Save the old weapon, to verify that it is or is not the same as the new weapon. // rww - Make sure weapons don't get set BEFORE cent->ghoul2 is initialized or else we'll have no // weapon bolted on if (cent->currentState.saberInFlight) { cent->ghoul2weapon = CG_G2WeaponInstance(cent, WP_SABER); } if (cent->ghoul2 && (cent->currentState.eType != ET_NPC || (cent->currentState.NPC_class != CLASS_VEHICLE&¢->currentState.NPC_class != CLASS_REMOTE&¢->currentState.NPC_class != CLASS_SEEKER)) && //don't add weapon models to NPCs that have no bolt for them! cent->ghoul2weapon != CG_G2WeaponInstance(cent, cent->currentState.weapon) && !(cent->currentState.eFlags & EF_DEAD) && !cent->torsoBolt && cg.snap && (cent->currentState.number != cg.snap->ps.clientNum || (cg.snap->ps.pm_flags & PMF_FOLLOW))) { if (ci->team == TEAM_SPECTATOR) { cent->ghoul2weapon = NULL; cent->weapon = 0; } else { CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2); if (cent->currentState.eType != ET_NPC) { if (cent->weapon == WP_SABER && cent->weapon != cent->currentState.weapon && !cent->currentState.saberHolstered) { //switching away from the saber //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" )); if (ci->saber[0].soundOff && !cent->currentState.saberHolstered) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOff); } if (ci->saber[1].soundOff && ci->saber[1].model[0] && !cent->currentState.saberHolstered) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOff); } } else if (cent->currentState.weapon == WP_SABER && cent->weapon != cent->currentState.weapon && !cent->saberWasInFlight) { //switching to the saber //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); if (ci->saber[0].soundOn) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOn); } if (ci->saber[1].soundOn) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOn); } BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); } } cent->weapon = cent->currentState.weapon; cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon); } } else if ((cent->currentState.eFlags & EF_DEAD) || cent->torsoBolt) { cent->ghoul2weapon = NULL; //be sure to update after respawning/getting limb regrown } if (cent->saberWasInFlight && g2HasWeapon) { cent->saberWasInFlight = qfalse; } memset (&legs, 0, sizeof(legs)); CG_SetGhoul2Info(&legs, cent); VectorCopy(cent->modelScale, legs.modelScale); legs.radius = CG_RadiusForCent( cent ); VectorClear(legs.angles); if (ci->colorOverride[0] != 0.0f || ci->colorOverride[1] != 0.0f || ci->colorOverride[2] != 0.0f) { legs.shaderRGBA[0] = ci->colorOverride[0]*255.0f; legs.shaderRGBA[1] = ci->colorOverride[1]*255.0f; legs.shaderRGBA[2] = ci->colorOverride[2]*255.0f; legs.shaderRGBA[3] = cent->currentState.customRGBA[3]; } else { legs.shaderRGBA[0] = cent->currentState.customRGBA[0]; legs.shaderRGBA[1] = cent->currentState.customRGBA[1]; legs.shaderRGBA[2] = cent->currentState.customRGBA[2]; legs.shaderRGBA[3] = cent->currentState.customRGBA[3]; } // minimal_add: team = ci->team; if (cgs.gametype >= GT_TEAM && cg_drawFriend.integer && cent->currentState.number != cg.snap->ps.clientNum && cent->currentState.eType != ET_NPC) { // If the view is either a spectator or on the same team as this character, show a symbol above their head. if ((cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == team) && !(cent->currentState.eFlags & EF_DEAD)) { if (cgs.gametype == GT_SIEGE) { //check for per-map team shaders if (team == SIEGETEAM_TEAM1) { if (cgSiegeTeam1PlShader) { CG_PlayerFloatSprite( cent, cgSiegeTeam1PlShader); } else { //if there isn't one fallback to default CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); } } else { if (cgSiegeTeam2PlShader) { CG_PlayerFloatSprite( cent, cgSiegeTeam2PlShader); } else { //if there isn't one fallback to default CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader); } } } else { //generic teamplay if (team == TEAM_RED) { CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); } else // if (team == TEAM_BLUE) { CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader); } } } } else if (cgs.gametype == GT_POWERDUEL && cg_drawFriend.integer && cent->currentState.number != cg.snap->ps.clientNum) { if (cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR && cent->currentState.number < MAX_CLIENTS && !(cent->currentState.eFlags & EF_DEAD) && ci && cgs.clientinfo[cg.snap->ps.clientNum].duelTeam == ci->duelTeam) { //ally in powerduel, so draw the icon CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader); } else if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR && cent->currentState.number < MAX_CLIENTS && !(cent->currentState.eFlags & EF_DEAD) && ci->duelTeam == DUELTEAM_DOUBLE) { CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader); } } if (cgs.gametype == GT_JEDIMASTER && cg_drawFriend.integer && cent->currentState.number != cg.snap->ps.clientNum) // Don't show a sprite above a player's own head in 3rd person. { // If the view is either a spectator or on the same team as this character, show a symbol above their head. if ((cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == team) && !(cent->currentState.eFlags & EF_DEAD)) { if (CG_ThereIsAMaster()) { if (!cg.snap->ps.isJediMaster) { if (!cent->currentState.isJediMaster) { CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); } } } } } // add the shadow shadow = CG_PlayerShadow( cent, &shadowPlane ); if ( ((cent->currentState.eFlags & EF_SEEKERDRONE) || cent->currentState.genericenemyindex != -1) && cent->currentState.eType != ET_NPC ) { refEntity_t seeker; memset( &seeker, 0, sizeof(seeker) ); VectorCopy(cent->lerpOrigin, elevated); elevated[2] += 40; VectorCopy( elevated, seeker.lightingOrigin ); seeker.shadowPlane = shadowPlane; seeker.renderfx = 0; //renderfx; //don't show in first person? angle = ((cg.time / 12) & 255) * (M_PI * 2) / 255; dir[0] = cos(angle) * 20; dir[1] = sin(angle) * 20; dir[2] = cos(angle) * 5; VectorAdd(elevated, dir, seeker.origin); VectorCopy(seeker.origin, seekorg); if (cent->currentState.genericenemyindex > MAX_GENTITIES) { float prefig = (cent->currentState.genericenemyindex-cg.time)/80; if (prefig > 55) { prefig = 55; } else if (prefig < 1) { prefig = 1; } elevated[2] -= 55-prefig; angle = ((cg.time / 12) & 255) * (M_PI * 2) / 255; dir[0] = cos(angle) * 20; dir[1] = sin(angle) * 20; dir[2] = cos(angle) * 5; VectorAdd(elevated, dir, seeker.origin); } else if (cent->currentState.genericenemyindex != ENTITYNUM_NONE && cent->currentState.genericenemyindex != -1) { centity_t *enent = &cg_entities[cent->currentState.genericenemyindex]; if (enent) { VectorSubtract(enent->lerpOrigin, seekorg, enang); VectorNormalize(enang); vectoangles(enang, angles); successchange = 1; } } if (!successchange) { angles[0] = sin(angle) * 30; angles[1] = (angle * 180 / M_PI) + 90; if (angles[1] > 360) angles[1] -= 360; angles[2] = 0; } AnglesToAxis( angles, seeker.axis ); seeker.hModel = trap_R_RegisterModel("models/items/remote.md3"); trap_R_AddRefEntityToScene( &seeker ); } // add a water splash if partially in and out of water CG_PlayerSplash( cent ); if ( (cg_shadows.integer == 3 || cg_shadows.integer == 2) && shadow ) { renderfx |= RF_SHADOW_PLANE; } renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all // if we've been hit, display proper fullscreen fx CG_PlayerHitFX(cent); VectorCopy( cent->lerpOrigin, legs.origin ); VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); legs.shadowPlane = shadowPlane; legs.renderfx = renderfx; if (cg_shadows.integer == 2 && (renderfx & RF_THIRD_PERSON)) { //can see own shadow legs.renderfx |= RF_SHADOW_ONLY; } VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all CG_G2PlayerAngles( cent, legs.axis, rootAngles ); CG_G2PlayerHeadAnims( cent ); if ( (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER) && cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... { centity_t *rancor = &cg_entities[cent->currentState.lookTarget]; if ( rancor ) { BG_AttachToRancor( rancor->ghoul2, //ghoul2 info rancor->lerpAngles[YAW], rancor->lerpOrigin, cg.time, cgs.gameModels, rancor->modelScale, (rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG), legs.origin, legs.angles, NULL ); if ( cent->isRagging ) {//hack, ragdoll has you way at bottom of bounding box VectorMA( legs.origin, 32, legs.axis[2], legs.origin ); } VectorCopy( legs.origin, legs.oldorigin ); VectorCopy( legs.origin, legs.lightingOrigin ); VectorCopy( legs.angles, cent->lerpAngles ); VectorCopy( cent->lerpAngles, rootAngles );//??? tempAngles );//tempAngles is needed a lot below VectorCopy( cent->lerpAngles, cent->turAngles ); VectorCopy( legs.origin, cent->lerpOrigin ); } } //This call is mainly just to reconstruct the skeleton. But we'll get the left hand matrix while we're at it. //If we don't reconstruct the skeleton after setting the bone angles, we will get bad bolt points on the model //(e.g. the weapon model bolt will look "lagged") if there's no other GetBoltMatrix call for the rest of the //frame. Yes, this is stupid and needs to be fixed properly. //The current solution is to force it not to reconstruct the skeleton for the first GBM call in G2PlayerAngles. //It works and we end up only reconstructing it once, but it doesn't seem like the best solution. trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); gotLHandMatrix = qtrue; #if 0 if (cg.renderingThirdPerson) { if (cgFPLSState != 0) { CG_ForceFPLSPlayerModel(cent, ci); cgFPLSState = 0; return; } } else if (ci->team == TEAM_SPECTATOR || (cg.snap && (cg.snap->ps.pm_flags & PMF_FOLLOW))) { //don't allow this when spectating if (cgFPLSState != 0) { trap_Cvar_Set("cg_fpls", "0"); cg_fpls.integer = 0; CG_ForceFPLSPlayerModel(cent, ci); cgFPLSState = 0; return; } if (cg_fpls.integer) { trap_Cvar_Set("cg_fpls", "0"); } } else { if (cg_fpls.integer && cent->currentState.weapon == WP_SABER && cg.snap && cent->currentState.number == cg.snap->ps.clientNum) { if (cgFPLSState != cg_fpls.integer) { CG_ForceFPLSPlayerModel(cent, ci); cgFPLSState = cg_fpls.integer; return; } /* mdxaBone_t headMatrix; trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &headMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&headMatrix, ORIGIN, cg.refdef.vieworg); */ } else if (!cg_fpls.integer && cgFPLSState) { if (cgFPLSState != cg_fpls.integer) { CG_ForceFPLSPlayerModel(cent, ci); cgFPLSState = cg_fpls.integer; return; } } } #endif if (cent->currentState.eFlags & EF_DEAD) { dead = qtrue; //rww - since our angles are fixed when we're dead this shouldn't be an issue anyway //we need to render the dying/dead player because we are now spawning the body on respawn instead of death //return; } ScaleModelAxis(&legs); memset( &torso, 0, sizeof(torso) ); //rww - force speed "trail" effect if (!(cent->currentState.powerups & (1 << PW_SPEED)) || doAlpha || !cg_speedTrail.integer) { cent->frame_minus1_refreshed = 0; cent->frame_minus2_refreshed = 0; } if (cent->frame_minus1_refreshed || cent->frame_minus2_refreshed) { vec3_t tDir; int distVelBase; VectorCopy(cent->currentState.pos.trDelta, tDir); distVelBase = SPEED_TRAIL_DISTANCE*(VectorNormalize(tDir)*0.004); if (cent->frame_minus1_refreshed) { refEntity_t reframe_minus1 = legs; reframe_minus1.renderfx |= RF_FORCE_ENT_ALPHA; reframe_minus1.shaderRGBA[0] = legs.shaderRGBA[0]; reframe_minus1.shaderRGBA[1] = legs.shaderRGBA[1]; reframe_minus1.shaderRGBA[2] = legs.shaderRGBA[2]; reframe_minus1.shaderRGBA[3] = 100; //rww - if the client gets a bad framerate we will only receive frame positions //once per frame anyway, so we might end up with speed trails very spread out. //in order to avoid that, we'll get the direction of the last trail from the player //and place the trail refent a set distance from the player location this frame VectorSubtract(cent->frame_minus1, legs.origin, tDir); VectorNormalize(tDir); cent->frame_minus1[0] = legs.origin[0]+tDir[0]*distVelBase; cent->frame_minus1[1] = legs.origin[1]+tDir[1]*distVelBase; cent->frame_minus1[2] = legs.origin[2]+tDir[2]*distVelBase; VectorCopy(cent->frame_minus1, reframe_minus1.origin); //reframe_minus1.customShader = 2; trap_R_AddRefEntityToScene(&reframe_minus1); } if (cent->frame_minus2_refreshed) { refEntity_t reframe_minus2 = legs; reframe_minus2.renderfx |= RF_FORCE_ENT_ALPHA; reframe_minus2.shaderRGBA[0] = legs.shaderRGBA[0]; reframe_minus2.shaderRGBA[1] = legs.shaderRGBA[1]; reframe_minus2.shaderRGBA[2] = legs.shaderRGBA[2]; reframe_minus2.shaderRGBA[3] = 50; //Same as above but do it between trail points instead of the player and first trail entry VectorSubtract(cent->frame_minus2, cent->frame_minus1, tDir); VectorNormalize(tDir); cent->frame_minus2[0] = cent->frame_minus1[0]+tDir[0]*distVelBase; cent->frame_minus2[1] = cent->frame_minus1[1]+tDir[1]*distVelBase; cent->frame_minus2[2] = cent->frame_minus1[2]+tDir[2]*distVelBase; VectorCopy(cent->frame_minus2, reframe_minus2.origin); //reframe_minus2.customShader = 2; trap_R_AddRefEntityToScene(&reframe_minus2); } } //trigger animation-based sounds, done before next lerp frame. CG_TriggerAnimSounds(cent); // get the animation state (after rotation, to allow feet shuffle) CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, &torso.oldframe, &torso.frame, &torso.backlerp ); // add the talk baloon or disconnect icon CG_PlayerSprites( cent ); if (cent->currentState.eFlags & EF_DEAD) { //keep track of death anim frame for when we copy off the bodyqueue ci->frame = cent->pe.torso.frame; } if (cent->currentState.activeForcePass > FORCE_LEVEL_3 && cent->currentState.NPC_class != CLASS_VEHICLE) { vec3_t axis[3]; vec3_t tAng, fAng, fxDir; vec3_t efOrg; int realForceLev = (cent->currentState.activeForcePass - FORCE_LEVEL_3); VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 ); AngleVectors( fAng, fxDir, NULL, NULL ); if ( cent->currentState.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD && Q_irand( 0, 1 ) ) {//alternate back and forth between left and right mdxaBone_t rHandMatrix; trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &rHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); efOrg[0] = rHandMatrix.matrix[0][3]; efOrg[1] = rHandMatrix.matrix[1][3]; efOrg[2] = rHandMatrix.matrix[2][3]; } else { //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); if (!gotLHandMatrix) { trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); gotLHandMatrix = qtrue; } efOrg[0] = lHandMatrix.matrix[0][3]; efOrg[1] = lHandMatrix.matrix[1][3]; efOrg[2] = lHandMatrix.matrix[2][3]; } AnglesToAxis( fAng, axis ); if ( realForceLev > FORCE_LEVEL_2 ) {//arc //trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir ); //trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, -1, -1, -1, -1); } else {//line //trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir ); //trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, -1, -1, -1, -1); } /* if (cent->bolt4 < cg.time) { cent->bolt4 = cg.time + 100; trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/drain.wav") ); } */ } else if ( cent->currentState.activeForcePass && cent->currentState.NPC_class != CLASS_VEHICLE) {//doing the electrocuting vec3_t axis[3]; vec3_t tAng, fAng, fxDir; vec3_t efOrg; VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 ); AngleVectors( fAng, fxDir, NULL, NULL ); //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); if (!gotLHandMatrix) { trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); gotLHandMatrix = qtrue; } efOrg[0] = lHandMatrix.matrix[0][3]; efOrg[1] = lHandMatrix.matrix[1][3]; efOrg[2] = lHandMatrix.matrix[2][3]; AnglesToAxis( fAng, axis ); if ( cent->currentState.activeForcePass > FORCE_LEVEL_2 ) {//arc //trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir ); //trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, -1, -1, -1, -1); } else {//line //trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir ); //trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, -1, -1, -1, -1); } /* if (cent->bolt4 < cg.time) { cent->bolt4 = cg.time + 100; trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/lightning.wav") ); } */ } //fullbody push effect if (cent->currentState.eFlags & EF_BODYPUSH) { CG_ForcePushBodyBlur(cent); } if ( cent->currentState.powerups & (1 << PW_DISINT_4) ) { vec3_t tAng; vec3_t efOrg; //VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 ); VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); if (!gotLHandMatrix) { trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); gotLHandMatrix = qtrue; } efOrg[0] = lHandMatrix.matrix[0][3]; efOrg[1] = lHandMatrix.matrix[1][3]; efOrg[2] = lHandMatrix.matrix[2][3]; if ( (cent->currentState.forcePowersActive & (1 << FP_GRIP)) && (cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum) ) { vec3_t boltDir; vec3_t origBolt; VectorCopy(efOrg, origBolt); BG_GiveMeVectorFromMatrix( &lHandMatrix, NEGATIVE_Y, boltDir ); CG_ForceGripEffect( efOrg ); CG_ForceGripEffect( efOrg ); /* //Render a scaled version of the model's hand with a n337 looking shader { const char *rotateBone; char *limbName; char *limbCapName; vec3_t armAng; refEntity_t regrip_arm; float wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f; //rotateBone = "lradius"; rotateBone = "lradiusX"; limbName = "l_arm"; limbCapName = "l_arm_cap_torso"; if (cent->grip_arm && trap_G2_HaveWeGhoul2Models(cent->grip_arm)) { trap_G2API_CleanGhoul2Models(&(cent->grip_arm)); } memset( ®rip_arm, 0, sizeof(regrip_arm) ); VectorCopy(origBolt, efOrg); //efOrg[2] += 8; efOrg[2] -= 4; VectorCopy(efOrg, regrip_arm.origin); VectorCopy(regrip_arm.origin, regrip_arm.lightingOrigin); //VectorCopy(cent->lerpAngles, armAng); VectorAdd(vec3_origin, rootAngles, armAng); //armAng[ROLL] = -90; armAng[ROLL] = 0; armAng[PITCH] = 0; AnglesToAxis(armAng, regrip_arm.axis); trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, ¢->grip_arm); //remove all other models if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 1)) { //weapon right trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 1); } if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 2)) { //weapon left trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 2); } if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 3)) { //jetpack trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 3); } trap_G2API_SetRootSurface(cent->grip_arm, 0, limbName); trap_G2API_SetNewOrigin(cent->grip_arm, trap_G2API_AddBolt(cent->grip_arm, 0, rotateBone)); trap_G2API_SetSurfaceOnOff(cent->grip_arm, limbCapName, 0); regrip_arm.modelScale[0] = 1;//+(wv*6); regrip_arm.modelScale[1] = 1;//+(wv*6); regrip_arm.modelScale[2] = 1;//+(wv*6); ScaleModelAxis(®rip_arm); regrip_arm.radius = 64; regrip_arm.customShader = trap_R_RegisterShader( "gfx/misc/red_portashield" ); regrip_arm.renderfx |= RF_RGB_TINT; regrip_arm.shaderRGBA[0] = 255 - (wv*900); if (regrip_arm.shaderRGBA[0] < 30) { regrip_arm.shaderRGBA[0] = 30; } if (regrip_arm.shaderRGBA[0] > 255) { regrip_arm.shaderRGBA[0] = 255; } regrip_arm.shaderRGBA[1] = regrip_arm.shaderRGBA[2] = regrip_arm.shaderRGBA[0]; regrip_arm.ghoul2 = cent->grip_arm; trap_R_AddRefEntityToScene( ®rip_arm ); } */ } else if (!(cent->currentState.forcePowersActive & (1 << FP_GRIP))) { //use refractive effect CG_ForcePushBlur( efOrg, cent ); } } else if (cent->bodyFadeTime) { //reset the counter for keeping track of push refraction effect state cent->bodyFadeTime = 0; } if (cent->currentState.weapon == WP_STUN_BATON && cent->currentState.number == cg.snap->ps.clientNum) { trap_S_AddLoopingSound( cent->currentState.number, cg.refdef.vieworg, vec3_origin, trap_S_RegisterSound( "sound/weapons/baton/idle.wav" ) ); } //NOTE: All effects that should be visible during mindtrick should go above here if (iwantout) { goto stillDoSaber; //return; } else if (doAlpha) { legs.renderfx |= RF_FORCE_ENT_ALPHA; legs.shaderRGBA[3] = cent->trickAlpha; if (legs.shaderRGBA[3] < 1) { //don't cancel it out even if it's < 1 legs.shaderRGBA[3] = 1; } } if (cent->teamPowerEffectTime > cg.time) { if (cent->teamPowerType == 3) { //absorb is a somewhat different effect entirely //Guess I'll take care of it where it's always been, just checking these values instead. } else { vec4_t preCol; int preRFX; preRFX = legs.renderfx; legs.renderfx |= RF_RGB_TINT; legs.renderfx |= RF_FORCE_ENT_ALPHA; preCol[0] = legs.shaderRGBA[0]; preCol[1] = legs.shaderRGBA[1]; preCol[2] = legs.shaderRGBA[2]; preCol[3] = legs.shaderRGBA[3]; if (cent->teamPowerType == 1) { //heal legs.shaderRGBA[0] = 0; legs.shaderRGBA[1] = 255; legs.shaderRGBA[2] = 0; } else if (cent->teamPowerType == 0) { //regen legs.shaderRGBA[0] = 0; legs.shaderRGBA[1] = 0; legs.shaderRGBA[2] = 255; } else { //drain legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 0; legs.shaderRGBA[2] = 0; } legs.shaderRGBA[3] = ((cent->teamPowerEffectTime - cg.time)/8); legs.customShader = trap_R_RegisterShader( "powerups/ysalimarishell" ); trap_R_AddRefEntityToScene(&legs); legs.customShader = 0; legs.renderfx = preRFX; legs.shaderRGBA[0] = preCol[0]; legs.shaderRGBA[1] = preCol[1]; legs.shaderRGBA[2] = preCol[2]; legs.shaderRGBA[3] = preCol[3]; } } //If you've tricked this client. if (CG_IsMindTricked(cg.snap->ps.fd.forceMindtrickTargetIndex, cg.snap->ps.fd.forceMindtrickTargetIndex2, cg.snap->ps.fd.forceMindtrickTargetIndex3, cg.snap->ps.fd.forceMindtrickTargetIndex4, cent->currentState.number)) { if (cent->ghoul2) { vec3_t efOrg; vec3_t tAng, fxAng; vec3_t axis[3]; //VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 ); VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, efOrg); BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fxAng); axis[0][0] = boltMatrix.matrix[0][0]; axis[0][1] = boltMatrix.matrix[1][0]; axis[0][2] = boltMatrix.matrix[2][0]; axis[1][0] = boltMatrix.matrix[0][1]; axis[1][1] = boltMatrix.matrix[1][1]; axis[1][2] = boltMatrix.matrix[2][1]; axis[2][0] = boltMatrix.matrix[0][2]; axis[2][1] = boltMatrix.matrix[1][2]; axis[2][2] = boltMatrix.matrix[2][2]; //trap_FX_PlayEntityEffectID(trap_FX_RegisterEffect("force/confusion.efx"), efOrg, axis, cent->boltInfo, cent->currentState.number); trap_FX_PlayEntityEffectID(cgs.effects.mForceConfustionOld, efOrg, axis, -1, -1, -1, -1); } } if (cgs.gametype == GT_HOLOCRON && cent->currentState.time2 && (cg.renderingThirdPerson || cg.snap->ps.clientNum != cent->currentState.number)) { int i = 0; int renderedHolos = 0; refEntity_t holoRef; while (i < NUM_FORCE_POWERS && renderedHolos < 3) { if (cent->currentState.time2 & (1 << i)) { memset( &holoRef, 0, sizeof(holoRef) ); VectorCopy(cent->lerpOrigin, elevated); elevated[2] += 8; VectorCopy( elevated, holoRef.lightingOrigin ); holoRef.shadowPlane = shadowPlane; holoRef.renderfx = 0;//RF_THIRD_PERSON; if (renderedHolos == 0) { angle = ((cg.time / 8) & 255) * (M_PI * 2) / 255; dir[0] = cos(angle) * 20; dir[1] = sin(angle) * 20; dir[2] = cos(angle) * 20; VectorAdd(elevated, dir, holoRef.origin); angles[0] = sin(angle) * 30; angles[1] = (angle * 180 / M_PI) + 90; if (angles[1] > 360) angles[1] -= 360; angles[2] = 0; AnglesToAxis( angles, holoRef.axis ); } else if (renderedHolos == 1) { angle = ((cg.time / 8) & 255) * (M_PI * 2) / 255 + M_PI; if (angle > M_PI * 2) angle -= (float)M_PI * 2; dir[0] = sin(angle) * 20; dir[1] = cos(angle) * 20; dir[2] = cos(angle) * 20; VectorAdd(elevated, dir, holoRef.origin); angles[0] = cos(angle - 0.5 * M_PI) * 30; angles[1] = 360 - (angle * 180 / M_PI); if (angles[1] > 360) angles[1] -= 360; angles[2] = 0; AnglesToAxis( angles, holoRef.axis ); } else { angle = ((cg.time / 6) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; if (angle > M_PI * 2) angle -= (float)M_PI * 2; dir[0] = sin(angle) * 20; dir[1] = cos(angle) * 20; dir[2] = 0; VectorAdd(elevated, dir, holoRef.origin); VectorCopy(dir, holoRef.axis[1]); VectorNormalize(holoRef.axis[1]); VectorSet(holoRef.axis[2], 0, 0, 1); CrossProduct(holoRef.axis[1], holoRef.axis[2], holoRef.axis[0]); } holoRef.modelScale[0] = 0.5; holoRef.modelScale[1] = 0.5; holoRef.modelScale[2] = 0.5; ScaleModelAxis(&holoRef); { float wv; addspriteArgStruct_t fxSArgs; vec3_t holoCenter; holoCenter[0] = holoRef.origin[0] + holoRef.axis[2][0]*18; holoCenter[1] = holoRef.origin[1] + holoRef.axis[2][1]*18; holoCenter[2] = holoRef.origin[2] + holoRef.axis[2][2]*18; wv = sin( cg.time * 0.004f ) * 0.08f + 0.1f; VectorCopy(holoCenter, fxSArgs.origin); VectorClear(fxSArgs.vel); VectorClear(fxSArgs.accel); fxSArgs.scale = wv*60; fxSArgs.dscale = wv*60; fxSArgs.sAlpha = wv*12; fxSArgs.eAlpha = wv*12; fxSArgs.rotation = 0.0f; fxSArgs.bounce = 0.0f; fxSArgs.life = 1.0f; fxSArgs.flags = 0x08000000|0x00000001; if (forcePowerDarkLight[i] == FORCE_DARKSIDE) { //dark fxSArgs.sAlpha *= 3; fxSArgs.eAlpha *= 3; fxSArgs.shader = cgs.media.redSaberGlowShader; trap_FX_AddSprite(&fxSArgs); } else if (forcePowerDarkLight[i] == FORCE_LIGHTSIDE) { //light fxSArgs.sAlpha *= 1.5; fxSArgs.eAlpha *= 1.5; fxSArgs.shader = cgs.media.redSaberGlowShader; trap_FX_AddSprite(&fxSArgs); fxSArgs.shader = cgs.media.greenSaberGlowShader; trap_FX_AddSprite(&fxSArgs); fxSArgs.shader = cgs.media.blueSaberGlowShader; trap_FX_AddSprite(&fxSArgs); } else { //neutral if (i == FP_SABER_OFFENSE || i == FP_SABER_DEFENSE || i == FP_SABERTHROW) { //saber power fxSArgs.sAlpha *= 1.5; fxSArgs.eAlpha *= 1.5; fxSArgs.shader = cgs.media.greenSaberGlowShader; trap_FX_AddSprite(&fxSArgs); } else { fxSArgs.sAlpha *= 0.5; fxSArgs.eAlpha *= 0.5; fxSArgs.shader = cgs.media.greenSaberGlowShader; trap_FX_AddSprite(&fxSArgs); fxSArgs.shader = cgs.media.blueSaberGlowShader; trap_FX_AddSprite(&fxSArgs); } } } holoRef.hModel = trap_R_RegisterModel(forceHolocronModels[i]); trap_R_AddRefEntityToScene( &holoRef ); renderedHolos++; } i++; } } if ((cent->currentState.powerups & (1 << PW_YSALAMIRI)) || (cgs.gametype == GT_CTY && ((cent->currentState.powerups & (1 << PW_REDFLAG)) || (cent->currentState.powerups & (1 << PW_BLUEFLAG)))) ) { if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_REDFLAG))) { CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliredShader ); } else if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_BLUEFLAG))) { CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliblueShader ); } else { CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysalimariShader ); } } if (cent->currentState.powerups & (1 << PW_FORCE_BOON)) { CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.boonShader ); } if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_DARK)) { CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.endarkenmentShader ); } else if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_LIGHT)) { CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.enlightenmentShader ); } if (cent->currentState.eFlags & EF_INVULNERABLE) { CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.0f, cgs.media.invulnerabilityShader ); } stillDoSaber: if ((cent->currentState.eFlags & EF_DEAD) && cent->currentState.weapon == WP_SABER) { //cent->saberLength = 0; BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); drawPlayerSaber = qtrue; } else if (cent->currentState.weapon == WP_SABER && cent->currentState.saberHolstered < 2 ) { if ( (!cent->currentState.saberInFlight //saber not in flight || ci->saber[1].soundLoop) //??? && !(cent->currentState.eFlags & EF_DEAD))//still alive { vec3_t soundSpot; qboolean didFirstSound = qfalse; if (cg.snap->ps.clientNum == cent->currentState.number) { //trap_S_AddLoopingSound( cent->currentState.number, cg.refdef.vieworg, vec3_origin, // trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); VectorCopy(cg.refdef.vieworg, soundSpot); } else { //trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, // trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); VectorCopy(cent->lerpOrigin, soundSpot); } if (ci->saber[0].model[0] && ci->saber[0].soundLoop && !cent->currentState.saberInFlight) { int i = 0; qboolean hasLen = qfalse; while (i < ci->saber[0].numBlades) { if (ci->saber[0].blade[i].length) { hasLen = qtrue; break; } i++; } if (hasLen) { trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin, ci->saber[0].soundLoop ); didFirstSound = qtrue; } } if (ci->saber[1].model[0] && ci->saber[1].soundLoop && (!didFirstSound || ci->saber[0].soundLoop != ci->saber[1].soundLoop)) { int i = 0; qboolean hasLen = qfalse; while (i < ci->saber[1].numBlades) { if (ci->saber[1].blade[i].length) { hasLen = qtrue; break; } i++; } if (hasLen) { trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin, ci->saber[1].soundLoop ); } } } if (iwantout && !cent->currentState.saberInFlight) { if (cent->currentState.eFlags & EF_DEAD) { if (cent->ghoul2 && cent->currentState.saberInFlight && g2HasWeapon) { //special case, kill the saber on a freshly dead player if another source says to. trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); g2HasWeapon = qfalse; } } return; //goto endOfCall; } if (g2HasWeapon && cent->currentState.saberInFlight) { //keep this set, so we don't re-unholster the thing when we get it back, even if it's knocked away. cent->saberWasInFlight = qtrue; } if (cent->currentState.saberInFlight && cent->currentState.saberEntityNum) { centity_t *saberEnt; saberEnt = &cg_entities[cent->currentState.saberEntityNum]; if (/*!cent->bolt4 &&*/ g2HasWeapon || !cent->bolt3 || saberEnt->serverSaberHitIndex != saberEnt->currentState.modelindex/*|| !cent->saberLength*/) { //saber is in flight, do not have it as a standard weapon model qboolean addBolts = qfalse; mdxaBone_t boltMat; if (g2HasWeapon) { //ah well, just stick it over the right hand right now. trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMat, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); BG_GiveMeVectorFromMatrix(&boltMat, ORIGIN, saberEnt->currentState.pos.trBase); trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); g2HasWeapon = qfalse; } //cent->bolt4 = 1; saberEnt->currentState.pos.trTime = cg.time; saberEnt->currentState.apos.trTime = cg.time; VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin); VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles); cent->bolt3 = saberEnt->currentState.apos.trBase[0]; if (!cent->bolt3) { cent->bolt3 = 1; } cent->bolt2 = 0; saberEnt->currentState.bolt2 = 123; if (saberEnt->ghoul2 && saberEnt->serverSaberHitIndex == saberEnt->currentState.modelindex) { // now set up the gun bolt on it addBolts = qtrue; } else { const char *saberModel = CG_ConfigString( CS_MODELS+saberEnt->currentState.modelindex ); saberEnt->serverSaberHitIndex = saberEnt->currentState.modelindex; if (saberEnt->ghoul2) { //clean if we already have one (because server changed model string index) trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2)); saberEnt->ghoul2 = 0; } if (saberModel && saberModel[0]) { trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, saberModel, 0, 0, 0, 0, 0); } else if (ci->saber[0].model[0]) { trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, ci->saber[0].model, 0, 0, 0, 0, 0); } else { trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, "models/weapons2/saber/saber_w.glm", 0, 0, 0, 0, 0); } //trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &saberEnt->ghoul2); if (saberEnt->ghoul2) { addBolts = qtrue; //cent->bolt4 = 2; VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin); VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles); saberEnt->currentState.pos.trTime = cg.time; saberEnt->currentState.apos.trTime = cg.time; } } if (addBolts) { int m = 0; int tagBolt; char *tagName; while (m < ci->saber[0].numBlades) { tagName = va("*blade%i", m+1); tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, tagName); if (tagBolt == -1) { if (m == 0) { //guess this is an 0ldsk3wl saber tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash"); if (tagBolt == -1) { assert(0); } break; } if (tagBolt == -1) { assert(0); break; } } m++; } } } /*else if (cent->bolt4 != 2) { if (saberEnt->ghoul2) { trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash"); cent->bolt4 = 2; } }*/ if (saberEnt && saberEnt->ghoul2 /*&& cent->bolt4 == 2*/) { vec3_t bladeAngles; vec3_t tAng; vec3_t efOrg; float wv; int k = 0; int l = 0; addspriteArgStruct_t fxSArgs; if (!cent->bolt2) { cent->bolt2 = cg.time; } if (cent->bolt3 != 90) { if (cent->bolt3 < 90) { cent->bolt3 += (cg.time - cent->bolt2)*0.5; if (cent->bolt3 > 90) { cent->bolt3 = 90; } } else if (cent->bolt3 > 90) { cent->bolt3 -= (cg.time - cent->bolt2)*0.5; if (cent->bolt3 < 90) { cent->bolt3 = 90; } } } cent->bolt2 = cg.time; saberEnt->currentState.apos.trBase[0] = cent->bolt3; saberEnt->lerpAngles[0] = cent->bolt3; if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123) { //owner is pulling is back if ( !(ci->saber[0].saberFlags&SFL_RETURN_DAMAGE) || cent->currentState.saberHolstered ) { vec3_t owndir; VectorSubtract(saberEnt->lerpOrigin, cent->lerpOrigin, owndir); VectorNormalize(owndir); vectoangles(owndir, owndir); owndir[0] += 90; VectorCopy(owndir, saberEnt->currentState.apos.trBase); VectorCopy(owndir, saberEnt->lerpAngles); VectorClear(saberEnt->currentState.apos.trDelta); } } //We don't actually want to rely entirely on server updates to render the position of the saber, because we actually know generally where //it's going to be before the first position update even gets here, and it needs to start getting rendered the instant the saber model is //removed from the player hand. So we'll just render it manually and let normal rendering for the entity be ignored. if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123) { //tell it that we're a saber and to render the glow around our handle because we're being pulled back saberEnt->bolt3 = 999; } saberEnt->currentState.modelGhoul2 = 1; CG_ManualEntityRender(saberEnt); saberEnt->bolt3 = 0; saberEnt->currentState.modelGhoul2 = 127; VectorCopy(saberEnt->lerpAngles, bladeAngles); bladeAngles[ROLL] = 0; if ( ci->saber[0].numBlades > 1//staff && cent->currentState.saberHolstered == 1 )//extra blades off {//only first blade should be on BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[0], -1, 0); } else { BG_SI_SetDesiredLength(&ci->saber[0], -1, -1); } if ( ci->saber[1].model //dual sabers && cent->currentState.saberHolstered == 1 )//second one off { BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); } else { BG_SI_SetDesiredLength(&ci->saber[1], -1, -1); } //while (l < MAX_SABERS) //Only want to do for the first saber actually, it's the one in flight. while (l < 1) { if (!ci->saber[l].model[0]) { break; } k = 0; while (k < ci->saber[l].numBlades) { if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style l == 0//first saber && cent->currentState.saberHolstered == 1 //extra blades should be off && k > 0 )//this is an extra blade {//extra blades off //don't draw them CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qtrue); } else { CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qfalse); } k++; } if ( ci->saber[l].numBlades > 2 ) {//add a single glow for the saber based on all the blade colors combined CG_DoSaberLight( &ci->saber[l] ); } l++; } //Make the player's hand glow while guiding the saber VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); efOrg[0] = boltMatrix.matrix[0][3]; efOrg[1] = boltMatrix.matrix[1][3]; efOrg[2] = boltMatrix.matrix[2][3]; wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f; //trap_FX_AddSprite( NULL, efOrg, NULL, NULL, 8.0f, 8.0f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 ); VectorCopy(efOrg, fxSArgs.origin); VectorClear(fxSArgs.vel); VectorClear(fxSArgs.accel); fxSArgs.scale = 8.0f; fxSArgs.dscale = 8.0f; fxSArgs.sAlpha = wv; fxSArgs.eAlpha = wv; fxSArgs.rotation = 0.0f; fxSArgs.bounce = 0.0f; fxSArgs.life = 1.0f; fxSArgs.shader = cgs.media.yellowDroppedSaberShader; fxSArgs.flags = 0x08000000; trap_FX_AddSprite(&fxSArgs); } } else { if ( ci->saber[0].numBlades > 1//staff && cent->currentState.saberHolstered == 1 )//extra blades off {//only first blade should be on BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[0], -1, 0); } else { BG_SI_SetDesiredLength(&ci->saber[0], -1, -1); } if ( ci->saber[1].model //dual sabers && cent->currentState.saberHolstered == 1 )//second one off { BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); } else { BG_SI_SetDesiredLength(&ci->saber[1], -1, -1); } } //If the arm the saber is in is broken, turn it off. /* if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM)) { BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); } */ //Leaving right arm on, at least for now. if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM)) { BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); } if (!cent->currentState.saberEntityNum) { BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); //BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); } /* else { BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); } */ drawPlayerSaber = qtrue; } else if (cent->currentState.weapon == WP_SABER) { //cent->saberLength = 0; BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); drawPlayerSaber = qtrue; } else { //cent->saberLength = 0; BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); BG_SI_SetLength(&ci->saber[0], 0); BG_SI_SetLength(&ci->saber[1], 0); } #ifdef _RAG_BOLT_TESTING if (cent->currentState.eFlags & EF_RAG) { CG_TempTestFunction(cent, cent->turAngles); } #endif if (cent->currentState.weapon == WP_SABER) { BG_SI_SetLengthGradual(&ci->saber[0], cg.time); BG_SI_SetLengthGradual(&ci->saber[1], cg.time); } if (drawPlayerSaber) { centity_t *saberEnt; int k = 0; int l = 0; if (!cent->currentState.saberEntityNum) { l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one } else if (!cent->currentState.saberInFlight) { saberEnt = &cg_entities[cent->currentState.saberEntityNum]; if (/*cent->bolt4 && */!g2HasWeapon) { trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, WP_SABER), 0, cent->ghoul2, 1); if (saberEnt && saberEnt->ghoul2) { trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2)); } saberEnt->currentState.modelindex = 0; saberEnt->ghoul2 = NULL; VectorClear(saberEnt->currentState.pos.trBase); } cent->bolt3 = 0; cent->bolt2 = 0; } else { l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one } while (l < MAX_SABERS) { k = 0; if (!ci->saber[l].model[0]) { break; } if (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER) { //vectoangles(legs.axis[0], rootAngles); #if 0 if ( cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... { centity_t *rancor = &cg_entities[cent->currentState.lookTarget]; if ( rancor && rancor->ghoul2 ) { BG_AttachToRancor( rancor->ghoul2, //ghoul2 info rancor->lerpAngles[YAW], rancor->lerpOrigin, cg.time, cgs.gameModels, rancor->modelScale, (rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG), legs.origin, rootAngles, NULL ); } } #else vectoangles(legs.axis[0], rootAngles); #endif } while (k < ci->saber[l].numBlades) { if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style cent->currentState.saberHolstered == 1 //extra blades should be off && k > 0 //this is an extra blade && ci->saber[l].blade[k].length <= 0 )//it's completely off {//extra blades off //don't draw them CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue); } else if ( ci->saber[1].model[0]//we have a second saber && cent->currentState.saberHolstered == 1 //it should be off && l > 0//and this is the second one && ci->saber[l].blade[k].length <= 0 )//it's completely off {//second saber is turned off and this blade is done with turning off CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue); } else { CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qfalse); } k++; } if ( ci->saber[l].numBlades > 2 ) {//add a single glow for the saber based on all the blade colors combined CG_DoSaberLight( &ci->saber[l] ); } l++; } } if (cent->currentState.saberInFlight && !cent->currentState.saberEntityNum) { //reset the length if the saber is knocked away BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); if (g2HasWeapon) { //and remember to kill the bolton model in case we didn't get a thrown saber update first trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); g2HasWeapon = qfalse; } cent->bolt3 = 0; cent->bolt2 = 0; } if (cent->currentState.eFlags & EF_DEAD) { if (cent->ghoul2 && cent->currentState.saberInFlight && g2HasWeapon) { //special case, kill the saber on a freshly dead player if another source says to. trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); g2HasWeapon = qfalse; } } if (iwantout) { return; //goto endOfCall; } if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number) { legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 255; legs.shaderRGBA[2] = 0; legs.renderfx |= RF_MINLIGHT; } if (cg.snap->ps.duelInProgress /*&& cent->currentState.number != cg.snap->ps.clientNum*/) { //I guess go ahead and glow your own client too in a duel if (cent->currentState.number != cg.snap->ps.duelIndex && cent->currentState.number != cg.snap->ps.clientNum) { //everyone not involved in the duel is drawn very dark legs.shaderRGBA[0] /= 5.0f; legs.shaderRGBA[1] /= 5.0f; legs.shaderRGBA[2] /= 5.0f; legs.renderfx |= RF_RGB_TINT; } else { //adjust the glow by how far away you are from your dueling partner centity_t *duelEnt; duelEnt = &cg_entities[cg.snap->ps.duelIndex]; if (duelEnt) { vec3_t vecSub; float subLen = 0; VectorSubtract(duelEnt->lerpOrigin, cg.snap->ps.origin, vecSub); subLen = VectorLength(vecSub); if (subLen < 1) { subLen = 1; } if (subLen > 1020) { subLen = 1020; } { const unsigned char savRGBA[3] = {legs.shaderRGBA[0],legs.shaderRGBA[1],legs.shaderRGBA[2]}; legs.shaderRGBA[0] = max(255-subLen/4,1); legs.shaderRGBA[1] = max(255-subLen/4,1); legs.shaderRGBA[2] = max(255-subLen/4,1); legs.renderfx &= ~RF_RGB_TINT; legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.customShader = cgs.media.forceShell; trap_R_AddRefEntityToScene( &legs ); //draw the shell legs.customShader = 0; //reset to player model legs.shaderRGBA[0] = max(savRGBA[0]-subLen/8,1); legs.shaderRGBA[1] = max(savRGBA[1]-subLen/8,1); legs.shaderRGBA[2] = max(savRGBA[2]-subLen/8,1); } if (subLen <= 1024) { legs.renderfx |= RF_RGB_TINT; } } } } else { if (cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg.snap->ps.clientNum && (!cg.snap->ps.duelInProgress || cg.snap->ps.duelIndex != cent->currentState.number)) { legs.shaderRGBA[0] = 50; legs.shaderRGBA[1] = 50; legs.shaderRGBA[2] = 50; legs.renderfx |= RF_RGB_TINT; } } if (cent->currentState.eFlags & EF_DISINTEGRATION) { if (!cent->dustTrailTime) { cent->dustTrailTime = cg.time; cent->miscTime = legs.frame; } if ((cg.time - cent->dustTrailTime) > 1500) { //avoid rendering the entity after disintegration has finished anyway //goto endOfCall; return; } trap_G2API_SetBoneAnim(legs.ghoul2, 0, "model_root", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1); if (!cent->noLumbar) { trap_G2API_SetBoneAnim(legs.ghoul2, 0, "lower_lumbar", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1); if (cent->localAnimIndex <= 1) { trap_G2API_SetBoneAnim(legs.ghoul2, 0, "Motion", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1); } } CG_Disintegration(cent, &legs); //goto endOfCall; return; } else { cent->dustTrailTime = 0; cent->miscTime = 0; } if (cent->currentState.powerups & (1 << PW_CLOAKED)) { if (!cent->cloaked) { cent->cloaked = qtrue; cent->uncloaking = cg.time + 2000; } } else if (cent->cloaked) { cent->cloaked = qfalse; cent->uncloaking = cg.time + 2000; } if (cent->uncloaking > cg.time) {//in the middle of cloaking if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number) {//just draw him trap_R_AddRefEntityToScene( &legs ); } else { float perc = (float)(cent->uncloaking - cg.time) / 2000.0f; if (( cent->currentState.powerups & ( 1 << PW_CLOAKED ))) {//actually cloaking, so reverse it perc = 1.0f - perc; } if ( perc >= 0.0f && perc <= 1.0f ) { legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.renderfx |= RF_RGB_TINT; legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * perc; legs.shaderRGBA[3] = 0; legs.customShader = cgs.media.cloakedShader; trap_R_AddRefEntityToScene( &legs ); legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255; legs.shaderRGBA[3] = 255 * (1.0f - perc); // let model alpha in legs.customShader = 0; // use regular skin legs.renderfx &= ~RF_RGB_TINT; legs.renderfx |= RF_FORCE_ENT_ALPHA; trap_R_AddRefEntityToScene( &legs ); } } } else if (( cent->currentState.powerups & ( 1 << PW_CLOAKED ))) {//fully cloaked if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number) {//just draw him trap_R_AddRefEntityToScene( &legs ); } else { if (cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum) { /* legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE); legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255; legs.customShader = cgs.media.cloakedShader; legs.nonNormalizedAxes = qtrue; legs.modelScale[0] = 1.02f; legs.modelScale[1] = 1.02f; legs.modelScale[2] = 1.02f; VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] ); VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] ); VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] ); ScaleModelAxis(&legs); trap_R_AddRefEntityToScene( &legs ); legs.modelScale[0] = 0.98f; legs.modelScale[1] = 0.98f; legs.modelScale[2] = 0.98f; VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] ); VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] ); VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] ); ScaleModelAxis(&legs); */ if (cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4 && cg_renderToTextureFX.integer) { trap_R_SetRefractProp(1.0f, 0.0f, qfalse, qfalse); //don't need to do this every frame.. but.. legs.customShader = 2; //crazy "refractive" shader trap_R_AddRefEntityToScene( &legs ); legs.customShader = 0; } else { //stencil buffer's in use, sorry legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE); legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255; legs.customShader = cgs.media.cloakedShader; trap_R_AddRefEntityToScene( &legs ); legs.customShader = 0; } } } } if (!(cent->currentState.powerups & (1 << PW_CLOAKED))) { //don't add the normal model if cloaked CG_CheckThirdPersonAlpha( cent, &legs ); trap_R_AddRefEntityToScene(&legs); } //cent->frame_minus2 = cent->frame_minus1; VectorCopy(cent->frame_minus1, cent->frame_minus2); if (cent->frame_minus1_refreshed) { cent->frame_minus2_refreshed = 1; } //cent->frame_minus1 = legs; VectorCopy(legs.origin, cent->frame_minus1); cent->frame_minus1_refreshed = 1; if (!cent->frame_hold_refreshed && (cent->currentState.powerups & (1 << PW_SPEEDBURST))) { cent->frame_hold_time = cg.time + 254; } if (cent->frame_hold_time >= cg.time) { refEntity_t reframe_hold; if (!cent->frame_hold_refreshed) { //We're taking the ghoul2 instance from the original refent and duplicating it onto our refent alias so that we can then freeze the frame and fade it for the effect if (cent->frame_hold && trap_G2_HaveWeGhoul2Models(cent->frame_hold) && cent->frame_hold != cent->ghoul2) { trap_G2API_CleanGhoul2Models(&(cent->frame_hold)); } reframe_hold = legs; cent->frame_hold_refreshed = 1; reframe_hold.ghoul2 = NULL; trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, ¢->frame_hold); //Set the animation to the current frame and freeze on end //trap_G2API_SetBoneAnim(cent->frame_hold.ghoul2, 0, "model_root", cent->frame_hold.frame, cent->frame_hold.frame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->frame_hold.frame, -1); trap_G2API_SetBoneAnim(cent->frame_hold, 0, "model_root", legs.frame, legs.frame, 0, 1.0f, cg.time, legs.frame, -1); } else { reframe_hold = legs; reframe_hold.ghoul2 = cent->frame_hold; } reframe_hold.renderfx |= RF_FORCE_ENT_ALPHA; reframe_hold.shaderRGBA[3] = (cent->frame_hold_time - cg.time); if (reframe_hold.shaderRGBA[3] > 254) { reframe_hold.shaderRGBA[3] = 254; } if (reframe_hold.shaderRGBA[3] < 1) { reframe_hold.shaderRGBA[3] = 1; } reframe_hold.ghoul2 = cent->frame_hold; trap_R_AddRefEntityToScene(&reframe_hold); } else { cent->frame_hold_refreshed = 0; } // // add the gun / barrel / flash // if (cent->currentState.weapon != WP_EMPLACED_GUN) { CG_AddPlayerWeapon( &legs, NULL, cent, ci->team, rootAngles, qtrue ); } // add powerups floating behind the player CG_PlayerPowerups( cent, &legs ); if ((cent->currentState.forcePowersActive & (1 << FP_RAGE)) && (cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum)) { //legs.customShader = cgs.media.rageShader; legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.renderfx &= ~RF_MINLIGHT; legs.renderfx |= RF_RGB_TINT; legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = legs.shaderRGBA[2] = 0; legs.shaderRGBA[3] = 255; if ( rand() & 1 ) { legs.customShader = cgs.media.electricBodyShader; } else { legs.customShader = cgs.media.electricBody2Shader; } trap_R_AddRefEntityToScene(&legs); } if (!cg.snap->ps.duelInProgress && cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg.snap->ps.clientNum && (!cg.snap->ps.duelInProgress || cg.snap->ps.duelIndex != cent->currentState.number)) { legs.shaderRGBA[0] = 50; legs.shaderRGBA[1] = 50; legs.shaderRGBA[2] = 255; legs.renderfx &= ~RF_RGB_TINT; legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.customShader = cgs.media.forceSightBubble; trap_R_AddRefEntityToScene( &legs ); } if ( CG_VehicleShouldDrawShields( cent ) //vehicle || (checkDroidShields && CG_VehicleShouldDrawShields( &cg_entities[cent->currentState.m_iVehicleNum] )) )//droid in vehicle {//Vehicles have form-fitting shields Vehicle_t *pVeh = cent->m_pVehicle; if ( checkDroidShields ) { pVeh = cg_entities[cent->currentState.m_iVehicleNum].m_pVehicle; } legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 255; legs.shaderRGBA[2] = 255; legs.shaderRGBA[3] = 10.0f+(sin((float)(cg.time/4))*128.0f);//112.0 * ((cent->damageTime - cg.time) / MIN_SHIELD_TIME) + random()*16; legs.renderfx &= ~RF_RGB_TINT; legs.renderfx &= ~RF_FORCE_ENT_ALPHA; if ( pVeh && pVeh->m_pVehicleInfo && pVeh->m_pVehicleInfo->shieldShaderHandle ) {//use the vehicle-specific shader legs.customShader = pVeh->m_pVehicleInfo->shieldShaderHandle; } else { legs.customShader = cgs.media.playerShieldDamage; } trap_R_AddRefEntityToScene( &legs ); } //For now, these two are using the old shield shader. This is just so that you //can tell it apart from the JM/duel shaders, but it's still very obvious. if (cent->currentState.forcePowersActive & (1 << FP_PROTECT)) { //aborb is represented by green.. refEntity_t prot; memcpy(&prot, &legs, sizeof(prot)); prot.shaderRGBA[0] = 0; prot.shaderRGBA[1] = 128; prot.shaderRGBA[2] = 0; prot.shaderRGBA[3] = 254; prot.renderfx &= ~RF_RGB_TINT; prot.renderfx &= ~RF_FORCE_ENT_ALPHA; prot.customShader = cgs.media.protectShader; /* if (!prot.modelScale[0] && !prot.modelScale[1] && !prot.modelScale[2]) { prot.modelScale[0] = prot.modelScale[1] = prot.modelScale[2] = 1.0f; } VectorScale(prot.modelScale, 1.1f, prot.modelScale); prot.origin[2] -= 2.0f; ScaleModelAxis(&prot); */ trap_R_AddRefEntityToScene( &prot ); } //if (cent->currentState.forcePowersActive & (1 << FP_ABSORB)) //Showing only when the power has been active (absorbed something) recently now, instead of always. //AND //always show if it is you with the absorb on if ((cent->currentState.number == cg.predictedPlayerState.clientNum && (cg.predictedPlayerState.fd.forcePowersActive & (1<teamPowerEffectTime > cg.time && cent->teamPowerType == 3)) { //aborb is represented by blue.. legs.shaderRGBA[0] = 0; legs.shaderRGBA[1] = 0; legs.shaderRGBA[2] = 255; legs.shaderRGBA[3] = 254; legs.renderfx &= ~RF_RGB_TINT; legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.customShader = cgs.media.playerShieldDamage; trap_R_AddRefEntityToScene( &legs ); } if (cent->currentState.isJediMaster && cg.snap->ps.clientNum != cent->currentState.number) { legs.shaderRGBA[0] = 100; legs.shaderRGBA[1] = 100; legs.shaderRGBA[2] = 255; legs.renderfx &= ~RF_RGB_TINT; legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.renderfx |= RF_NODEPTH; legs.customShader = cgs.media.forceShell; trap_R_AddRefEntityToScene( &legs ); legs.renderfx &= ~RF_NODEPTH; } if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number && cg_auraShell.integer) { if (cgs.gametype == GT_SIEGE) { // A team game if ( ci->team == TEAM_SPECTATOR || ci->team == TEAM_FREE ) {//yellow legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 255; legs.shaderRGBA[2] = 0; } else if ( ci->team != cgs.clientinfo[cg.snap->ps.clientNum].team ) {//red legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 50; legs.shaderRGBA[2] = 50; } else {//green legs.shaderRGBA[0] = 50; legs.shaderRGBA[1] = 255; legs.shaderRGBA[2] = 50; } } else if (cgs.gametype >= GT_TEAM) { // A team game switch(ci->team) { case TEAM_RED: legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 50; legs.shaderRGBA[2] = 50; break; case TEAM_BLUE: legs.shaderRGBA[0] = 75; legs.shaderRGBA[1] = 75; legs.shaderRGBA[2] = 255; break; default: legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 255; legs.shaderRGBA[2] = 0; break; } } else { // Not a team game legs.shaderRGBA[0] = 255; legs.shaderRGBA[1] = 255; legs.shaderRGBA[2] = 0; } /* if (cg.snap->ps.fd.forcePowerLevel[FP_SEE] <= FORCE_LEVEL_1) { legs.renderfx |= RF_MINLIGHT; } else */ { // See through walls. legs.renderfx |= RF_MINLIGHT | RF_NODEPTH; if (cg.snap->ps.fd.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2) { //only level 2+ can see players through walls legs.renderfx &= ~RF_NODEPTH; } } legs.renderfx &= ~RF_RGB_TINT; legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.customShader = cgs.media.sightShell; trap_R_AddRefEntityToScene( &legs ); } // Electricity //------------------------------------------------ if ( cent->currentState.emplacedOwner > cg.time ) { int dif = cent->currentState.emplacedOwner - cg.time; vec3_t tempAngles; if ( dif > 0 && random() > 0.4f ) { // fade out over the last 500 ms int brightness = 255; if ( dif < 500 ) { brightness = floor((dif - 500.0f) / 500.0f * 255.0f ); } legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.renderfx &= ~RF_MINLIGHT; legs.renderfx |= RF_RGB_TINT; legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = brightness; legs.shaderRGBA[3] = 255; if ( rand() & 1 ) { legs.customShader = cgs.media.electricBodyShader; } else { legs.customShader = cgs.media.electricBody2Shader; } trap_R_AddRefEntityToScene( &legs ); if ( random() > 0.9f ) trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.crackleSound ); } VectorSet(tempAngles, 0, cent->lerpAngles[YAW], 0); CG_ForceElectrocution( cent, legs.origin, tempAngles, cgs.media.boltShader, qfalse ); } if (cent->currentState.powerups & (1 << PW_SHIELDHIT)) { /* legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * 0.5f;//t; legs.shaderRGBA[3] = 255; legs.renderfx &= ~RF_ALPHA_FADE; legs.renderfx |= RF_RGB_TINT; */ legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = Q_irand(1, 255); legs.renderfx &= ~RF_FORCE_ENT_ALPHA; legs.renderfx &= ~RF_MINLIGHT; legs.renderfx &= ~RF_RGB_TINT; legs.customShader = cgs.media.playerShieldDamage; trap_R_AddRefEntityToScene( &legs ); } #if 0 endOfCall: if (cgBoneAnglePostSet.refreshSet) { trap_G2API_SetBoneAngles(cgBoneAnglePostSet.ghoul2, cgBoneAnglePostSet.modelIndex, cgBoneAnglePostSet.boneName, cgBoneAnglePostSet.angles, cgBoneAnglePostSet.flags, cgBoneAnglePostSet.up, cgBoneAnglePostSet.right, cgBoneAnglePostSet.forward, cgBoneAnglePostSet.modelList, cgBoneAnglePostSet.blendTime, cgBoneAnglePostSet.currentTime); cgBoneAnglePostSet.refreshSet = qfalse; } #endif } //===================================================================== /* =============== CG_ResetPlayerEntity A player just came into view or teleported, so reset all animation info =============== */ void CG_ResetPlayerEntity( centity_t *cent ) { clientInfo_t *ci; int i = 0; int j = 0; // cent->errorTime = -99999; // guarantee no error decay added // cent->extrapolated = qfalse; if (cent->currentState.eType == ET_NPC) { if (cent->currentState.NPC_class == CLASS_VEHICLE && cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && cg.predictedPlayerState.m_iVehicleNum && cent->currentState.number == cg.predictedPlayerState.m_iVehicleNum) { //holy hackery, batman! //I don't think this will break anything. But really, do I ever? return; } if (!cent->npcClient) { CG_CreateNPCClient(¢->npcClient); //allocate memory for it if (!cent->npcClient) { assert(0); return; } memset(cent->npcClient, 0, sizeof(clientInfo_t)); cent->npcClient->ghoul2Model = NULL; } ci = cent->npcClient; assert(ci); //just force these guys to be set again, it won't hurt anything if they're //already set. cent->npcLocalSurfOff = 0; cent->npcLocalSurfOn = 0; } else { ci = &cgs.clientinfo[ cent->currentState.clientNum ]; } while (i < MAX_SABERS) { j = 0; while (j < ci->saber[i].numBlades) { ci->saber[i].blade[j].trail.lastTime = -20000; j++; } i++; } ci->facial_blink = -1; ci->facial_frown = 0; ci->facial_aux = 0; ci->superSmoothTime = 0; //reset lerp origin smooth point VectorCopy(cent->lerpOrigin, cent->beamEnd); if (cent->currentState.eType != ET_NPC || !(cent->currentState.eFlags & EF_DEAD)) { CG_ClearLerpFrame( cent, ci, ¢->pe.legs, cent->currentState.legsAnim, qfalse); CG_ClearLerpFrame( cent, ci, ¢->pe.torso, cent->currentState.torsoAnim, qtrue); BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); // VectorCopy( cent->lerpOrigin, cent->rawOrigin ); VectorCopy( cent->lerpAngles, cent->rawAngles ); memset( ¢->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( ¢->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; if (cent->currentState.eType == ET_NPC) { //just start them off at 0 pitch cent->pe.torso.pitchAngle = 0; } if ((cent->ghoul2 == NULL) && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) { trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); cent->weapon = 0; cent->ghoul2weapon = NULL; //Attach the instance to this entity num so we can make use of client-server //shared operations if possible. trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) { //check now to see if we have this bone for setting anims and such cent->noFace = qtrue; } cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2); cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex); //CG_CopyG2WeaponInstance(cent->currentState.weapon, ci->ghoul2Model); //cent->weapon = cent->currentState.weapon; } } //do this to prevent us from making a saber unholster sound the first time we enter the pvs if (cent->currentState.number != cg.predictedPlayerState.clientNum && cent->currentState.weapon == WP_SABER && cent->weapon != cent->currentState.weapon) { cent->weapon = cent->currentState.weapon; if (cent->ghoul2 && ci->ghoul2Model) { CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2); cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon); } if (!cent->currentState.saberHolstered) { //if not holstered set length and desired length for both blades to full right now. BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); i = 0; while (i < MAX_SABERS) { j = 0; while (j < ci->saber[i].numBlades) { ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax; j++; } i++; } } } if ( cg_debugPosition.integer ) { CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); } }