// 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) { if (item->giType == IT_HEALTH) { //medpack gets nomip'd by the ui or something I guess. itemInfo->icon = trap_R_RegisterShaderNoMip( item->icon ); } else { 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 = bgHumanoidAnimations; #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_ATTACK10: 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.refdef.viewangles, 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.refdef.viewangles[i]; if (a > 180) { a -= 360; } if (a < -180) { a += 360; } angle[i] = cg.refdef.viewangles[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 (cg.predictedPlayerState.electrifyTime > cg.time) { //add electrocution shell int preShader = gun->customShader; if ( rand() & 1 ) { gun->customShader = cgs.media.electricBodyShader; } else { gun->customShader = cgs.media.electricBody2Shader; } trap_R_AddRefEntityToScene( gun ); gun->customShader = preShader; //set back just to be safe } } /* ============= 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; } if (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cent->currentState.number == cg.predictedPlayerState.clientNum) { //spectator mode, don't draw it... 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.modelindex2 == WEAPON_CHARGING_ALT && cent->currentState.weapon == WP_BRYAR_OLD ) || ( 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; } BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); } if ( cent->currentState.weapon == WP_BRYAR_PISTOL || cent->currentState.weapon == WP_BRYAR_OLD) { // 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; } BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); BG_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) { if (!thirdPerson) { trap_FX_PlayEntityEffectID(weapon->altMuzzleEffect, flashorigin, flash.axis, -1, -1, -1, -1 ); } else { trap_FX_PlayEffectID(weapon->altMuzzleEffect, flashorigin, flashdir, -1, -1); } } } else { // Regular firing if (weapon->muzzleEffect) { if (!thirdPerson) { trap_FX_PlayEntityEffectID(weapon->muzzleEffect, flashorigin, flash.axis, -1, -1, -1, -1 ); } else { trap_FX_PlayEffectID(weapon->muzzleEffect, flashorigin, flashdir, -1, -1); } } } } // 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_entities[cg.predictedPlayerState.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 if (cent->currentState.eType == ET_NPC) { if (!cent->npcClient) { return; } ci = cent->npcClient; } else { ci = &cgs.clientinfo[ cent->currentState.clientNum ]; } hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame, cent->currentState.torsoAnim ); hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame, cent->currentState.torsoAnim ); 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_entities[cg.predictedPlayerState.clientNum], 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; // int drawType = cgs.media.weaponIconBackground; // int yOffset = 0; #ifdef _XBOX //yOffset = -50; #endif // 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; cg.iconSelectTime = cg.invenSelectTime; } else { // drawType = cgs.media.weaponIconBackground; cg.iconSelectTime = cg.weaponSelectTime; } if (fpTime > inTime && fpTime > wpTime) { // drawType = cgs.media.forceIconBackground; 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+yOffset, 460, -height, drawType); // Top half //CG_DrawPic( x2+60, y2+30-2+yOffset, 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; } 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 =================== */ #ifdef _XBOX extern bool CL_ExtendSelectTime(void); #endif 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; int yOffset = 0; qboolean drewConc = qfalse; 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; } if ( cg.weaponSelect == WP_CONCUSSION ) { i = WP_FLECHETTE; } else { i = cg.weaponSelect - 1; } if (i<1) { i = LAST_USEABLE_WEAPON; } 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; drewConc = qfalse; for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--) { if ( i == WP_CONCUSSION ) { i--; } else if ( i == WP_FLECHETTE && !drewConc && cg.weaponSelect != WP_CONCUSSION ) { i = WP_CONCUSSION; } if (i<1) { //i = 13; //...don't ever do this. i = LAST_USEABLE_WEAPON; } if ( !(bits & ( 1 << i ))) // Does he have this weapon? { if ( i == WP_CONCUSSION ) { drewConc = qtrue; i = WP_ROCKET_LAUNCHER; } 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+yOffset, smallIconSize, smallIconSize, /*weaponInfo->weaponIconNoAmmo*/cgs.media.weaponIcons_NA[i] ); } else { CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, /*weaponInfo->weaponIcon*/cgs.media.weaponIcons[i] ); } holdX -= (smallIconSize+pad); } if ( i == WP_CONCUSSION ) { drewConc = qtrue; i = WP_ROCKET_LAUNCHER; } } // 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+yOffset, bigIconSize, bigIconSize, cgs.media.weaponIcons_NA[cg.weaponSelect] ); } else { CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset, bigIconSize, bigIconSize, cgs.media.weaponIcons[cg.weaponSelect] ); } } if ( cg.weaponSelect == WP_CONCUSSION ) { i = WP_ROCKET_LAUNCHER; } else { i = cg.weaponSelect + 1; } if (i> LAST_USEABLE_WEAPON) { 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 == WP_CONCUSSION ) { i++; } else if ( i == WP_ROCKET_LAUNCHER && !drewConc && cg.weaponSelect != WP_CONCUSSION ) { i = WP_CONCUSSION; } if (i>LAST_USEABLE_WEAPON) { i = 1; } if ( !(bits & ( 1 << i ))) // Does he have this weapon? { if ( i == WP_CONCUSSION ) { drewConc = qtrue; i = WP_FLECHETTE; } 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+yOffset, smallIconSize, smallIconSize, cgs.media.weaponIcons_NA[i] ); } else { CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, cgs.media.weaponIcons[i] ); } holdX += (smallIconSize+pad); } if ( i == WP_CONCUSSION ) { drewConc = qtrue; i = WP_FLECHETTE; } } // draw the selected name if ( cg_weapons[ cg.weaponSelect ].item ) { vec4_t textColor = { .875f, .718f, .121f, 1.0f }; char text[1024]; char upperKey[1024]; strcpy(upperKey, cg_weapons[ cg.weaponSelect ].item->classname); if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text ))) { UI_DrawProportionalString(320, y+45+yOffset, text, UI_CENTER|UI_SMALLFONT, textColor); } else { UI_DrawProportionalString(320, y+45+yOffset, 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.predictedPlayerState.pm_type == PM_SPECTATOR) { return; } if (cg.snap->ps.emplacedIndex) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < WP_NUM_WEAPONS ; i++ ) { //*SIGH*... Hack to put concussion rifle before rocketlauncher if ( cg.weaponSelect == WP_FLECHETTE ) { cg.weaponSelect = WP_CONCUSSION; } else if ( cg.weaponSelect == WP_CONCUSSION ) { cg.weaponSelect = WP_ROCKET_LAUNCHER; } else if ( cg.weaponSelect == WP_DET_PACK ) { cg.weaponSelect = WP_BRYAR_OLD; } else { cg.weaponSelect++; } if ( cg.weaponSelect == WP_NUM_WEAPONS ) { cg.weaponSelect = 0; } // if ( cg.weaponSelect == WP_STUN_BATON ) { // continue; // never cycle to gauntlet // } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == WP_NUM_WEAPONS ) { 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.predictedPlayerState.pm_type == PM_SPECTATOR) { return; } if (cg.snap->ps.emplacedIndex) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < WP_NUM_WEAPONS ; i++ ) { //*SIGH*... Hack to put concussion rifle before rocketlauncher if ( cg.weaponSelect == WP_ROCKET_LAUNCHER ) { cg.weaponSelect = WP_CONCUSSION; } else if ( cg.weaponSelect == WP_CONCUSSION ) { cg.weaponSelect = WP_FLECHETTE; } else if ( cg.weaponSelect == WP_BRYAR_OLD ) { cg.weaponSelect = WP_DET_PACK; } else { cg.weaponSelect--; } if ( cg.weaponSelect == -1 ) { cg.weaponSelect = WP_NUM_WEAPONS-1; } // if ( cg.weaponSelect == WP_STUN_BATON ) { // continue; // never cycle to gauntlet // } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == WP_NUM_WEAPONS ) { 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 > LAST_USEABLE_WEAPON ) { return; } if (num == 1 && cg.snap->ps.weapon == WP_SABER) { if (cg.snap->ps.weaponTime < 1) { trap_SendConsoleCommand("sv_saberswitch\n"); } return; } //rww - hack to make weapon numbers same as single player if (num > WP_STUN_BATON) { //num++; num += 2; //I suppose this is getting kind of crazy, what with the wp_melee in there too now. } else { if (cg.snap->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) { num = WP_SABER; } else { num = WP_MELEE; } } if (num > LAST_USEABLE_WEAPON+1) { //other weapons are off limits due to not actually being weapon weapons return; } if (num >= WP_THERMAL && num <= WP_DET_PACK) { 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 melee on the same slot num = WP_MELEE; 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; } //Version of the above which doesn't add +2 to a weapon. The above can't //triger WP_MELEE or WP_STUN_BATON. Derogatory comments go here. void CG_WeaponClean_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 > LAST_USEABLE_WEAPON ) { return; } if (num == 1 && cg.snap->ps.weapon == WP_SABER) { if (cg.snap->ps.weaponTime < 1) { trap_SendConsoleCommand("sv_saberswitch\n"); } return; } if(num == WP_STUN_BATON) { if (cg.snap->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) { num = WP_SABER; } else { num = WP_MELEE; } } if (num > LAST_USEABLE_WEAPON+1) { //other weapons are off limits due to not actually being weapon weapons return; } if (num >= WP_THERMAL && num <= WP_DET_PACK) { 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 melee on the same slot num = WP_MELEE; 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 = LAST_USEABLE_WEAPON ; 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_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); BG_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; 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_BRYAR_OLD && 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 || (ent->weapon == WP_CONCUSSION && !altFire)) { if (ent->weapon == WP_CONCUSSION) { if (!cg.renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise {//kick the view back cg.kick_angles[PITCH] = flrand( -10, -15 ); cg.kick_time = cg.time; } } else if (ent->weapon == WP_ROCKET_LAUNCHER) { CGCam_Shake(flrand(2, 3), 350); } else if (ent->weapon == WP_REPEATER) { CGCam_Shake(flrand(2, 3), 350); } else if (ent->weapon == WP_FLECHETTE) { if (altFire) { CGCam_Shake(flrand(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] ); } } } } qboolean CG_VehicleWeaponImpact( centity_t *cent ) {//see if this is a missile entity that's owned by a vehicle and should do a special, overridden impact effect if ((cent->currentState.eFlags&EF_JETPACK_ACTIVE)//hack so we know we're a vehicle Weapon shot && cent->currentState.otherEntityNum2 && g_vehWeaponInfo[cent->currentState.otherEntityNum2].iImpactFX) {//missile is from a special vehWeapon vec3_t normal; ByteToDir( cent->currentState.eventParm, normal ); trap_FX_PlayEffectID( g_vehWeaponInfo[cent->currentState.otherEntityNum2].iImpactFX, cent->lerpOrigin, normal, -1, -1 ); return qtrue; } return qfalse; } /* ================= 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_CONCUSSION: FX_ConcussionHitWall( origin, dir ); break; case WP_BRYAR_OLD: 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(cgs.effects.mAltDetonate, origin, dir, -1, -1); } 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, -1, -1 ); trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up, -1, -1 ); 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_CONCUSSION: FX_ConcussionHitPlayer( origin, dir, humanoid ); break; case WP_BRYAR_OLD: 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(cgs.effects.mAltDetonate, origin, dir, -1, -1); } 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, -1, -1 ); trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up, -1, -1 ); break; case WP_EMPLACED_GUN: //FIXME: Its own effect? FX_BlasterWeaponHitPlayer( origin, dir, humanoid ); break; default: break; } } /* ============================================================================ BULLETS ============================================================================ */ /* ====================== 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; centity_t *pEnt = &cg_entities[cg.predictedPlayerState.clientNum]; VectorCopy(WP_MuzzlePoint[weapontype], weaponMuzzle); if (weapontype == WP_DISRUPTOR || weapontype == WP_STUN_BATON || weapontype == WP_MELEE || weapontype == WP_SABER) { VectorClear(weaponMuzzle); } if (cg.renderingThirdPerson) { VectorCopy( pEnt->lerpOrigin, gunpoint ); AngleVectors( pEnt->lerpAngles, forward, right, NULL ); } else { VectorCopy( cg.refdef.vieworg, gunpoint ); AngleVectors( cg.refdef.viewangles, 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(pEnt->lerpAngles, pitchConstraint); } else { VectorCopy(cg.refdef.viewangles, 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; 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 static 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 ) { assert(item->giTag < MAX_WEAPONS); // 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 if (item->giTag == WP_SABER) { trap_G2API_AddBolt(g2WeaponInstances[/*i*/item->giTag], 0, "*blade1"); } else { 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.eType != ET_PLAYER && cent->currentState.eType != ET_NPC) { return g2WeaponInstances[weapon]; } if (cent->currentState.eType == ET_NPC) { ci = cent->npcClient; } else { ci = &cgs.clientinfo[cent->currentState.number]; } if (!ci) { return g2WeaponInstances[weapon]; } //Try to return the custom saber instance if we can. if (ci->saber[0].model[0] && ci->ghoul2Weapons[0]) { return ci->ghoul2Weapons[0]; } //If no custom then just use the default. return g2WeaponInstances[weapon]; } // what ghoul2 model do we want to copy ? void CG_CopyG2WeaponInstance(centity_t *cent, int weaponNum, void *toGhoul2) { //rww - the -1 is because there is no "weapon" for WP_NONE assert(weaponNum < MAX_WEAPONS); if (CG_G2WeaponInstance(cent, weaponNum/*-1*/)) { if (weaponNum == WP_SABER) { clientInfo_t *ci = NULL; if (cent->currentState.eType == ET_NPC) { ci = cent->npcClient; } else { ci = &cgs.clientinfo[cent->currentState.number]; } if (!ci) { trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, weaponNum/*-1*/), 0, toGhoul2, 1); } else { //Try both the left hand saber and the right hand saber int i = 0; while (i < MAX_SABERS) { if (ci->saber[i].model[0] && ci->ghoul2Weapons[i]) { trap_G2API_CopySpecificGhoul2Model(ci->ghoul2Weapons[i], 0, toGhoul2, i+1); } else if (ci->ghoul2Weapons[i]) { //if the second saber has been removed, then be sure to remove it and free the instance. qboolean g2HasSecondSaber = trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 2); if (g2HasSecondSaber) { //remove it now since we're switching away from sabers trap_G2API_RemoveGhoul2Model(&(toGhoul2), 2); } trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[i]); } i++; } } } else { qboolean g2HasSecondSaber = trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 2); if (g2HasSecondSaber) { //remove it now since we're switching away from sabers trap_G2API_RemoveGhoul2Model(&(toGhoul2), 2); } if (weaponNum == WP_EMPLACED_GUN) { //a bit of a hack to remove gun model when using an emplaced weap if (trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 1)) { trap_G2API_RemoveGhoul2Model(&(toGhoul2), 1); } } else if (weaponNum == WP_MELEE) { //don't want a weapon on the model for this one if (trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 1)) { trap_G2API_RemoveGhoul2Model(&(toGhoul2), 1); } } else { trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, weaponNum/*-1*/), 0, toGhoul2, 1); } } } } void CG_CheckPlayerG2Weapons(playerState_t *ps, centity_t *cent) { if (!ps) { assert(0); return; } if (ps->pm_flags & PMF_FOLLOW) { return; } if (cent->currentState.eType == ET_NPC) { assert(0); return; } // should we change the gun model on this player? if (cent->currentState.saberInFlight) { cent->ghoul2weapon = CG_G2WeaponInstance(cent, 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 (cgs.clientinfo[ps->clientNum].team == TEAM_SPECTATOR || ps->persistant[PERS_TEAM] == TEAM_SPECTATOR) { cent->ghoul2weapon = cg_entities[ps->clientNum].ghoul2weapon = NULL; cent->weapon = cg_entities[ps->clientNum].weapon = 0; return; } if (cent->ghoul2 && cent->ghoul2weapon != CG_G2WeaponInstance(cent, ps->weapon) && ps->clientNum == cent->currentState.number) //don't want spectator mode forcing one client's weapon instance over another's { CG_CopyG2WeaponInstance(cent, ps->weapon, cent->ghoul2); cent->ghoul2weapon = CG_G2WeaponInstance(cent, ps->weapon); if (cent->weapon == WP_SABER && cent->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" )); if (cgs.clientinfo[ps->clientNum].saber[0].soundOff && !ps->saberHolstered) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[0].soundOff); } if (cgs.clientinfo[ps->clientNum].saber[1].soundOff && cgs.clientinfo[ps->clientNum].saber[1].model[0] && !ps->saberHolstered) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[1].soundOff); } } else if (ps->weapon == WP_SABER && cent->weapon != ps->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 (cgs.clientinfo[ps->clientNum].saber[0].soundOn) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[0].soundOn); } if (cgs.clientinfo[ps->clientNum].saber[1].soundOn) { trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[1].soundOn); } BG_SI_SetDesiredLength(&cgs.clientinfo[ps->clientNum].saber[0], 0, -1); BG_SI_SetDesiredLength(&cgs.clientinfo[ps->clientNum].saber[1], 0, -1); } cent->weapon = ps->weapon; } } /* Ghoul2 Insert End */