/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* ========================================================================== - SPLITMODELS - ========================================================================== */ // - Adding the View Weapon in split pieces #include "cg_local.h" viewweaponinfo_t vweap; //====================================================================== // Predict WeaponChange //====================================================================== qboolean CG_UseWeapon( int newweapon, qboolean feedback ) { gitem_t *item; if( cg.demoPlaying ) return qfalse; if( newweapon < WEAP_GUNBLADE || newweapon >= WEAP_TOTAL ) return qfalse; item = GS_FindItemByTag( newweapon ); if( !item ) return qfalse; //check if we have the weapon if( !cg.frame.playerState.weaponlist[newweapon-1][0] ) { if( feedback ) { Com_Printf( "Out of item: %s\n", item->pickup_name ); trap_S_StartSound( cg.refdef.vieworg, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxWeaponUpNoAmmo ), cg_volume_effects->value, ATTN_NONE, 0 ); } return qfalse; } //check we have ammo for it if( !cg.frame.playerState.weaponlist[newweapon-1][1] && !cg.frame.playerState.weaponlist[newweapon-1][2] && newweapon != WEAP_GUNBLADE ) { if( feedback ) { Com_Printf( "No ammo for %s\n", item->pickup_name ); trap_S_StartSound( cg.refdef.vieworg, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxWeaponUpNoAmmo ), cg_volume_effects->value, ATTN_NONE, 0 ); } return qfalse; } cg.latched_weapon = newweapon; trap_Cmd_ExecuteText( EXEC_NOW, va("svuse %s", item->pickup_name ) ); return qtrue; } //================= //CG_Cmd_Use_f //================= void CG_Cmd_Use_f( void ) { gitem_t *item; if( cg.demoPlaying ) return; if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM || cg.frame.playerState.pmove.pm_type == PM_DEAD || cg.frame.playerState.pmove.pm_type == PM_SPECTATOR ) return; if( trap_Cmd_Argc() < 2 ) return; item = GS_FindItemByName( trap_Cmd_Args() ); if( !item ) { CG_Printf( "unknown item: %s\n", trap_Cmd_Args() ); return; } if( !(item->flags & ITFLAG_USABLE) ) { CG_Printf( "%s is not usable.\n", item->pickup_name ); return; } if( item->type & IT_WEAPON ) { CG_UseWeapon( item->tag, qtrue ); return; } trap_Cmd_ExecuteText( EXEC_NOW, va("svuse %s", item->pickup_name ) ); } void CG_ChasePrev( void ); void CG_ChaseNext( void ); /* ================= CG_WeapPrev_f ================= */ void CG_WeapPrev_f( void ) { int tag; int selected_weapon; if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM ) { CG_ChasePrev(); return; } if( cg.frame.playerState.pmove.pm_type == PM_DEAD ) return; if( cg.demoPlaying ) return; if( cg.latched_weapon == WEAP_NONE ) { selected_weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM]; } else { selected_weapon = cg.latched_weapon; } if( selected_weapon < WEAP_GUNBLADE || selected_weapon >= WEAP_TOTAL ) selected_weapon = WEAP_GUNBLADE; // don't get stuck to a loop with invalid weapon data tag = selected_weapon; tag--; if( tag < WEAP_GUNBLADE ) tag = WEAP_TOTAL - 1; while( tag != selected_weapon ) { if( CG_UseWeapon( tag, qfalse ) ) return; // successful tag--; if( tag < WEAP_GUNBLADE ) tag = WEAP_TOTAL - 1; } } /* ================= CG_WeapNext_f ================= */ void CG_WeapNext_f( void ) { int tag; int selected_weapon; if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM ) { CG_ChaseNext(); return; } if( cg.frame.playerState.pmove.pm_type == PM_DEAD ) return; if( cg.demoPlaying ) return; if( cg.latched_weapon == WEAP_NONE ) { selected_weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM]; } else { selected_weapon = cg.latched_weapon; } if( selected_weapon < WEAP_GUNBLADE || selected_weapon >= WEAP_TOTAL ) selected_weapon = WEAP_GUNBLADE; // don't get stuck to a loop with invalid weapon data tag = selected_weapon; tag++; if( tag >= WEAP_TOTAL ) tag = WEAP_GUNBLADE; while( tag != selected_weapon ) { if( CG_UseWeapon( tag, qfalse ) ) return; // successful tag++; if( tag >= WEAP_TOTAL ) tag = WEAP_GUNBLADE; } } //================= //CG_NoAmmoWeaponChange // // Called only from the ViewWeapon events filter //================= void CG_NoAmmoWeaponChange( void ) { int weaptag; int newweapon = WEAP_GUNBLADE; if( cg.frame.playerState.pmove.pm_type == PM_DEAD ) return; // check if we're already changing weapon if( cg.demoPlaying || cg.frame.playerState.pmove.pm_type == PM_CHASECAM ) { if( cg.changing_weapon ) return; } else { if( cg.latched_weapon != WEAP_NONE ) return; } trap_S_StartSound( cg.refdef.vieworg, 0, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxWeaponUpNoAmmo ), cg_volume_effects->value, ATTN_NONE, 0 ); if( cg.demoPlaying || cg.frame.playerState.pmove.pm_type == PM_CHASECAM ) { cg.changing_weapon = qtrue; return; } // in playerstate, GUNBLADE is 0 for( weaptag = WEAP_TOTAL-1; weaptag > WEAP_NONE; weaptag-- ) // we could ignore the gunblade here, but I prefer the 'if' being visible { if( !cg.frame.playerState.weaponlist[weaptag-1][0] ) // has the weapon? continue; if( !cg.frame.playerState.weaponlist[weaptag-1][1] ) // has strong ammo? continue; if( weaptag != WEAP_GUNBLADE ) { // gunblade is ignored in strong list newweapon = weaptag; goto found; } } for( weaptag = WEAP_TOTAL-1; weaptag > WEAP_NONE; weaptag-- ) { if( !cg.frame.playerState.weaponlist[weaptag-1][0] ) // has the weapon? continue; if( weaptag != WEAP_GUNBLADE ) { if( !cg.frame.playerState.weaponlist[weaptag-1][2] ) // has weak ammo? continue; } newweapon = weaptag; goto found; } found: { CG_UseWeapon( newweapon, qfalse ); } } void CG_CheckWeaponState( void ) { if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM || cg.frame.playerState.pmove.pm_type == PM_DEAD ) { static int last_weapon = WEAP_NONE; if( cg.changing_weapon && last_weapon != cg.frame.playerState.stats[STAT_WEAPON_ITEM] ) cg.changing_weapon = qfalse; last_weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM]; cg.latched_weapon = WEAP_NONE; return; } // check if the weapon change is complete if( cg.latched_weapon == cg.frame.playerState.stats[STAT_WEAPON_ITEM] ) cg.latched_weapon = WEAP_NONE; // check if we don't have the weapon/ammo we're changing to anymore if( cg.latched_weapon != WEAP_NONE ) { if( !cg.frame.playerState.weaponlist[cg.latched_weapon-1][0] || (!cg.frame.playerState.weaponlist[cg.latched_weapon-1][1] && !cg.frame.playerState.weaponlist[cg.latched_weapon-1][2] && cg.latched_weapon != WEAP_GUNBLADE) ) cg.latched_weapon = WEAP_NONE; } } //====================================================================== // ViewWeapon //====================================================================== void CG_AddKickAngles( vec3_t viewangles ); /* ============== CG_CalcGunOffset ============== */ static void CG_CalcGunOffset( vec3_t angles ) { int i; float delta; // gun angles from bobbing if( cg.bobCycle & 1 ) { angles[ROLL] -= cg.xyspeed * cg.bobFracSin * 0.002 * cg_bobRoll->value; angles[YAW] -= cg.xyspeed * cg.bobFracSin * 0.002 * cg_bobYaw->value; } else { angles[ROLL] += cg.xyspeed * cg.bobFracSin * 0.002 * cg_bobRoll->value; angles[YAW] += cg.xyspeed * cg.bobFracSin * 0.002 * cg_bobYaw->value; } angles[PITCH] += cg.xyspeed * cg.bobFracSin * 0.002 * cg_bobPitch->value; // gun angles from delta movement for( i = 0; i < 3; i++ ) { delta = ( cg.player.oldps->viewangles[i] - cg.player.curps->viewangles[i] ) * cg.lerpfrac; if( delta > 180 ) delta -= 360; if( delta < -180 ) delta += 360; clamp( delta, -45, 45 ); if( i == YAW ) angles[ROLL] += 0.001 * delta; angles[i] += 0.002 * delta; } // gun angles from kicks if( !cg_damage_kick->integer ) CG_AddKickAngles( angles ); } /* ============== CG_vWeapStartFallKickEff ============== */ void CG_vWeapStartFallKickEff( int parms ) { int bouncetime; bouncetime = ((parms + 1)*50)+150; vweap.fallEff_Time = cg.time + 2*bouncetime; vweap.fallEff_rebTime = cg.time + bouncetime; } /* =============== CG_vWeapSetPosition Custom gun position =============== */ static void CG_vWeapGetPosition( vec3_t origin, vec3_t axis[3] ) { vec3_t gun_angles; vec3_t forward, right, up; float gunx, guny, gunz; // set up gun position VectorCopy( cg.refdef.vieworg, origin ); VectorCopy( cg.refdef.viewangles, gun_angles ); //offset by client cvars gunx = cg_gunx->value; guny = cg_guny->value; gunz = cg_gunz->value; //move hand to the left/center if( cg.demoPlaying && !cg_demo_truePOV->integer ) { if( hand->integer == 0 ) gunx += cg_handOffset->value; else if( hand->integer == 1 ) gunx -= cg_handOffset->value; } else { if( cgs.clientInfo[cg.chasedNum].hand == 0 ) gunx += cg_handOffset->value; else if( cgs.clientInfo[cg.chasedNum].hand == 1 ) gunx -= cg_handOffset->value; } //Add fallkick effect if( vweap.fallEff_Time > cg.time ) vweap.fallKick += (vweap.fallEff_rebTime*0.001f - cg.time*0.001f); else vweap.fallKick = 0; guny -= vweap.fallKick; //Move the gun AngleVectors( gun_angles, forward, right, up ); VectorMA( origin, gunx, right, origin ); VectorMA( origin, gunz, forward, origin ); VectorMA( origin, guny, up, origin ); //add bobbing CG_CalcGunOffset( gun_angles ); AnglesToAxis( gun_angles, axis ); } /* =============== CG_vWeapUpdateProjectionSource =============== */ static void CG_vWeapUpdateProjectionSource( vec3_t hand_origin, vec3_t hand_axis[3], vec3_t weap_origin, vec3_t weap_axis[3] ) { orientation_t *tag_result = &vweap.projectionSource; orientation_t tag_weapon; VectorCopy( vec3_origin, tag_weapon.origin ); Matrix_Copy( axis_identity, tag_weapon.axis ); // move to tag_weapon CG_MoveToTag( tag_weapon.origin, tag_weapon.axis, hand_origin, hand_axis, weap_origin, weap_axis ); // move to projectionSource tag if( vweap.pweapon.weaponInfo ) { VectorCopy( vec3_origin, tag_result->origin ); Matrix_Copy( axis_identity, tag_result->axis ); CG_MoveToTag( tag_result->origin, tag_result->axis, tag_weapon.origin, tag_weapon.axis, vweap.pweapon.weaponInfo->tag_projectionsource.origin, vweap.pweapon.weaponInfo->tag_projectionsource.axis ); return; } // fallback: copy gun origin and move it front by 16 units and 8 up VectorCopy( tag_weapon.origin, tag_result->origin ); Matrix_Copy( tag_weapon.axis, tag_result->axis ); VectorMA( tag_result->origin, 16, tag_result->axis[0], tag_result->origin ); VectorMA( tag_result->origin, 8, tag_result->axis[2], tag_result->origin ); } /* =============== CG_vWeapSetFrame =============== */ void CG_vWeapSetFrame( void ) { vweap.oldframe = vweap.frame; vweap.frame++; //looping if (vweap.frame > vweap.pweapon.weaponInfo->lastframe[vweap.currentAnim]) { if (vweap.pweapon.weaponInfo->loopingframes[vweap.currentAnim]) { vweap.frame = (vweap.pweapon.weaponInfo->lastframe[vweap.currentAnim] - (vweap.pweapon.weaponInfo->loopingframes[vweap.currentAnim] - 1)); } else if (!vweap.newAnim) vweap.newAnim = VWEAP_STANDBY; } //new animation if ( vweap.newAnim ) { if ( vweap.newAnim == VWEAP_WEAPONUP ) //weapon change { vweap.pweapon.weaponInfo = vweap.newWeaponInfo; vweap.oldframe = vweap.pweapon.weaponInfo->firstframe[vweap.newAnim];//don't lerp } vweap.currentAnim = vweap.newAnim; vweap.frame = vweap.pweapon.weaponInfo->firstframe[vweap.newAnim]; vweap.newAnim = 0; } } /* =============== CG_vWeapUpdateAnimation =============== */ void CG_vWeapUpdateAnimation( void ) { if (cg.time > vweap.nextframetime) { vweap.backlerp = 1.0f; CG_vWeapSetFrame ();//the model can change at this point vweap.prevframetime = cg.time; vweap.nextframetime = cg.time + vweap.pweapon.weaponInfo->frametime[vweap.currentAnim]; } else { vweap.backlerp = 1.0f - ((cg.time - vweap.prevframetime)/(vweap.nextframetime - vweap.prevframetime)); if (vweap.backlerp > 1) vweap.backlerp = 1.0f; else if (vweap.backlerp < 0) vweap.backlerp = 0; } } /* ================== CG_vWeapUpdateState Called each new serverframe ================== */ void CG_vWeapUpdateState( void ) { int i; centity_t *cent; int torsoNewAnim; cent = &cg_entities[cg.chasedNum+1]; // player in POV if( cent->serverFrame != cg.frame.serverFrame ) { vweap.state = NULL; return; } vweap.state = ¢->current; //store the current entity state for later access // no weapon to draw if( !vweap.state->weapon || vweap.state->effects & EF_CORPSE ) { vweap.pweapon.weaponInfo = NULL; return; } //update newweapon info vweap.newWeaponInfo = CG_GetWeaponFromPModelIndex( &cg_entPModels[cg.chasedNum+1], vweap.state->weapon ); //Update animations based on Torso //filter repeated animations coming from game torsoNewAnim = (cent->current.frame>>6 &0x3F) * ((cent->current.frame>>6 &0x3F) != (cent->prev.frame>>6 &0x3F)); if (torsoNewAnim == TORSO_FLIPOUT && vweap.newAnim < VWEAP_WEAPDOWN) vweap.newAnim = VWEAP_WEAPDOWN; //Update based on Events for ( i = 0; i < 2; i++ ) { switch ( vweap.state->events[i] ) { case EV_FALL: CG_vWeapStartFallKickEff( vweap.state->eventParms[i] ); break; case EV_PAIN: //add damage kickangles? break; case EV_JUMP: //add some kind of jumping angles? break; case EV_JUMP_PAD: //add jumpad kickangles? break; case EV_MUZZLEFLASH: //WEAK if( vweap.state->eventParms[i] == FIRE_MODE_WEAK && vweap.newAnim < VWEAP_ATTACK_WEAK ) { vweap.newAnim = VWEAP_ATTACK_WEAK; //activate flash if( vweap.state->weapon != WEAP_GUNBLADE ) { // gunblade knife doesn't flash if (cg_weaponFlashes->integer == 2 && vweap.pweapon.weaponInfo) //vweap.pweapon.flashtime = cg.time + (int)((vweap.pweapon.weaponInfo->frametime[VWEAP_ATTACK_WEAK]/4)*3); vweap.pweapon.flashtime = cg.time + (int)vweap.pweapon.weaponInfo->frametime[VWEAP_ATTACK_WEAK]; } } //STRONG else if( vweap.state->eventParms[i] == FIRE_MODE_STRONG && vweap.newAnim < VWEAP_ATTACK_STRONG ) { vweap.newAnim = VWEAP_ATTACK_STRONG; //activate flash if (cg_weaponFlashes->integer == 2 && vweap.pweapon.weaponInfo) //vweap.pweapon.flashtime = cg.time + (int)((vweap.pweapon.weaponInfo->frametime[VWEAP_ATTACK_STRONG]/4)*3); vweap.pweapon.flashtime = cg.time + (int)vweap.pweapon.weaponInfo->frametime[VWEAP_ATTACK_STRONG]; } //eject brass-debris if (cg_ejectBrass->integer && cg_ejectBrass->integer < 3 && vweap.pweapon.weaponInfo && vweap.active) { vec3_t origin; vec3_t forward, right, up; VectorCopy( cg.refdef.vieworg, origin ); AngleVectors( cg.refdef.viewangles, forward, right, up ); //move it a bit fordward and up VectorMA( origin, 16, forward, origin ); VectorMA( origin, 4, up, origin ); if( cgs.clientInfo[cg.chasedNum].hand == 0) VectorMA( origin, 8, right, origin ); else if( cgs.clientInfo[cg.chasedNum].hand == 1) VectorMA( origin, -4, right, origin ); } break; case EV_DROP: break; case EV_WEAPONUP: vweap.newAnim = VWEAP_WEAPONUP;//is top priority break; } } //remove when there is no hand model, so it tries to reboot if (vweap.pweapon.weaponInfo && !vweap.pweapon.weaponInfo->model[HAND]) vweap.pweapon.weaponInfo = NULL; //Init if (!vweap.pweapon.weaponInfo && vweap.newWeaponInfo) { vweap.newAnim = 0; if (vweap.newWeaponInfo->model[HAND]) { vweap.nextframetime = cg.time;//don't wait for next frame vweap.pweapon.weaponInfo = vweap.newWeaponInfo; if ( !vweap.newAnim ) vweap.currentAnim = VWEAP_STANDBY; else vweap.currentAnim = vweap.newAnim; vweap.frame = vweap.pweapon.weaponInfo->firstframe[vweap.currentAnim]; vweap.oldframe = vweap.frame;//don't lerp } return; } //if showing the wrong weapon, fix. (It happens in chasecam when changing POV) if ((vweap.pweapon.weaponInfo && vweap.newWeaponInfo) && (vweap.pweapon.weaponInfo != vweap.newWeaponInfo) ) { if ( (vweap.newAnim != VWEAP_WEAPONUP) && (vweap.currentAnim != VWEAP_WEAPDOWN) && (vweap.newWeaponInfo->model[HAND]) ) { if (cg_debugWeaponModels->integer) CG_Printf ("fixing wrong viewWeapon\n"); if ( !vweap.currentAnim ) { if ( !vweap.newAnim ) vweap.currentAnim = VWEAP_WEAPONUP; else vweap.currentAnim = vweap.newAnim; } vweap.nextframetime = cg.time;//don't wait for next frame vweap.pweapon.weaponInfo = vweap.newWeaponInfo; vweap.frame = vweap.pweapon.weaponInfo->firstframe[vweap.currentAnim]; vweap.oldframe = vweap.frame;//don't lerp } } } static entity_t gun; // hand model /* ============== CG_CalcViewWeapon ============== */ void CG_CalcViewWeapon( void ) { orientation_t tag; // gun disabled if( !vweap.active || !vweap.state ) return; //remove if player in POV has changed if( vweap.state->number != cg.chasedNum+1 ) vweap.pweapon.weaponInfo = NULL; if( !vweap.pweapon.weaponInfo ) return; if( !vweap.pweapon.weaponInfo->model[HAND] ) return; //setup CG_vWeapUpdateAnimation(); CG_vWeapGetPosition( vweap.origin, vweap.axis ); //hand entity memset( &gun, 0, sizeof(gun) ); gun.model = vweap.pweapon.weaponInfo->model[HAND]; //if the player doesn't want to view the weapon we still have to build the projection source if( CG_GrabTag( &tag, &gun, "tag_weapon" ) ) CG_vWeapUpdateProjectionSource( vweap.origin, vweap.axis, tag.origin, tag.axis ); else CG_vWeapUpdateProjectionSource( vweap.origin, vweap.axis, vec3_origin, axis_identity ); } /* ============== CG_AddViewWeapon ============== */ void CG_AddViewWeapon( void ) { orientation_t tag; // gun disabled if( !vweap.active || !vweap.state ) return; //remove if player in POV has changed if( vweap.state->number != cg.chasedNum+1 ) vweap.pweapon.weaponInfo = NULL; if( !vweap.pweapon.weaponInfo ) return; if( !vweap.pweapon.weaponInfo->model[HAND] ) return; if( !cg_gun->integer ) return; //hand entity gun.model = vweap.pweapon.weaponInfo->model[HAND]; //update the position VectorCopy( vweap.origin, gun.origin ); VectorCopy( vweap.origin, gun.oldorigin ); VectorCopy( vweap.origin, gun.lightingOrigin ); Matrix_Copy( vweap.axis, gun.axis ); gun.flags = RF_MINLIGHT|RF_WEAPONMODEL; gun.scale = 1.0f; gun.frame = vweap.frame; gun.oldframe = vweap.oldframe; gun.backlerp = vweap.backlerp; CG_AddEntityToScene( &gun ); CG_AddColoredOutLineEffect( &gun, cg.effects, 0, 0, 0, 255 ); CG_AddShellEffects( &gun, cg.effects ); // add attached weapon if( CG_GrabTag( &tag, &gun, "tag_weapon" ) ) CG_AddWeaponOnTag( &gun, &tag, &vweap.pweapon, cg.effects|EF_OUTLINE, NULL ); }