810 lines
22 KiB
C
810 lines
22 KiB
C
// Copyright (C) 2001-2002 Raven Software
|
|
//
|
|
// g_weapon.c
|
|
// perform the server side effects of a weapon firing
|
|
|
|
#include "g_local.h"
|
|
#include "be_aas.h"
|
|
|
|
extern qboolean G_BoxInBounds( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t boundsMins, vec3_t boundsMaxs );
|
|
|
|
typedef struct hitLocationConversion_s
|
|
{
|
|
int SOF2poses;
|
|
int hitLocation;
|
|
float damageMultiplier;
|
|
} hitLocationConversion_t;
|
|
|
|
#define HITLOC_HEAD_DM -1.0f
|
|
#define HITLOC_NECK_DM -2.0f
|
|
|
|
#define HL_CONV_MAX 18
|
|
|
|
hitLocationConversion_t hitLocationConversion[HL_CONV_MAX] =
|
|
{
|
|
{-1, HL_NONE ,0 }, // UNKNOWN
|
|
{0, HL_HEAD ,HITLOC_HEAD_DM }, // "HEAD"
|
|
{1, HL_LEG_LOWER_RT ,0.7f}, // "RIGHT LEG"
|
|
{4, HL_NECK ,HITLOC_NECK_DM}, // "NECK"
|
|
{8, HL_CHEST ,1.0f}, // "CHEST"
|
|
{12, HL_FOOT_LT ,0.4f}, // "RIGHT FOOT"
|
|
{16, HL_CHEST_LT ,1.0f}, // "LEFT SHOULDER"
|
|
{20, HL_ARM_LT ,0.7f}, // "LEFT ARM"
|
|
{24, HL_HAND_LT ,0.4f}, // "LEFT HAND"
|
|
{28, HL_CHEST_RT ,1.0f}, // "RIGHT SHOULDER"
|
|
{32, HL_ARM_RT ,0.7f}, // "RIGHT ARM"
|
|
{36, HL_HAND_RT ,0.4f}, // "RIGHT HAND"
|
|
{40, HL_WAIST ,1.0f}, // "GUT"
|
|
{44, HL_WAIST ,1.0f}, // "GROIN"
|
|
{48, HL_LEG_UPPER_LT ,0.7f}, // "LEFT THIGH"
|
|
{52, HL_LEG_LOWER_LT ,0.7f}, // "LEFT LEG"
|
|
{56, HL_FOOT_RT ,0.4f}, // "LEFT FOOT"
|
|
{60, HL_LEG_UPPER_RT ,0.7f}, // "RIGHT THIGH"
|
|
};
|
|
|
|
/*
|
|
===============
|
|
G_CheckSniperCall
|
|
|
|
Checks to see if the client or someone near him should yell "sniper"
|
|
===============
|
|
*/
|
|
void G_CheckSniperCall ( weapon_t weapon, gentity_t* attacker, gentity_t* target, qboolean miss )
|
|
{
|
|
vec3_t diff;
|
|
|
|
// If it was a sniper that shot this guy then play the sniper voice
|
|
if ( !G_CanVoiceGlobal ( ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Needs to have been a snipe rifle and an enemy
|
|
if ( weapon != WP_MSG90A1 || OnSameTeam ( attacker, target ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure hes not close
|
|
VectorSubtract ( attacker->r.currentOrigin, target->r.currentOrigin, diff );
|
|
if ( VectorLengthSquared ( diff ) > 1500 * 1500 )
|
|
{
|
|
gentity_t* nearby;
|
|
|
|
// Find someone close
|
|
nearby = G_FindNearbyClient ( target->r.currentOrigin, target->client->sess.team, 800, target );
|
|
|
|
// if it was a miss have the guy that was shot at say it
|
|
if ( miss )
|
|
{
|
|
if ( nearby )
|
|
{
|
|
G_VoiceGlobal ( target, "sniper", qfalse );
|
|
}
|
|
}
|
|
// Look for someone around the guy being shot at to say sniper
|
|
else if ( nearby )
|
|
{
|
|
G_VoiceGlobal ( nearby, "sniper", qfalse );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_TraceBullet
|
|
|
|
Runs a trace for a fired bullet
|
|
===============
|
|
*/
|
|
void G_TraceBullet ( weapon_t weapon, trace_t* tr, G2Trace_t G2Trace, vec3_t start, vec3_t end, int passent, int mask, qboolean detailed )
|
|
{
|
|
int unlinkCount;
|
|
gentity_t* unlinked[20];
|
|
|
|
unlinkCount = 0;
|
|
|
|
G2Trace[0].mEntityNum = -1;
|
|
|
|
while ( 1 )
|
|
{
|
|
// Run the trace as is
|
|
trap_Trace ( tr, start, NULL, NULL, end, passent, mask );
|
|
|
|
// If the bullet hit glass then break it completely for now
|
|
if ( tr->fraction != 1 && !Q_stricmp ( g_entities[ tr->entityNum ].classname, "func_glass" ) )
|
|
{
|
|
// break the glass
|
|
g_entities[ tr->entityNum ].use ( &g_entities[ tr->entityNum ], &g_entities[ tr->entityNum ], &g_entities[ tr->entityNum ] );
|
|
continue;
|
|
}
|
|
|
|
// If we hit a client, do a detailed trace
|
|
if ( detailed && tr->fraction != 1 && g_entities[ tr->entityNum ].client )
|
|
{
|
|
animation_t* anim;
|
|
gentity_t* traceEnt;
|
|
trace_t vtr;
|
|
|
|
traceEnt = &g_entities[ tr->entityNum ];
|
|
|
|
anim = &level.ghoulAnimations[traceEnt->client->legs.anim&(~ANIM_TOGGLEBIT)];
|
|
trap_G2API_SetBoneAnim(level.serverGhoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, BONE_ANIM_OVERRIDE_LOOP, 50.0f / anim->frameLerp, traceEnt->client->legs.animTime, -1, 0);
|
|
|
|
anim = &level.ghoulAnimations[traceEnt->client->torso.anim&(~ANIM_TOGGLEBIT)];
|
|
trap_G2API_SetBoneAnim(level.serverGhoul2, 0, "lower_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, BONE_ANIM_OVERRIDE_LOOP, 50.0f / anim->frameLerp, traceEnt->client->torso.animTime, -1, 0);
|
|
|
|
trap_G2API_SetBoneAngles( level.serverGhoul2, 0, "upper_lumbar", traceEnt->client->ghoulUpperTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, level.time );
|
|
trap_G2API_SetBoneAngles( level.serverGhoul2, 0, "lower_lumbar", traceEnt->client->ghoulLowerTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, level.time );
|
|
trap_G2API_SetBoneAngles( level.serverGhoul2, 0, "cranium", traceEnt->client->ghoulHeadAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, 0,0, level.time );
|
|
|
|
trap_G2API_CollisionDetect ( G2Trace, level.serverGhoul2, traceEnt->client->ghoulLegsAngles, traceEnt->r.currentOrigin, level.time, traceEnt->s.number, start, end, vec3_identity, 0, 2 );
|
|
|
|
// Check to see if anyone should yell SNIPER!
|
|
G_CheckSniperCall ( weapon, &g_entities[passent], traceEnt, G2Trace[0].mEntityNum != traceEnt->s.number ? qtrue : qfalse );
|
|
|
|
// Did we hit?
|
|
if ( G2Trace[0].mEntityNum == -1 )
|
|
{
|
|
unlinked[unlinkCount++] = traceEnt;
|
|
trap_UnlinkEntity ( traceEnt );
|
|
continue;
|
|
}
|
|
|
|
trap_UnlinkEntity ( traceEnt );
|
|
|
|
// Unfortunately because the players bounding box is doubled we have to run
|
|
// on more trace to make sure the player is actually able to be hit where they
|
|
// were hit. The bounding box is doubled to ensure that the entire ghoul model
|
|
// is accounted for, but that means the player will stick through walls too. To check
|
|
// for this we run a trace from the hit location on the big bounding box to the
|
|
// hit location of the ghoul model and make sure there is nothing solid in the way
|
|
trap_Trace ( &vtr, tr->endpos, NULL, NULL, G2Trace[0].mCollisionPosition, passent, MASK_SHOT&(~CONTENTS_BODY) );
|
|
if ( vtr.entityNum != G2Trace[0].mEntityNum )
|
|
{
|
|
if ( vtr.entityNum != ENTITYNUM_NONE )
|
|
{
|
|
// Must have hit something, discount the trace
|
|
unlinked[unlinkCount++] = traceEnt;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
trap_LinkEntity ( traceEnt );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Relink all the unlinked entities
|
|
while ( unlinkCount > 0 )
|
|
{
|
|
trap_LinkEntity ( unlinked[--unlinkCount] );
|
|
}
|
|
}
|
|
|
|
#define MAX_HITS 20
|
|
|
|
typedef struct gbullethit_s
|
|
{
|
|
gentity_t* ent;
|
|
vec3_t origin;
|
|
int damage;
|
|
int location;
|
|
|
|
} gbullethit_t;
|
|
|
|
/*
|
|
===============
|
|
G_FireBullet
|
|
|
|
Fires a bullet from the given player entity using the given weapon and attack
|
|
===============
|
|
*/
|
|
void G_FireBullet ( gentity_t* ent, int weapon, int attack )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
int i;
|
|
vec3_t muzzlePoint;
|
|
vec3_t fwd;
|
|
vec3_t right;
|
|
vec3_t up;
|
|
vec3_t fireAngs;
|
|
float damageMult;
|
|
int hitcount;
|
|
G2Trace_t G2Trace;
|
|
|
|
int maxFx;
|
|
weaponData_t* weaponDat;
|
|
attackData_t* attackDat;
|
|
float inaccuracy;
|
|
qboolean detailed;
|
|
int seed;
|
|
|
|
gbullethit_t hit[MAX_HITS];
|
|
|
|
// Grab the firing info
|
|
weaponDat = &weaponData[ent->s.weapon];
|
|
attackDat = &weaponDat->attack[attack];
|
|
|
|
// Maximum effects that can be spawned from this bullet
|
|
maxFx = (attackDat->pellets / 2) + 1;
|
|
hitcount = 0;
|
|
|
|
// Detailed trace or not?
|
|
detailed = (attackDat->pellets > 1 || attackDat->melee) ? qfalse : qtrue;
|
|
|
|
// Current inaccuracy
|
|
inaccuracy = (float)ent->client->ps.inaccuracy / 1000.0f;
|
|
if ( detailed )
|
|
{
|
|
if ( ent->client->ps.pm_flags & PMF_JUMPING )
|
|
{
|
|
inaccuracy *= JUMP_ACCURACY_MODIFIER;
|
|
}
|
|
else if ( ent->client->ps.pm_flags & PMF_DUCKED )
|
|
{
|
|
inaccuracy *= DUCK_ACCURACY_MODIFIER;
|
|
}
|
|
}
|
|
|
|
// Anti-lag
|
|
G_ApplyAntiLag ( ent, detailed );
|
|
|
|
// where is gun muzzle?
|
|
VectorCopy( ent->client->ps.origin, muzzlePoint );
|
|
muzzlePoint[2] += ent->client->ps.viewheight;
|
|
|
|
// Handle leaning
|
|
VectorCopy(ent->client->ps.viewangles, fireAngs);
|
|
|
|
AngleVectors( fireAngs, fwd, right, up);
|
|
|
|
if ( ent->client->ps.pm_flags & PMF_LEANING )
|
|
{
|
|
BG_ApplyLeanOffset ( &ent->client->ps, muzzlePoint );
|
|
}
|
|
|
|
// Move the start trace back a bit to account for bumping up against someone
|
|
VectorMA ( muzzlePoint, -15, fwd, muzzlePoint );
|
|
|
|
seed = ent->client->ps.stats[STAT_SEED];
|
|
|
|
// Run a trace for each pellet being fired
|
|
for (i = 0; i < attackDat->pellets; i++)
|
|
{
|
|
int location = HL_NONE;
|
|
|
|
// Determine the endpoint for the bullet
|
|
BG_CalculateBulletEndpoint ( muzzlePoint, fireAngs, inaccuracy, attackDat->rV.range + 15, end, &seed );
|
|
|
|
// Trace the bullet
|
|
G_TraceBullet ( weapon, &tr, G2Trace, muzzlePoint, end, ent->s.number, MASK_SHOT, detailed );
|
|
|
|
if ( (tr.surfaceFlags & SURF_NOIMPACT) || tr.entityNum == ENTITYNUM_NONE )
|
|
{
|
|
// a big miss
|
|
continue;
|
|
}
|
|
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
// snap the endpos to integers, but nudged towards the line
|
|
SnapVectorTowards( tr.endpos, muzzlePoint );
|
|
|
|
damageMult = 1.0f;
|
|
if ( traceEnt->takedamage )
|
|
{
|
|
// Where did the bullet hit?
|
|
if ( ent->client && traceEnt->client )
|
|
{
|
|
// Add or subtract some damage depending on where it hits
|
|
if ( G2Trace[0].mEntityNum == -1 )
|
|
{
|
|
vec3_t dir;
|
|
|
|
VectorSubtract ( end, muzzlePoint, dir );
|
|
VectorNormalize ( dir );
|
|
|
|
location = G_GetHitLocation ( traceEnt, muzzlePoint, dir );
|
|
|
|
switch ( location )
|
|
{
|
|
case HL_FOOT_RT:
|
|
case HL_FOOT_LT:
|
|
case HL_HAND_RT:
|
|
case HL_HAND_LT:
|
|
damageMult = 0.3f;
|
|
break;
|
|
|
|
case HL_ARM_RT:
|
|
case HL_ARM_LT:
|
|
case HL_LEG_UPPER_RT:
|
|
case HL_LEG_UPPER_LT:
|
|
case HL_LEG_LOWER_RT:
|
|
case HL_LEG_LOWER_LT:
|
|
damageMult = 0.7f;
|
|
break;
|
|
|
|
case HL_HEAD:
|
|
case HL_NECK:
|
|
damageMult = 1.75f;
|
|
break;
|
|
|
|
default:
|
|
case HL_BACK_RT:
|
|
case HL_BACK_LT:
|
|
case HL_BACK:
|
|
case HL_CHEST_RT:
|
|
case HL_CHEST_LT:
|
|
case HL_CHEST:
|
|
case HL_WAIST:
|
|
damageMult = 1.0f;
|
|
break;
|
|
|
|
case HL_NONE:
|
|
damageMult = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int z;
|
|
float accuracyRatio;
|
|
float maxinaccuracy;
|
|
float addinaccuracy;
|
|
|
|
location = HL_NONE;
|
|
damageMult = 0;
|
|
accuracyRatio = 1.0f;
|
|
maxinaccuracy = attackDat->maxInaccuracy / 1000.0f;
|
|
addinaccuracy = attackDat->inaccuracy / 1000.0f;
|
|
|
|
if ( maxinaccuracy )
|
|
{
|
|
// Calculate the ratio of how far along the inaccuracy spread they are
|
|
accuracyRatio = 1.0f - ((inaccuracy - addinaccuracy) / (maxinaccuracy));
|
|
accuracyRatio = Com_Clampf ( 0.0f, 1.0f, accuracyRatio );
|
|
}
|
|
|
|
for ( z = 0; z < MAX_G2_COLLISIONS && G2Trace[z].mEntityNum != -1; z ++ )
|
|
{
|
|
int temp_location;
|
|
float temp_damageMult;
|
|
int pose;
|
|
int l;
|
|
|
|
pose = ( G2Trace[z].mLocation >> 2 );
|
|
temp_damageMult = 0.0f;
|
|
temp_location = HL_NONE;
|
|
|
|
// Convert hitregions (SOF2.poses) to hitlocation
|
|
for ( l=0; l < HL_CONV_MAX; l++)
|
|
{
|
|
// Found the SOF2pose
|
|
if (hitLocationConversion[l].SOF2poses == pose )
|
|
{
|
|
temp_location = hitLocationConversion[l].hitLocation;
|
|
|
|
// Special cases
|
|
if (hitLocationConversion[l].damageMultiplier==HITLOC_HEAD_DM) // Head
|
|
{
|
|
temp_damageMult = 1.0f + 2.0f * accuracyRatio;
|
|
}
|
|
else if (hitLocationConversion[l].damageMultiplier==HITLOC_NECK_DM) // Neck
|
|
{
|
|
temp_damageMult = 1.0f + 0.75f * accuracyRatio;
|
|
}
|
|
else
|
|
{
|
|
temp_damageMult = hitLocationConversion[l].damageMultiplier;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Didn't find it? Default to waist
|
|
if (temp_location == HL_NONE)
|
|
{
|
|
temp_location = HL_WAIST;
|
|
|
|
// Search to find waist damageMultiplier
|
|
for ( l=0; l < HL_CONV_MAX; l++)
|
|
{
|
|
if (temp_location == hitLocationConversion[l].hitLocation)
|
|
{
|
|
temp_damageMult = hitLocationConversion[l].damageMultiplier;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( temp_damageMult > damageMult )
|
|
{
|
|
location = temp_location;
|
|
damageMult = temp_damageMult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If a range attack then do distance affected damage, but exclude close range attacks
|
|
if ( attackDat->rV.range > 100 )
|
|
{
|
|
vec3_t diff;
|
|
float dist;
|
|
float checkrange;
|
|
|
|
VectorSubtract ( muzzlePoint, tr.endpos, diff );
|
|
|
|
dist = VectorLength ( diff );
|
|
checkrange = attackDat->rV.range * 0.5f;
|
|
|
|
// If half the way through its range then start falling off the damage to 1 quarter damage
|
|
if ( dist > attackDat->rV.range * 0.5f )
|
|
{
|
|
dist -= (attackDat->rV.range * 0.5f);
|
|
|
|
// Scale oof the damage
|
|
damageMult *= (1.0f - (dist / (float)checkrange));
|
|
}
|
|
}
|
|
|
|
// we hit something that noticed, so that is enough pellets
|
|
if ( (int)(attackDat->damage * damageMult) > 0 )
|
|
{
|
|
if ( !attackDat->melee )
|
|
{
|
|
damageMult *= 1.15f;
|
|
}
|
|
|
|
hit[hitcount].ent = traceEnt;
|
|
hit[hitcount].damage = attackDat->damage * damageMult;
|
|
hit[hitcount].location = location;
|
|
VectorCopy ( tr.endpos, hit[hitcount].origin );
|
|
|
|
hitcount++;
|
|
}
|
|
}
|
|
|
|
// send bullet impact
|
|
if (i < maxFx && attackDat->damage * damageMult > 0.0f )
|
|
{
|
|
qboolean flesh = qfalse;
|
|
|
|
// Are they are a client?
|
|
if ( traceEnt->client )
|
|
{
|
|
tr.surfaceFlags = 0;
|
|
|
|
// Invulnerable?
|
|
if ( level.time - traceEnt->client->invulnerableTime >= g_respawnInvulnerability.integer * 1000 )
|
|
{
|
|
// Shot my a teammate with ff off?
|
|
if ( !level.gametypeData->teams || (ent->client && !(OnSameTeam(ent,traceEnt) && (!g_friendlyFire.integer || level.warmupTime) ) ) )
|
|
{
|
|
flesh = qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If it was a client that was hit and it wasnt by a teammate when friendly fire is off then send blood!
|
|
if ( flesh )
|
|
{
|
|
tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
|
|
|
|
// send entity and direction
|
|
tent->s.eventParm = DirToByte( fwd );
|
|
tent->s.otherEntityNum = ent->s.number;
|
|
tent->s.otherEntityNum2 = traceEnt->s.number;
|
|
|
|
// Pack the shot info into the temp end for gore
|
|
tent->s.time = weapon + ((attack&0xFF)<<8) + ((((int)traceEnt->s.apos.trBase[YAW]&0x7FFF) % 360) << 16);
|
|
VectorCopy ( traceEnt->r.currentOrigin, tent->s.angles );
|
|
SnapVector ( tent->s.angles );
|
|
|
|
// Some procedural gore should be ignored because it would look odd on the player. For
|
|
// example, if someone gets shot in the head but doesnt die from it.
|
|
if ( traceEnt->client->ps.stats[STAT_HEALTH] > 0 )
|
|
{
|
|
if ( location & (HL_HEAD) )
|
|
{
|
|
tent->s.time |= GORE_NONE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
|
|
// send direction and material
|
|
tent->s.eventParm = DirToByte( tr.plane.normal );
|
|
tent->s.eventParm <<= MATERIAL_BITS;
|
|
tent->s.eventParm |= (tr.surfaceFlags & MATERIAL_MASK);
|
|
tent->s.time = weapon + ((attack&0xFF)<<8);
|
|
tent->r.detailTime = level.time + rand() % 1000;
|
|
tent->r.svFlags |= SVF_DETAIL;
|
|
}
|
|
|
|
tent->s.otherEntityNum = ent->s.number;
|
|
}
|
|
}
|
|
|
|
// Push all the clients back to the real timeframe. We need to do this
|
|
// before damaging the client so the real bounding box and location are stored in
|
|
// the body
|
|
G_UndoAntiLag ( );
|
|
|
|
if ( hitcount )
|
|
{
|
|
int flags;
|
|
int h;
|
|
|
|
flags = 0;
|
|
flags |= (attackDat->gore ? 0 : DAMAGE_NO_GORE);
|
|
flags |= (attackDat->melee ? DAMAGE_NO_ARMOR : 0);
|
|
flags |= (hitcount > 2 ? DAMAGE_FORCE_GORE : 0 );
|
|
|
|
for ( h = 0; h < hitcount; h ++ )
|
|
{
|
|
G_Damage( hit[h].ent, ent, ent, fwd, hit[h].origin, hit[h].damage, flags, attackDat->mod + (attack<<8), hit[h].location );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_FireProjectile
|
|
|
|
Fires a projectile from the given client entity
|
|
===============
|
|
*/
|
|
gentity_t* G_FireProjectile ( gentity_t *ent, weapon_t weapon, attackType_t attack, int projectileLifetime, int flags )
|
|
{
|
|
int i;
|
|
vec3_t muzzlePoint;
|
|
vec3_t fwd, right, up, fireAngs;
|
|
gentity_t* missile = NULL;
|
|
weaponData_t* weaponDat;
|
|
attackData_t* attackDat;
|
|
float inaccuracy;
|
|
|
|
// Grab the firing info
|
|
weaponDat = &weaponData[ent->s.weapon];
|
|
attackDat = &weaponDat->attack[attack];
|
|
|
|
inaccuracy = attackDat->inaccuracy / 1000.0f;
|
|
|
|
// where is gun muzzle?
|
|
VectorCopy( ent->client->ps.origin, muzzlePoint );
|
|
muzzlePoint[2] += ent->client->ps.viewheight;
|
|
|
|
// Inform of the grenade toss if its a timed grenade
|
|
if ( weapon >= WP_M84_GRENADE && weapon < WP_M15_GRENADE && (flags&PROJECTILE_TIMED) && (ent->client->ps.pm_type == PM_NORMAL) )
|
|
{
|
|
gentity_t* nearby;
|
|
|
|
// Make sure there is someone nearby to hear you yell
|
|
nearby = G_FindNearbyClient ( ent->r.currentOrigin, ent->client->sess.team, 1200, ent );
|
|
if ( nearby )
|
|
{
|
|
G_VoiceGlobal ( ent, "fire_hole", qtrue );
|
|
}
|
|
}
|
|
|
|
// snap to integer coordinates for more efficient network bandwidth usage
|
|
SnapVector( muzzlePoint );
|
|
|
|
VectorCopy(ent->client->ps.viewangles, fireAngs);
|
|
|
|
if ( ent->client->ps.pm_flags & PMF_LEANING )
|
|
{
|
|
BG_ApplyLeanOffset ( &ent->client->ps, muzzlePoint );
|
|
}
|
|
|
|
AngleVectors( fireAngs, fwd, right, up );
|
|
|
|
for (i = 0; i < attackDat->pellets; i++)
|
|
{
|
|
vec3_t dir;
|
|
VectorCopy( fwd, dir );
|
|
if ( inaccuracy != 0)
|
|
{ // add in some spread / scatter
|
|
dir[0] += flrand(-0.1 * inaccuracy, 0.1 * inaccuracy);
|
|
dir[1] += flrand(-0.1 * inaccuracy, 0.1 * inaccuracy);
|
|
dir[2] += flrand(-0.1 * inaccuracy, 0.1 * inaccuracy);
|
|
}
|
|
|
|
missile = G_CreateMissile( muzzlePoint, dir, attackDat->rV.velocity, min(projectileLifetime, 10000), ent, attack );
|
|
|
|
missile->classname = ammoData[attackDat->ammoIndex].name;
|
|
missile->s.weapon = weapon;
|
|
|
|
VectorSet( missile->r.maxs, 1, 1, 1 );
|
|
VectorScale( missile->r.maxs, -1, missile->r.mins );
|
|
|
|
missile->damage = attackDat->damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_ARMOR;
|
|
missile->dflags |= (attackDat->gore ? 0 : DAMAGE_NO_GORE);
|
|
missile->methodOfDeath = attackDat->mod + (attack<<8);
|
|
missile->clipmask = MASK_SHOT | CONTENTS_MISSILECLIP;
|
|
if(attackDat->splashRadius)
|
|
{
|
|
missile->splashDamage = attackDat->damage;
|
|
missile->splashRadius = attackDat->splashRadius;
|
|
missile->splashMethodOfDeath = missile->methodOfDeath;
|
|
}
|
|
else
|
|
{
|
|
missile->splashDamage = 0;
|
|
missile->splashRadius = 0;
|
|
missile->splashMethodOfDeath = MOD_UNKNOWN; //??
|
|
}
|
|
|
|
if (flags & PROJECTILE_GRAVITY)
|
|
{
|
|
missile->s.pos.trType = TR_GRAVITY;
|
|
}
|
|
else if (flags & PROJECTILE_LIGHTGRAVITY)
|
|
{
|
|
missile->s.pos.trType = TR_LIGHTGRAVITY;
|
|
}
|
|
|
|
if (flags & PROJECTILE_TIMED)
|
|
{
|
|
missile->s.eFlags |= EF_BOUNCE_SCALE;
|
|
missile->think = G_GrenadeThink;
|
|
missile->bounceScale = attackDat->bounceScale;
|
|
}
|
|
else
|
|
{
|
|
// we don't want it to bounce, just blow up
|
|
missile->bounceScale = 0;
|
|
missile->think = G_ExplodeMissile;
|
|
}
|
|
|
|
if (flags & PROJECTILE_DAMAGE_AREA)
|
|
{ // add area damage over time
|
|
missile->damage /= 2;
|
|
missile->dflags |= DAMAGE_AREA_DAMAGE;
|
|
}
|
|
}
|
|
|
|
// Angle override on the knife
|
|
if ( weapon == WP_KNIFE )
|
|
{
|
|
missile->s.eFlags |= EF_ANGLE_OVERRIDE;
|
|
}
|
|
|
|
return missile;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
SnapVectorTowards
|
|
|
|
Round a vector to integers for more efficient network
|
|
transmission, but make sure that it rounds towards a given point
|
|
rather than blindly truncating. This prevents it from truncating
|
|
into a wall.
|
|
======================
|
|
*/
|
|
void SnapVectorTowards( vec3_t v, vec3_t to ) {
|
|
int i;
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
if ( to[i] <= v[i] ) {
|
|
v[i] = (int)v[i];
|
|
} else {
|
|
v[i] = (int)v[i] + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_FireWeapon
|
|
|
|
Fires either a bullet or a projectile
|
|
===============
|
|
*/
|
|
gentity_t* G_FireWeapon( gentity_t *ent, attackType_t attack )
|
|
{
|
|
weaponData_t *weaponDat;
|
|
attackData_t *attackDat;
|
|
int projectileLifetime;
|
|
int flags;
|
|
|
|
weaponDat = &weaponData[ent->s.weapon];
|
|
attackDat = &weaponDat->attack[attack];
|
|
flags = attackDat->weaponFlags;
|
|
|
|
// Determine lifetime of projectile. Modified by how long we've been holding it!
|
|
if( !ent->client->ps.grenadeTimer )
|
|
{
|
|
projectileLifetime = attackDat->projectileLifetime;
|
|
}
|
|
else
|
|
{
|
|
projectileLifetime = ent->client->ps.grenadeTimer;
|
|
|
|
// Less than 50 milliseconds will just cause it to blow up in your hand
|
|
if ( projectileLifetime < 50 )
|
|
{
|
|
flags &= ~PROJECTILE_TIMED;
|
|
}
|
|
}
|
|
|
|
// Clear the grenade timer
|
|
ent->client->ps.grenadeTimer = 0;
|
|
|
|
if ( attackDat->weaponFlags & PROJECTILE_FIRE)
|
|
{
|
|
return G_FireProjectile ( ent, ent->s.weapon, attack, projectileLifetime, flags );
|
|
}
|
|
else
|
|
{
|
|
G_FireBullet ( ent, ent->s.weapon, attack );
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_InitHitModel
|
|
===============
|
|
*/
|
|
void* G_InitHitModel ( void )
|
|
{
|
|
void* ghoul2;
|
|
char temp[20480];
|
|
int numPairs;
|
|
qhandle_t handle;
|
|
|
|
ghoul2 = NULL;
|
|
|
|
// Initialize the ghoul2 model
|
|
trap_G2API_InitGhoul2Model ( &ghoul2,
|
|
"models/characters/average_sleeves/average_sleeves.glm",
|
|
0, 0, 0, (1<<4), 2 );
|
|
|
|
// Verify it
|
|
if ( !ghoul2 )
|
|
{
|
|
return ghoul2;
|
|
}
|
|
|
|
// Parse the skin file that will be used for hit information
|
|
numPairs = BG_ParseSkin ( "models/characters/skins/col_rebel_h1.g2skin", temp, sizeof(temp) );
|
|
if ( !numPairs )
|
|
{
|
|
trap_G2API_CleanGhoul2Models ( &ghoul2 );
|
|
return NULL;
|
|
}
|
|
|
|
// Register the skin and attach it to the ghoul model
|
|
handle = trap_G2API_RegisterSkin( "hitmodel", numPairs, temp );
|
|
trap_G2API_SetSkin( ghoul2, 0, handle );
|
|
|
|
// Read in the animations for this model
|
|
trap_G2API_GetAnimFileNameIndex( ghoul2, 0, temp );
|
|
BG_ParseAnimationFile ( va("%s_mp.cfg", temp), level.ghoulAnimations );
|
|
|
|
// Hand the hit model back
|
|
return ghoul2;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_InitHitModels
|
|
===============
|
|
*/
|
|
void G_InitHitModels ( void )
|
|
{
|
|
level.serverGhoul2 = G_InitHitModel ( );
|
|
}
|
|
|
|
|