// Copyright (C) 1999-2000 Id Software, Inc. // // cg_weapons.c -- events and effects dealing with weapons #include "cg_local.h" #include "fx_local.h" extern vec4_t bluehudtint; extern vec4_t redhudtint; extern float *hudTintColor; /* Ghoul2 Insert Start */ // set up the appropriate ghoul2 info to a refent void CG_SetGhoul2InfoRef( refEntity_t *ent, refEntity_t *s1) { ent->ghoul2 = s1->ghoul2; VectorCopy( s1->modelScale, ent->modelScale); ent->radius = s1->radius; VectorCopy( s1->angles, ent->angles); } /* Ghoul2 Insert End */ /* ================= CG_RegisterItemVisuals The server says this item is used on this level ================= */ void CG_RegisterItemVisuals( int itemNum ) { itemInfo_t *itemInfo; gitem_t *item; int handle; if ( itemNum < 0 || itemNum >= bg_numItems ) { CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); } itemInfo = &cg_items[ itemNum ]; if ( itemInfo->registered ) { return; } item = &bg_itemlist[ itemNum ]; memset( itemInfo, 0, sizeof( &itemInfo ) ); itemInfo->registered = qtrue; if (item->giType == IT_TEAM && (item->giTag == PW_REDFLAG || item->giTag == PW_BLUEFLAG) && cgs.gametype == GT_CTY) { //in CTY the flag model is different itemInfo->models[0] = trap_R_RegisterModel( item->world_model[1] ); } else if (item->giType == IT_WEAPON && (item->giTag == WP_THERMAL || item->giTag == WP_TRIP_MINE || item->giTag == WP_DET_PACK)) { itemInfo->models[0] = trap_R_RegisterModel( item->world_model[1] ); } else { itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); } /* Ghoul2 Insert Start */ if (!Q_stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm")) { handle = trap_G2API_InitGhoul2Model(&itemInfo->g2Models[0], item->world_model[0], 0 , 0, 0, 0, 0); if (handle<0) { itemInfo->g2Models[0] = NULL; } else { itemInfo->radius[0] = 60; } } /* Ghoul2 Insert End */ if (item->icon) { itemInfo->icon = trap_R_RegisterShader( item->icon ); } else { itemInfo->icon = 0; } if ( item->giType == IT_WEAPON ) { CG_RegisterWeapon( item->giTag ); } // // powerups have an accompanying ring or sphere // if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { if ( item->world_model[1] ) { itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); } } } /* ======================================================================================== VIEW WEAPON ======================================================================================== */ #define WEAPON_FORCE_BUSY_HOLSTER #ifdef WEAPON_FORCE_BUSY_HOLSTER //rww - this was done as a last resort. Forgive me. static int cgWeapFrame = 0; static int cgWeapFrameTime = 0; #endif /* ================= CG_MapTorsoToWeaponFrame ================= */ static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame, int animNum ) { animation_t *animations = bgGlobalAnimations; #ifdef WEAPON_FORCE_BUSY_HOLSTER if (cg.snap->ps.forceHandExtend != HANDEXTEND_NONE || cgWeapFrameTime > cg.time) { //the reason for the after delay is so that it doesn't snap the weapon frame to the "idle" (0) frame //for a very quick moment if (cgWeapFrame < 6) { cgWeapFrame = 6; cgWeapFrameTime = cg.time + 10; } if (cgWeapFrameTime < cg.time && cgWeapFrame < 10) { cgWeapFrame++; cgWeapFrameTime = cg.time + 10; } if (cg.snap->ps.forceHandExtend != HANDEXTEND_NONE && cgWeapFrame == 10) { cgWeapFrameTime = cg.time + 100; } return cgWeapFrame; } else { cgWeapFrame = 0; cgWeapFrameTime = 0; } #endif switch( animNum ) { case TORSO_DROPWEAP1: if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 5 ) { return frame - animations[animNum].firstFrame + 6; } break; case TORSO_RAISEWEAP1: if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 4 ) { return frame - animations[animNum].firstFrame + 6 + 4; } break; case BOTH_ATTACK1: case BOTH_ATTACK2: case BOTH_ATTACK3: case BOTH_ATTACK4: case BOTH_ATTACK5: case BOTH_ATTACK6: case BOTH_ATTACK7: case BOTH_ATTACK8: case BOTH_ATTACK9: case BOTH_ATTACK10: case BOTH_ATTACK11: case BOTH_ATTACK12: case BOTH_THERMAL_THROW: if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 6 ) { return 1 + ( frame - animations[animNum].firstFrame ); } break; } return -1; } /* ============== CG_CalculateWeaponPosition ============== */ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { float scale; int delta; float fracsin; VectorCopy( cg.refdef.vieworg, origin ); VectorCopy( cg.refdefViewAngles, angles ); // on odd legs, invert some angles if ( cg.bobcycle & 1 ) { scale = -cg.xyspeed; } else { scale = cg.xyspeed; } // gun angles from bobbing angles[ROLL] += scale * cg.bobfracsin * 0.005; angles[YAW] += scale * cg.bobfracsin * 0.01; angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; // drop the weapon when landing delta = cg.time - cg.landTime; if ( delta < LAND_DEFLECT_TIME ) { origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { origin[2] += cg.landChange*0.25 * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; } #if 0 // drop the weapon when stair climbing delta = cg.time - cg.stepTime; if ( delta < STEP_TIME/2 ) { origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); } else if ( delta < STEP_TIME ) { origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); } #endif // idle drift scale = cg.xyspeed + 40; fracsin = sin( cg.time * 0.001 ); angles[ROLL] += scale * fracsin * 0.01; angles[YAW] += scale * fracsin * 0.01; angles[PITCH] += scale * fracsin * 0.01; } /* =============== CG_LightningBolt Origin will be the exact tag point, which is slightly different than the muzzle point used for determining hits. The cent should be the non-predicted cent if it is from the player, so the endpoint will reflect the simulated strike (lagging the predicted angle) =============== */ static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { // trace_t trace; refEntity_t beam; // vec3_t forward; // vec3_t muzzlePoint, endPoint; //Must be a durational weapon that continuously generates an effect. if ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.eFlags & EF_ALT_FIRING ) { /*nothing*/ } else { return; } memset( &beam, 0, sizeof( beam ) ); // NOTENOTE No lightning gun-ish stuff yet. /* // CPMA "true" lightning if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { vec3_t angle; int i; for (i = 0; i < 3; i++) { float a = cent->lerpAngles[i] - cg.refdefViewAngles[i]; if (a > 180) { a -= 360; } if (a < -180) { a += 360; } angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value); if (angle[i] < 0) { angle[i] += 360; } if (angle[i] > 360) { angle[i] -= 360; } } AngleVectors(angle, forward, NULL, NULL ); VectorCopy(cent->lerpOrigin, muzzlePoint ); // VectorCopy(cg.refdef.vieworg, muzzlePoint ); } else { // !CPMA AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorCopy(cent->lerpOrigin, muzzlePoint ); } // FIXME: crouch muzzlePoint[2] += DEFAULT_VIEWHEIGHT; VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // project forward by the lightning range VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); // see if it hit a wall CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, cent->currentState.number, MASK_SHOT ); // this is the endpoint VectorCopy( trace.endpos, beam.oldorigin ); // use the provided origin, even though it may be slightly // different than the muzzle origin VectorCopy( origin, beam.origin ); beam.reType = RT_LIGHTNING; beam.customShader = cgs.media.lightningShader; trap_R_AddRefEntityToScene( &beam ); */ // NOTENOTE No lightning gun-ish stuff yet. /* // add the impact flare if it hit something if ( trace.fraction < 1.0 ) { vec3_t angles; vec3_t dir; VectorSubtract( beam.oldorigin, beam.origin, dir ); VectorNormalize( dir ); memset( &beam, 0, sizeof( beam ) ); beam.hModel = cgs.media.lightningExplosionModel; VectorMA( trace.endpos, -16, dir, beam.origin ); // make a random orientation angles[0] = rand() % 360; angles[1] = rand() % 360; angles[2] = rand() % 360; AnglesToAxis( angles, beam.axis ); trap_R_AddRefEntityToScene( &beam ); } */ } /* ======================== CG_AddWeaponWithPowerups ======================== */ static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { // add powerup effects trap_R_AddRefEntityToScene( gun ); if ( powerups & ( 1 << PW_BATTLESUIT ) ) { gun->customShader = cgs.media.battleWeaponShader; trap_R_AddRefEntityToScene( gun ); } if ( powerups & ( 1 << PW_QUAD ) ) { gun->customShader = cgs.media.quadWeaponShader; trap_R_AddRefEntityToScene( gun ); } } /* ============= CG_AddPlayerWeapon Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) The main player will have this called for BOTH cases, so effects like light and sound should only be done on the world model case. ============= */ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team, vec3_t newAngles, qboolean thirdPerson ) { refEntity_t gun; refEntity_t barrel; vec3_t angles; weapon_t weaponNum; weaponInfo_t *weapon; centity_t *nonPredictedCent; refEntity_t flash; weaponNum = cent->currentState.weapon; if (cent->currentState.weapon == WP_EMPLACED_GUN) { return; } CG_RegisterWeapon( weaponNum ); weapon = &cg_weapons[weaponNum]; /* Ghoul2 Insert Start */ memset( &gun, 0, sizeof( gun ) ); // only do this if we are in first person, since world weapons are now handled on the server by Ghoul2 if (!thirdPerson) { // add the weapon VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); gun.shadowPlane = parent->shadowPlane; gun.renderfx = parent->renderfx; if (ps) { // this player, in first person view gun.hModel = weapon->viewModel; } else { gun.hModel = weapon->weaponModel; } if (!gun.hModel) { return; } if ( !ps ) { // add weapon ready sound cent->pe.lightningFiring = qfalse; if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { // lightning gun and guantlet make a different sound when fire is held down trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); cent->pe.lightningFiring = qtrue; } else if ( weapon->readySound ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } } CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); if (!CG_IsMindTricked(cent->currentState.trickedentindex, cent->currentState.trickedentindex2, cent->currentState.trickedentindex3, cent->currentState.trickedentindex4, cg.snap->ps.clientNum)) { CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); //don't draw the weapon if the player is invisible /* if ( weaponNum == WP_STUN_BATON ) { gun.shaderRGBA[0] = gun.shaderRGBA[1] = gun.shaderRGBA[2] = 25; gun.customShader = trap_R_RegisterShader( "gfx/effects/stunPass" ); gun.renderfx = RF_RGB_TINT | RF_FIRST_PERSON | RF_DEPTHHACK; trap_R_AddRefEntityToScene( &gun ); } */ } if (weaponNum == WP_STUN_BATON) { int i = 0; while (i < 3) { memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; if (i == 0) { barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel.md3"); } else if (i == 1) { barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel2.md3"); } else { barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel3.md3"); } angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = 0; AnglesToAxis( angles, barrel.axis ); if (i == 0) { CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel" ); } else if (i == 1) { CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel2" ); } else { CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel3" ); } CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); i++; } } else { // add the spinning barrel if ( weapon->barrelModel ) { memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; barrel.hModel = weapon->barrelModel; angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = 0; AnglesToAxis( angles, barrel.axis ); CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel" ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); } } } /* Ghoul2 Insert End */ memset (&flash, 0, sizeof(flash)); CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash"); VectorCopy(flash.origin, cg.lastFPFlashPoint); // Do special charge bits //----------------------- if ( (ps || cg.renderingThirdPerson || cg.predictedPlayerState.clientNum != cent->currentState.number) && ( ( cent->currentState.modelindex2 == WEAPON_CHARGING_ALT && cent->currentState.weapon == WP_BRYAR_PISTOL ) || ( cent->currentState.weapon == WP_BOWCASTER && cent->currentState.modelindex2 == WEAPON_CHARGING ) || ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.modelindex2 == WEAPON_CHARGING_ALT) ) ) { int shader = 0; float val = 0.0f; float scale = 1.0f; addspriteArgStruct_t fxSArgs; vec3_t flashorigin, flashdir; if (!thirdPerson) { VectorCopy(flash.origin, flashorigin); VectorCopy(flash.axis[0], flashdir); } else { mdxaBone_t boltMatrix; if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) { //it's quite possible that we may have have no weapon model and be in a valid state, so return here if this is the case return; } // go away and get me the bolt position for this frame please if (!(trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, newAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale))) { // Couldn't find bolt point. return; } trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); } if ( cent->currentState.weapon == WP_BRYAR_PISTOL ) { // Hardcoded max charge time of 1 second val = ( cg.time - cent->currentState.constantLight ) * 0.001f; shader = cgs.media.bryarFrontFlash; } else if ( cent->currentState.weapon == WP_BOWCASTER ) { // Hardcoded max charge time of 1 second val = ( cg.time - cent->currentState.constantLight ) * 0.001f; shader = cgs.media.greenFrontFlash; } else if ( cent->currentState.weapon == WP_DEMP2 ) { val = ( cg.time - cent->currentState.constantLight ) * 0.001f; shader = cgs.media.lightningFlash; scale = 1.75f; } if ( val < 0.0f ) { val = 0.0f; } else if ( val > 1.0f ) { val = 1.0f; if (ps && cent->currentState.number == ps->clientNum) { CGCam_Shake( /*0.1f*/0.2f, 100 ); } } else { if (ps && cent->currentState.number == ps->clientNum) { CGCam_Shake( val * val * /*0.3f*/0.6f, 100 ); } } val += random() * 0.5f; VectorCopy(flashorigin, fxSArgs.origin); VectorClear(fxSArgs.vel); VectorClear(fxSArgs.accel); fxSArgs.scale = 3.0f*val*scale; fxSArgs.dscale = 0.0f; fxSArgs.sAlpha = 0.7f; fxSArgs.eAlpha = 0.7f; fxSArgs.rotation = random()*360; fxSArgs.bounce = 0.0f; fxSArgs.life = 1.0f; fxSArgs.shader = shader; fxSArgs.flags = 0x08000000; //FX_AddSprite( flash.origin, NULL, NULL, 3.0f * val, 0.0f, 0.7f, 0.7f, WHITE, WHITE, random() * 360, 0.0f, 1.0f, shader, FX_USE_ALPHA ); trap_FX_AddSprite(&fxSArgs); } // make sure we aren't looking at cg.predictedPlayerEntity for LG nonPredictedCent = &cg_entities[cent->currentState.clientNum]; // if the index of the nonPredictedCent is not the same as the clientNum // then this is a fake player (like on teh single player podiums), so // go ahead and use the cent if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { nonPredictedCent = cent; } // add the flash if ( ( weaponNum == WP_DEMP2) && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) { // continuous flash } else { // impulse flash if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME) { return; } } if ( ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum ) { // Make sure we don't do the thirdperson model effects for the local player if we're in first person vec3_t flashorigin, flashdir; refEntity_t flash; memset (&flash, 0, sizeof(flash)); if (!thirdPerson) { CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash"); VectorCopy(flash.origin, flashorigin); VectorCopy(flash.axis[0], flashdir); } else { mdxaBone_t boltMatrix; if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) { //it's quite possible that we may have have no weapon model and be in a valid state, so return here if this is the case return; } // go away and get me the bolt position for this frame please if (!(trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, newAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale))) { // Couldn't find bolt point. return; } trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); } if ( cg.time - cent->muzzleFlashTime <= MUZZLE_FLASH_TIME + 10 ) { // Handle muzzle flashes if ( cent->currentState.eFlags & EF_ALT_FIRING ) { // Check the alt firing first. if (weapon->altMuzzleEffect) { trap_FX_PlayEffectID(weapon->altMuzzleEffect, flashorigin, flashdir); } } else { // Regular firing if (weapon->muzzleEffect) { trap_FX_PlayEffectID(weapon->muzzleEffect, flashorigin, flashdir); } } } // add lightning bolt CG_LightningBolt( nonPredictedCent, flashorigin ); if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { trap_R_AddLightToScene( flashorigin, 300 + (rand()&31), weapon->flashDlightColor[0], weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); } } } /* ============== CG_AddViewWeapon Add the weapon, and flash for the player's view ============== */ void CG_AddViewWeapon( playerState_t *ps ) { refEntity_t hand; centity_t *cent; clientInfo_t *ci; float fovOffset; vec3_t angles; weaponInfo_t *weapon; float cgFov = cg_fov.value; if (cgFov < 1) { cgFov = 1; } if (cgFov > 97) { cgFov = 97; } if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { return; } if ( ps->pm_type == PM_INTERMISSION ) { return; } // no gun if in third person view or a camera is active //if ( cg.renderingThirdPerson || cg.cameraMode) { if ( cg.renderingThirdPerson ) { return; } // allow the gun to be completely removed if ( !cg_drawGun.integer || cg.predictedPlayerState.zoomMode) { vec3_t origin; if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { // special hack for lightning gun... VectorCopy( cg.refdef.vieworg, origin ); VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); CG_LightningBolt( &cg_entities[ps->clientNum], origin ); } return; } // don't draw if testing a gun model if ( cg.testGun ) { return; } // drop gun lower at higher fov if ( cgFov > 90 ) { fovOffset = -0.2 * ( cgFov - 90 ); } else { fovOffset = 0; } cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; CG_RegisterWeapon( ps->weapon ); weapon = &cg_weapons[ ps->weapon ]; memset (&hand, 0, sizeof(hand)); // set up gun position CG_CalculateWeaponPosition( hand.origin, angles ); VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); AnglesToAxis( angles, hand.axis ); // map torso animations to weapon animations if ( cg_gun_frame.integer ) { // development tool hand.frame = hand.oldframe = cg_gun_frame.integer; hand.backlerp = 0; } else { // get clientinfo for animation map ci = &cgs.clientinfo[ cent->currentState.clientNum ]; hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame, cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ); hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame, cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ); hand.backlerp = cent->pe.torso.backlerp; // Handle the fringe situation where oldframe is invalid if ( hand.frame == -1 ) { hand.frame = 0; hand.oldframe = 0; hand.backlerp = 0; } else if ( hand.oldframe == -1 ) { hand.oldframe = hand.frame; hand.backlerp = 0; } } hand.hModel = weapon->handsModel; hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON;// | RF_MINLIGHT; // add everything onto the hand CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM], angles, qfalse ); } /* ============================================================================== WEAPON SELECTION ============================================================================== */ #define ICON_WEAPONS 0 #define ICON_FORCE 1 #define ICON_INVENTORY 2 void CG_DrawIconBackground(void) { int height,xAdd,x2,y2,t; int prongLeftX,prongRightX; float inTime = cg.invenSelectTime+WEAPON_SELECT_TIME; float wpTime = cg.weaponSelectTime+WEAPON_SELECT_TIME; float fpTime = cg.forceSelectTime+WEAPON_SELECT_TIME; qhandle_t background; int drawType = cgs.media.weaponIconBackground; int prongsOn = cgs.media.weaponProngsOn; // don't display if dead if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { return; } if (cg_hudFiles.integer) { //simple hud return; } x2 = 30; y2 = SCREEN_HEIGHT-70; prongLeftX =x2+37; prongRightX =x2+544; if (inTime > wpTime) { drawType = cgs.media.inventoryIconBackground; prongsOn = cgs.media.inventoryProngsOn; cg.iconSelectTime = cg.invenSelectTime; } else { drawType = cgs.media.weaponIconBackground; prongsOn = cgs.media.weaponProngsOn; cg.iconSelectTime = cg.weaponSelectTime; } if (fpTime > inTime && fpTime > wpTime) { drawType = cgs.media.forceIconBackground; prongsOn = cgs.media.forceProngsOn; cg.iconSelectTime = cg.forceSelectTime; } if ((cg.iconSelectTime+WEAPON_SELECT_TIME)1) { cg.iconHUDActive = qtrue; cg.iconHUDPercent=1; } else if (cg.iconHUDPercent<0) { cg.iconHUDPercent=0; } } else { cg.iconHUDPercent=1; } trap_R_SetColor( colorTable[CT_WHITE] ); height = (int) (60.0f*cg.iconHUDPercent); CG_DrawPic( x2+60, y2+30, 460, -height, drawType); // Top half CG_DrawPic( x2+60, y2+30-2,460, height, drawType); // Bottom half // And now for the prongs /* if ((cg.inventorySelectTime+WEAPON_SELECT_TIME)>cg.time) { cgs.media.currentBackground = ICON_INVENTORY; background = &cgs.media.inventoryProngsOn; } else if ((cg.weaponSelectTime+WEAPON_SELECT_TIME)>cg.time) {*/ cgs.media.currentBackground = ICON_WEAPONS; background = prongsOn; /* } else { cgs.media.currentBackground = ICON_FORCE; background = &cgs.media.forceProngsOn; } */ // Side Prongs trap_R_SetColor( colorTable[CT_WHITE]); xAdd = (int) 8*cg.iconHUDPercent; CG_DrawPic( prongLeftX+xAdd, y2-10, 40, 80, background); CG_DrawPic( prongRightX-xAdd, y2-10, -40, 80, background); } qboolean CG_WeaponCheck(int weap) { if (cg.snap->ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].energyPerShot && cg.snap->ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].altEnergyPerShot) { return qfalse; } return qtrue; } /* =============== CG_WeaponSelectable =============== */ static qboolean CG_WeaponSelectable( int i ) { /*if ( !cg.snap->ps.ammo[weaponData[i].ammoIndex] ) { return qfalse; }*/ if (!i) { return qfalse; } if (cg.predictedPlayerState.ammo[weaponData[i].ammoIndex] < weaponData[i].energyPerShot && cg.predictedPlayerState.ammo[weaponData[i].ammoIndex] < weaponData[i].altEnergyPerShot) { return qfalse; } if (i == WP_DET_PACK && cg.predictedPlayerState.ammo[weaponData[i].ammoIndex] < 1 && !cg.predictedPlayerState.hasDetPackPlanted) { return qfalse; } if ( ! (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { return qfalse; } return qtrue; } /* =================== CG_DrawWeaponSelect =================== */ void CG_DrawWeaponSelect( void ) { int i; int bits; int count; int smallIconSize,bigIconSize; int holdX,x,y,pad; int sideLeftIconCnt,sideRightIconCnt; int sideMax,holdCount,iconCnt; int height; if (cg.predictedPlayerState.emplacedIndex) { //can't cycle when on a weapon cg.weaponSelectTime = 0; } if ((cg.weaponSelectTime+WEAPON_SELECT_TIME) (2*sideMax)) // Go to the max on each side { sideLeftIconCnt = sideMax; sideRightIconCnt = sideMax; } else // Less than max, so do the calc { sideLeftIconCnt = holdCount/2; sideRightIconCnt = holdCount - sideLeftIconCnt; } i = cg.weaponSelect - 1; if (i<1) { i = 13; } smallIconSize = 40; bigIconSize = 80; pad = 12; x = 320; y = 410; // Background // memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); // calcColor[3] = .35f; // trap_R_SetColor( calcColor); // Left side ICONS trap_R_SetColor(colorTable[CT_WHITE]); // Work backwards from current icon holdX = x - ((bigIconSize/2) + pad + smallIconSize); height = smallIconSize * 1;//cg.iconHUDPercent; for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--) { if (i<1) { i = 13; } if ( !(bits & ( 1 << i ))) // Does he have this weapon? { continue; } if ( !CG_WeaponSelectable(i) && (i == WP_THERMAL || i == WP_TRIP_MINE) ) { //Don't show thermal and tripmine when out of them continue; } ++iconCnt; // Good icon if (cgs.media.weaponIcons[i]) { weaponInfo_t *weaponInfo; CG_RegisterWeapon( i ); weaponInfo = &cg_weapons[i]; trap_R_SetColor(colorTable[CT_WHITE]); if (!CG_WeaponCheck(i)) { CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, /*weaponInfo->weaponIconNoAmmo*/cgs.media.weaponIcons_NA[i] ); } else { CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, /*weaponInfo->weaponIcon*/cgs.media.weaponIcons[i] ); } holdX -= (smallIconSize+pad); } } // Current Center Icon height = bigIconSize * cg.iconHUDPercent; if (cgs.media.weaponIcons[cg.weaponSelect]) { weaponInfo_t *weaponInfo; CG_RegisterWeapon( cg.weaponSelect ); weaponInfo = &cg_weapons[cg.weaponSelect]; trap_R_SetColor( colorTable[CT_WHITE]); if (!CG_WeaponCheck(cg.weaponSelect)) { CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10, bigIconSize, bigIconSize, cgs.media.weaponIcons_NA[cg.weaponSelect] ); } else { CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10, bigIconSize, bigIconSize, cgs.media.weaponIcons[cg.weaponSelect] ); } } i = cg.weaponSelect + 1; if (i> 13) { i = 1; } // Right side ICONS // Work forwards from current icon holdX = x + (bigIconSize/2) + pad; height = smallIconSize * cg.iconHUDPercent; for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++) { if (i>13) { i = 1; } if ( !(bits & ( 1 << i ))) // Does he have this weapon? { continue; } if ( !CG_WeaponSelectable(i) && (i == WP_THERMAL || i == WP_TRIP_MINE) ) { //Don't show thermal and tripmine when out of them continue; } ++iconCnt; // Good icon if (/*weaponData[i].weaponIcon[0]*/cgs.media.weaponIcons[i]) { weaponInfo_t *weaponInfo; CG_RegisterWeapon( i ); weaponInfo = &cg_weapons[i]; // No ammo for this weapon? trap_R_SetColor( colorTable[CT_WHITE]); if (!CG_WeaponCheck(i)) { CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.weaponIcons_NA[i] ); } else { CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.weaponIcons[i] ); } holdX += (smallIconSize+pad); } } // draw the selected name if ( cg_weapons[ cg.weaponSelect ].item ) { vec4_t textColor = { .875f, .718f, .121f, 1.0f }; char text[1024]; if ( trap_SP_GetStringTextString( va("INGAME_%s",cg_weapons[ cg.weaponSelect ].item->classname), text, sizeof( text ))) { UI_DrawProportionalString(320, y+45, text, UI_CENTER|UI_SMALLFONT, textColor); } else { UI_DrawProportionalString(320, y+45, cg_weapons[ cg.weaponSelect ].item->classname, UI_CENTER|UI_SMALLFONT, textColor); } } trap_R_SetColor( NULL ); } /* =============== CG_NextWeapon_f =============== */ void CG_NextWeapon_f( void ) { int i; int original; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } if (cg.snap->ps.emplacedIndex) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < 16 ; i++ ) { cg.weaponSelect++; if ( cg.weaponSelect == 16 ) { cg.weaponSelect = 0; } // if ( cg.weaponSelect == WP_STUN_BATON ) { // continue; // never cycle to gauntlet // } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == 16 ) { cg.weaponSelect = original; } else { trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); } } /* =============== CG_PrevWeapon_f =============== */ void CG_PrevWeapon_f( void ) { int i; int original; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } if (cg.snap->ps.emplacedIndex) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < 16 ; i++ ) { cg.weaponSelect--; if ( cg.weaponSelect == -1 ) { cg.weaponSelect = 15; } // if ( cg.weaponSelect == WP_STUN_BATON ) { // continue; // never cycle to gauntlet // } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == 16 ) { cg.weaponSelect = original; } else { trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); } } /* =============== CG_Weapon_f =============== */ void CG_Weapon_f( void ) { int num; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } if (cg.snap->ps.emplacedIndex) { return; } num = atoi( CG_Argv( 1 ) ); if ( num < 1 || num > 15 ) { return; } if (num == 1 && cg.snap->ps.weapon == WP_SABER) { if (cg.snap->ps.weaponTime < 1) { //trap_SendClientCommand("sv_saberswitch"); trap_SendConsoleCommand("sv_saberswitch"); } return; } //rww - hack to make weapon numbers same as single player if (num > WP_STUN_BATON) { num++; } else { if (cg.snap->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) { num = WP_SABER; } else { num = WP_STUN_BATON; } } if (num > WP_DET_PACK+1) { //other weapons are off limits due to not actually being weapon weapons return; } if (num >= WP_THERMAL) { int weap, i = 0; if (cg.snap->ps.weapon >= WP_THERMAL && cg.snap->ps.weapon <= WP_DET_PACK) { // already in cycle range so start with next cycle item weap = cg.snap->ps.weapon + 1; } else { // not in cycle range, so start with thermal detonator weap = WP_THERMAL; } // prevent an endless loop while ( i <= 4 ) { if (weap > WP_DET_PACK) { weap = WP_THERMAL; } if (CG_WeaponSelectable(weap)) { num = weap; break; } weap++; i++; } } if (!CG_WeaponSelectable(num)) { return; } cg.weaponSelectTime = cg.time; if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { if (num == WP_SABER) { //don't have saber, try the stun baton on the same slot num = WP_STUN_BATON; if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { return; } } else { return; // don't have the weapon } } if (cg.weaponSelect != num) { trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); } cg.weaponSelect = num; } /* =================== CG_OutOfAmmoChange The current weapon has just run out of ammo =================== */ void CG_OutOfAmmoChange( int oldWeapon ) { int i; cg.weaponSelectTime = cg.time; for ( i = WP_DET_PACK ; i > 0 ; i-- ) //We don't want the emplaced or turret { if ( CG_WeaponSelectable( i ) ) { /* if ( 1 == cg_autoswitch.integer && ( i == WP_TRIP_MINE || i == WP_DET_PACK || i == WP_THERMAL || i == WP_ROCKET_LAUNCHER) ) // safe weapon switch */ //rww - Don't we want to make sure i != one of these if autoswitch is 1 (safe)? if (cg_autoswitch.integer != 1 || (i != WP_TRIP_MINE && i != WP_DET_PACK && i != WP_THERMAL && i != WP_ROCKET_LAUNCHER)) { if (i != oldWeapon) { //don't even do anything if we're just selecting the weapon we already have/had cg.weaponSelect = i; break; } } } } trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); } /* =================================================================================================== WEAPON EVENTS =================================================================================================== */ void CG_FireATST(centity_t *cent, qboolean altFire) { //No muzzle flash for now? #if 0 int getMeBolt = 0; mdxaBone_t boltMatrix; vec3_t flashorigin, flashdir; if (!cgs.clientinfo[cent->currentState.number].bolt_lhand) { return; } if (altFire) { getMeBolt = cgs.clientinfo[cent->currentState.number].bolt_lhand; } else { getMeBolt = cgs.clientinfo[cent->currentState.number].bolt_rhand; } if (!(trap_G2API_GetBoltMatrix(cent->ghoul2, 0, getMeBolt, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale))) { return; } trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); trap_FX_PlayEffectID(trap_FX_RegisterEffect("turret/muzzle_flash.efx"), flashorigin, flashdir); #endif trap_S_StartSound(NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound(va("sound/weapons/atst/ATSTfire1.wav"/*, Q_irand(1,4)*/))); } void CG_GetClientWeaponMuzzleBoltPoint(int clIndex, vec3_t to) { centity_t *cent; mdxaBone_t boltMatrix; if (clIndex < 0 || clIndex >= MAX_CLIENTS) { return; } cent = &cg_entities[clIndex]; if (!cent || !cent->ghoul2 || !trap_G2_HaveWeGhoul2Models(cent->ghoul2) || !trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) { return; } trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); trap_G2API_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, to); } /* ================ CG_FireWeapon Caused by an EV_FIRE_WEAPON event ================ */ void CG_FireWeapon( centity_t *cent, qboolean altFire ) { entityState_t *ent; int c; weaponInfo_t *weap; if (cent->isATST) { CG_FireATST(cent, altFire); return; } ent = ¢->currentState; if ( ent->weapon == WP_NONE ) { return; } if ( ent->weapon >= WP_NUM_WEAPONS ) { CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); return; } weap = &cg_weapons[ ent->weapon ]; // mark the entity as muzzle flashing, so when it is added it will // append the flash to the weapon model cent->muzzleFlashTime = cg.time; if (cg.predictedPlayerState.clientNum == cent->currentState.number) { if ((ent->weapon == WP_BRYAR_PISTOL && altFire) || (ent->weapon == WP_BOWCASTER && !altFire) || (ent->weapon == WP_DEMP2 && altFire)) { float val = ( cg.time - cent->currentState.constantLight ) * 0.001f; if (val > 3) { val = 3; } if (val < 0.2) { val = 0.2; } val *= 2; CGCam_Shake( val, 250 ); } else if (ent->weapon == WP_ROCKET_LAUNCHER || (ent->weapon == WP_REPEATER && altFire) || ent->weapon == WP_FLECHETTE) { if (ent->weapon == WP_ROCKET_LAUNCHER) { CGCam_Shake(Q_irand(2, 3), 350); } else if (ent->weapon == WP_REPEATER) { CGCam_Shake(Q_irand(2, 3), 350); } else if (ent->weapon == WP_FLECHETTE) { if (altFire) { CGCam_Shake(Q_irand(2, 3), 350); } else { CGCam_Shake(1.5, 250); } } } } // lightning gun only does this this on initial press if ( ent->weapon == WP_DEMP2 ) { if ( cent->pe.lightningFiring ) { return; } } // play quad sound if needed if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { //trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); } // play a sound if (altFire) { // play a sound for ( c = 0 ; c < 4 ; c++ ) { if ( !weap->altFlashSound[c] ) { break; } } if ( c > 0 ) { c = rand() % c; if ( weap->altFlashSound[c] ) { trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSound[c] ); } } // if ( weap->altFlashSnd ) // { // trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd ); // } } else { // play a sound for ( c = 0 ; c < 4 ; c++ ) { if ( !weap->flashSound[c] ) { break; } } if ( c > 0 ) { c = rand() % c; if ( weap->flashSound[c] ) { trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); } } } } /* ================= CG_MissileHitWall Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing ================= */ void CG_MissileHitWall(int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType, qboolean altFire, int charge) { int parm; vec3_t up={0,0,1}; switch( weapon ) { case WP_BRYAR_PISTOL: if ( altFire ) { parm = charge; FX_BryarAltHitWall( origin, dir, parm ); } else { FX_BryarHitWall( origin, dir ); } break; case WP_TURRET: FX_TurretHitWall( origin, dir ); break; case WP_BLASTER: FX_BlasterWeaponHitWall( origin, dir ); break; case WP_DISRUPTOR: FX_DisruptorAltMiss( origin, dir ); break; case WP_BOWCASTER: FX_BowcasterHitWall( origin, dir ); break; case WP_REPEATER: if ( altFire ) { FX_RepeaterAltHitWall( origin, dir ); } else { FX_RepeaterHitWall( origin, dir ); } break; case WP_DEMP2: if (altFire) { trap_FX_PlayEffectID(trap_FX_RegisterEffect("demp2/altDetonate.efx"), origin, dir); } else { FX_DEMP2_HitWall( origin, dir ); } break; case WP_FLECHETTE: /*if (altFire) { CG_SurfaceExplosion(origin, dir, 20.0f, 12.0f, qtrue); } else */ if (!altFire) { FX_FlechetteWeaponHitWall( origin, dir ); } break; case WP_ROCKET_LAUNCHER: FX_RocketHitWall( origin, dir ); break; case WP_THERMAL: trap_FX_PlayEffectID( cgs.effects.thermalExplosionEffect, origin, dir ); trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up ); break; case WP_EMPLACED_GUN: FX_BlasterWeaponHitWall( origin, dir ); //FIXME: Give it its own hit wall effect break; } } /* ================= CG_MissileHitPlayer ================= */ void CG_MissileHitPlayer(int weapon, vec3_t origin, vec3_t dir, int entityNum, qboolean altFire) { qboolean humanoid = qtrue; vec3_t up={0,0,1}; /* // NOTENOTE Non-portable code from single player if ( cent->gent ) { other = &g_entities[cent->gent->s.otherEntityNum]; if ( other->client && other->client->playerTeam == TEAM_BOTS ) { humanoid = qfalse; } } */ // NOTENOTE No bleeding in this game // CG_Bleed( origin, entityNum ); // some weapons will make an explosion with the blood, while // others will just make the blood switch ( weapon ) { case WP_BRYAR_PISTOL: if ( altFire ) { FX_BryarAltHitPlayer( origin, dir, humanoid ); } else { FX_BryarHitPlayer( origin, dir, humanoid ); } break; case WP_TURRET: FX_TurretHitPlayer( origin, dir, humanoid ); break; case WP_BLASTER: FX_BlasterWeaponHitPlayer( origin, dir, humanoid ); break; case WP_DISRUPTOR: FX_DisruptorAltHit( origin, dir); break; case WP_BOWCASTER: FX_BowcasterHitPlayer( origin, dir, humanoid ); break; case WP_REPEATER: if ( altFire ) { FX_RepeaterAltHitPlayer( origin, dir, humanoid ); } else { FX_RepeaterHitPlayer( origin, dir, humanoid ); } break; case WP_DEMP2: // Do a full body effect here for some more feedback // NOTENOTE The chaining of the demp2 is not yet implemented. /* if ( other ) { other->s.powerups |= ( 1 << PW_DISINT_1 ); other->client->ps.powerups[PW_DISINT_1] = cg.time + 650; } */ if (altFire) { trap_FX_PlayEffectID(trap_FX_RegisterEffect("demp2/altDetonate.efx"), origin, dir); } else { FX_DEMP2_HitPlayer( origin, dir, humanoid ); } break; case WP_FLECHETTE: FX_FlechetteWeaponHitPlayer( origin, dir, humanoid ); break; case WP_ROCKET_LAUNCHER: FX_RocketHitPlayer( origin, dir, humanoid ); break; case WP_THERMAL: trap_FX_PlayEffectID( cgs.effects.thermalExplosionEffect, origin, dir ); trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up ); break; case WP_EMPLACED_GUN: //FIXME: Its own effect? FX_BlasterWeaponHitPlayer( origin, dir, humanoid ); break; default: break; } } /* ============================================================================ BULLETS ============================================================================ */ /* =============== CG_Tracer =============== */ void CG_Tracer( vec3_t source, vec3_t dest ) { vec3_t forward, right; polyVert_t verts[4]; vec3_t line; float len, begin, end; vec3_t start, finish; vec3_t midpoint; // tracer VectorSubtract( dest, source, forward ); len = VectorNormalize( forward ); // start at least a little ways from the muzzle if ( len < 100 ) { return; } begin = 50 + random() * (len - 60); end = begin + cg_tracerLength.value; if ( end > len ) { end = len; } VectorMA( source, begin, forward, start ); VectorMA( source, end, forward, finish ); line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); VectorScale( cg.refdef.viewaxis[1], line[1], right ); VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); VectorNormalize( right ); VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); verts[0].st[0] = 0; verts[0].st[1] = 1; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); verts[1].st[0] = 1; verts[1].st[1] = 0; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); 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; VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); verts[3].st[0] = 0; 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.tracerShader, 4, verts ); midpoint[0] = ( start[0] + finish[0] ) * 0.5; midpoint[1] = ( start[1] + finish[1] ) * 0.5; midpoint[2] = ( start[2] + finish[2] ) * 0.5; // add the tracer sound //trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); } /* ====================== CG_CalcMuzzlePoint ====================== */ qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { vec3_t forward, right; vec3_t gunpoint; centity_t *cent; int anim; if ( entityNum == cg.snap->ps.clientNum ) { //I'm not exactly sure why we'd be rendering someone else's crosshair, but hey. int weapontype = cg.snap->ps.weapon; vec3_t weaponMuzzle; VectorCopy(WP_MuzzlePoint[weapontype], weaponMuzzle); if (weapontype == WP_DISRUPTOR || weapontype == WP_STUN_BATON || weapontype == WP_SABER) { VectorClear(weaponMuzzle); } if (cg.snap->ps.usingATST) { VectorClear(weaponMuzzle); weaponMuzzle[0] = 16; weaponMuzzle[2] = 128; } if (cg.renderingThirdPerson) { VectorCopy( cg.predictedPlayerEntity.lerpOrigin, gunpoint ); AngleVectors( cg.predictedPlayerEntity.lerpAngles, forward, right, NULL ); } else { VectorCopy( cg.refdef.vieworg, gunpoint ); AngleVectors( cg.refdefViewAngles, forward, right, NULL ); } if (weapontype == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex) { centity_t *gunEnt = &cg_entities[cg.snap->ps.emplacedIndex]; if (gunEnt) { vec3_t pitchConstraint; VectorCopy(gunEnt->lerpOrigin, gunpoint); gunpoint[2] += 46; if (cg.renderingThirdPerson) { VectorCopy(cg.predictedPlayerEntity.lerpAngles, pitchConstraint); } else { VectorCopy(cg.refdefViewAngles, pitchConstraint); } if (pitchConstraint[PITCH] > 40) { pitchConstraint[PITCH] = 40; } AngleVectors( pitchConstraint, forward, right, NULL ); } } VectorCopy(gunpoint, muzzle); VectorMA(muzzle, weaponMuzzle[0], forward, muzzle); VectorMA(muzzle, weaponMuzzle[1], right, muzzle); if (weapontype == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex) { //Do nothing } else if (cg.renderingThirdPerson) { muzzle[2] += cg.snap->ps.viewheight + weaponMuzzle[2]; } else { muzzle[2] += weaponMuzzle[2]; } return qtrue; } cent = &cg_entities[entityNum]; if ( !cent->currentValid ) { return qfalse; } VectorCopy( cent->currentState.pos.trBase, muzzle ); AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; if ( anim == BOTH_CROUCH1WALK || anim == BOTH_CROUCH1IDLE ) { muzzle[2] += CROUCH_VIEWHEIGHT; } else { muzzle[2] += DEFAULT_VIEWHEIGHT; } VectorMA( muzzle, 14, forward, muzzle ); return qtrue; } /* Ghoul2 Insert Start */ // create one instance of all the weapons we are going to use so we can just copy this info into each clients gun ghoul2 object in fast way void *g2WeaponInstances[MAX_WEAPONS]; void CG_InitG2Weapons(void) { int i = 0; gitem_t *item; memset(g2WeaponInstances, 0, sizeof(g2WeaponInstances)); for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { if ( item->giType == IT_WEAPON ) { // initialise model trap_G2API_InitGhoul2Model(&g2WeaponInstances[/*i*/item->giTag], item->world_model[0], 0, 0, 0, 0, 0); // trap_G2API_InitGhoul2Model(&g2WeaponInstances[i], item->world_model[0],G_ModelIndex( item->world_model[0] ) , 0, 0, 0, 0); if (g2WeaponInstances[/*i*/item->giTag]) { // indicate we will be bolted to model 0 (ie the player) on bolt 0 (always the right hand) when we get copied trap_G2API_SetBoltInfo(g2WeaponInstances[/*i*/item->giTag], 0, 0); // now set up the gun bolt on it trap_G2API_AddBolt(g2WeaponInstances[/*i*/item->giTag], 0, "*flash"); i++; } if (i == MAX_WEAPONS) { assert(0); break; } } } } // clean out any g2 models we instanciated for copying purposes void CG_ShutDownG2Weapons(void) { int i; for (i=0; icurrentState.saberInFlight) { cent->ghoul2weapon = g2WeaponInstances[WP_SABER]; } if (cent->currentState.eFlags & EF_DEAD) { //no updating weapons when dead cent->ghoul2weapon = NULL; return; } if (cent->torsoBolt) { //got our limb cut off, no updating weapons until it's restored cent->ghoul2weapon = NULL; return; } if (ps && ps->usingATST) { cent->ghoul2weapon = NULL; } if (cent->isATST) { cent->ghoul2weapon = NULL; return; } if (cent->ghoul2 && cent->ghoul2weapon != g2WeaponInstances[ps->weapon] && ps->clientNum == cent->currentState.number) //don't want spectator mode forcing one client's weapon instance over another's { CG_CopyG2WeaponInstance(ps->weapon, cent->ghoul2); cent->ghoul2weapon = g2WeaponInstances[ps->weapon]; if (cent->weapon == WP_SABER && cg_entities[cent->currentState.number].weapon != ps->weapon && !ps->saberHolstered) { //switching away from the saber trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" )); } else if (ps->weapon == WP_SABER && cg_entities[cent->currentState.number].weapon != ps->weapon) { //switching to the saber trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); } cent->weapon = ps->weapon; cg_entities[cent->currentState.number].weapon = ps->weapon; } } /* Ghoul2 Insert End */