Quake-III-Arena-VS/source/game/g_weapon.c
2016-07-10 12:22:27 +03:00

698 lines
17 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
// g_weapon.c
// perform the server side effects of a weapon firing
#include "g_local.h"
static float s_quadFactor;
static vec3_t forward, right, up;
static vec3_t muzzle;
#define NUM_NAILSHOTS 15
/*
================
G_BounceProjectile
================
*/
void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) {
vec3_t v, newv;
float dot;
VectorSubtract( impact, start, v );
dot = DotProduct( v, dir );
VectorMA( v, -2*dot, dir, newv );
VectorNormalize(newv);
VectorMA(impact, 8192, newv, endout);
}
/*
======================================================================
GAUNTLET
======================================================================
*/
void Weapon_Gauntlet( gentity_t *ent ) {
}
/*
===============
CheckGauntletAttack
===============
*/
qboolean CheckGauntletAttack( gentity_t *ent ) {
trace_t tr;
vec3_t end;
gentity_t *tent;
gentity_t *traceEnt;
int damage;
// set aiming directions
AngleVectors (ent->client->ps.viewangles, forward, right, up);
CalcMuzzlePoint ( ent, forward, right, up, muzzle );
VectorMA (muzzle, 32, forward, end);
trap_Trace (&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT);
if ( tr.surfaceFlags & SURF_NOIMPACT ) {
return qfalse;
}
traceEnt = &g_entities[ tr.entityNum ];
// send blood impact
if ( traceEnt->takedamage && traceEnt->client ) {
tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
tent->s.otherEntityNum = traceEnt->s.number;
tent->s.eventParm = DirToByte( tr.plane.normal );
tent->s.weapon = ent->s.weapon;
}
if ( !traceEnt->takedamage) {
return qfalse;
}
if (ent->client->ps.powerups[PW_QUAD] ) {
G_AddEvent( ent, EV_POWERUP_QUAD, 0 );
s_quadFactor = g_quadfactor.value;
} else {
s_quadFactor = 1;
}
damage = 50 * s_quadFactor;
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
damage, 0, MOD_GAUNTLET );
return qtrue;
}
/*
======================================================================
MACHINEGUN
======================================================================
*/
/*
======================
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;
}
}
}
#define MACHINEGUN_SPREAD 200
#define MACHINEGUN_DAMAGE 7
#define MACHINEGUN_TEAM_DAMAGE 5 // wimpier MG in teamplay
void Bullet_Fire (gentity_t *ent, float spread, int damage ) {
trace_t tr;
vec3_t end;
float r;
float u;
gentity_t *tent;
gentity_t *traceEnt;
int i, passent;
damage *= s_quadFactor;
r = random() * M_PI * 2.0f;
u = sin(r) * crandom() * spread * 16;
r = cos(r) * crandom() * spread * 16;
VectorMA (muzzle, 8192*16, forward, end);
VectorMA (end, r, right, end);
VectorMA (end, u, up, end);
passent = ent->s.number;
for (i = 0; i < 10; i++) {
trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT);
if ( tr.surfaceFlags & SURF_NOIMPACT ) {
return;
}
traceEnt = &g_entities[ tr.entityNum ];
// snap the endpos to integers, but nudged towards the line
SnapVectorTowards( tr.endpos, muzzle );
// send bullet impact
if ( traceEnt->takedamage && traceEnt->client ) {
tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
tent->s.eventParm = traceEnt->s.number;
if( LogAccuracyHit( traceEnt, ent ) ) {
ent->client->accuracy_hits++;
}
} else {
tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
tent->s.eventParm = DirToByte( tr.plane.normal );
}
tent->s.otherEntityNum = ent->s.number;
if ( traceEnt->takedamage) {
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
damage, 0, MOD_MACHINEGUN);
}
break;
}
}
/*
======================================================================
BFG
======================================================================
*/
void BFG_Fire ( gentity_t *ent ) {
gentity_t *m;
m = fire_bfg (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
}
/*
======================================================================
SHOTGUN
======================================================================
*/
// DEFAULT_SHOTGUN_SPREAD and DEFAULT_SHOTGUN_COUNT are in bg_public.h, because
// client predicts same spreads
#define DEFAULT_SHOTGUN_DAMAGE 10
qboolean ShotgunPellet( vec3_t start, vec3_t end, gentity_t *ent ) {
trace_t tr;
int damage, i, passent;
gentity_t *traceEnt;
vec3_t tr_start, tr_end;
passent = ent->s.number;
VectorCopy( start, tr_start );
VectorCopy( end, tr_end );
for (i = 0; i < 10; i++) {
trap_Trace (&tr, tr_start, NULL, NULL, tr_end, passent, MASK_SHOT);
traceEnt = &g_entities[ tr.entityNum ];
// send bullet impact
if ( tr.surfaceFlags & SURF_NOIMPACT ) {
return qfalse;
}
if ( traceEnt->takedamage) {
damage = DEFAULT_SHOTGUN_DAMAGE * s_quadFactor;
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SHOTGUN);
if( LogAccuracyHit( traceEnt, ent ) ) {
return qtrue;
}
}
return qfalse;
}
return qfalse;
}
// this should match CG_ShotgunPattern
void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) {
int i;
float r, u;
vec3_t end;
vec3_t forward, right, up;
int oldScore;
qboolean hitClient = qfalse;
// derive the right and up vectors from the forward vector, because
// the client won't have any other information
VectorNormalize2( origin2, forward );
PerpendicularVector( right, forward );
CrossProduct( forward, right, up );
oldScore = ent->client->ps.persistant[PERS_SCORE];
// generate the "random" spread pattern
for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) {
r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16;
VectorMA( origin, 8192 * 16, forward, end);
VectorMA (end, r, right, end);
VectorMA (end, u, up, end);
if( ShotgunPellet( origin, end, ent ) && !hitClient ) {
hitClient = qtrue;
ent->client->accuracy_hits++;
}
}
}
void weapon_supershotgun_fire (gentity_t *ent) {
gentity_t *tent;
// send shotgun blast
tent = G_TempEntity( muzzle, EV_SHOTGUN );
VectorScale( forward, 4096, tent->s.origin2 );
SnapVector( tent->s.origin2 );
tent->s.eventParm = rand() & 255; // seed for spread pattern
tent->s.otherEntityNum = ent->s.number;
ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
}
/*
======================================================================
GRENADE LAUNCHER
======================================================================
*/
void weapon_grenadelauncher_fire (gentity_t *ent) {
gentity_t *m;
// extra vertical velocity
forward[2] += 0.2f;
VectorNormalize( forward );
m = fire_grenade (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
}
/*
======================================================================
ROCKET
======================================================================
*/
void Weapon_RocketLauncher_Fire (gentity_t *ent) {
gentity_t *m;
m = fire_rocket (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
}
/*
======================================================================
PLASMA GUN
======================================================================
*/
void Weapon_Plasmagun_Fire (gentity_t *ent) {
gentity_t *m;
m = fire_plasma (ent, muzzle, forward);
m->damage *= s_quadFactor;
m->splashDamage *= s_quadFactor;
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
}
/*
======================================================================
RAILGUN
======================================================================
*/
/*
=================
weapon_railgun_fire
=================
*/
#define MAX_RAIL_HITS 4
void weapon_railgun_fire (gentity_t *ent) {
vec3_t end;
trace_t trace;
gentity_t *tent;
gentity_t *traceEnt;
int damage;
int i;
int hits;
int unlinked;
int passent;
gentity_t *unlinkedEntities[MAX_RAIL_HITS];
damage = 100 * s_quadFactor;
VectorMA (muzzle, 8192, forward, end);
// trace only against the solids, so the railgun will go through people
unlinked = 0;
hits = 0;
passent = ent->s.number;
do {
trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT );
if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
break;
}
traceEnt = &g_entities[ trace.entityNum ];
if ( traceEnt->takedamage ) {
if( LogAccuracyHit( traceEnt, ent ) ) {
hits++;
}
G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN);
}
if ( trace.contents & CONTENTS_SOLID ) {
break; // we hit something solid enough to stop the beam
}
// unlink this entity, so the next trace will go past it
trap_UnlinkEntity( traceEnt );
unlinkedEntities[unlinked] = traceEnt;
unlinked++;
} while ( unlinked < MAX_RAIL_HITS );
// link back in any entities we unlinked
for ( i = 0 ; i < unlinked ; i++ ) {
trap_LinkEntity( unlinkedEntities[i] );
}
// the final trace endpos will be the terminal point of the rail trail
// snap the endpos to integers to save net bandwidth, but nudged towards the line
SnapVectorTowards( trace.endpos, muzzle );
// send railgun beam effect
tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );
// set player number for custom colors on the railtrail
tent->s.clientNum = ent->s.clientNum;
VectorCopy( muzzle, tent->s.origin2 );
// move origin a bit to come closer to the drawn gun muzzle
VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );
// no explosion at end if SURF_NOIMPACT, but still make the trail
if ( trace.surfaceFlags & SURF_NOIMPACT ) {
tent->s.eventParm = 255; // don't make the explosion at the end
} else {
tent->s.eventParm = DirToByte( trace.plane.normal );
}
tent->s.clientNum = ent->s.clientNum;
// give the shooter a reward sound if they have made two railgun hits in a row
if ( hits == 0 ) {
// complete miss
ent->client->accurateCount = 0;
} else {
// check for "impressive" reward sound
ent->client->accurateCount += hits;
if ( ent->client->accurateCount >= 2 ) {
ent->client->accurateCount -= 2;
ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
// add the sprite over the player's head
ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE;
ent->client->rewardTime = level.time + REWARD_SPRITE_TIME;
}
ent->client->accuracy_hits++;
}
}
/*
======================================================================
GRAPPLING HOOK
======================================================================
*/
void Weapon_GrapplingHook_Fire (gentity_t *ent)
{
if (!ent->client->fireHeld && !ent->client->hook)
fire_grapple (ent, muzzle, forward);
ent->client->fireHeld = qtrue;
}
void Weapon_HookFree (gentity_t *ent)
{
ent->parent->client->hook = NULL;
ent->parent->client->ps.pm_flags &= ~PMF_GRAPPLE_PULL;
G_FreeEntity( ent );
}
void Weapon_HookThink (gentity_t *ent)
{
if (ent->enemy) {
vec3_t v, oldorigin;
VectorCopy(ent->r.currentOrigin, oldorigin);
v[0] = ent->enemy->r.currentOrigin[0] + (ent->enemy->r.mins[0] + ent->enemy->r.maxs[0]) * 0.5;
v[1] = ent->enemy->r.currentOrigin[1] + (ent->enemy->r.mins[1] + ent->enemy->r.maxs[1]) * 0.5;
v[2] = ent->enemy->r.currentOrigin[2] + (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5;
SnapVectorTowards( v, oldorigin ); // save net bandwidth
G_SetOrigin( ent, v );
}
VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);
}
/*
======================================================================
LIGHTNING GUN
======================================================================
*/
void Weapon_LightningFire( gentity_t *ent ) {
trace_t tr;
vec3_t end;
gentity_t *traceEnt, *tent;
int damage, i, passent;
damage = 8 * s_quadFactor;
passent = ent->s.number;
for (i = 0; i < 10; i++) {
VectorMA( muzzle, LIGHTNING_RANGE, forward, end );
trap_Trace( &tr, muzzle, NULL, NULL, end, passent, MASK_SHOT );
if ( tr.entityNum == ENTITYNUM_NONE ) {
return;
}
traceEnt = &g_entities[ tr.entityNum ];
if ( traceEnt->takedamage) {
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
damage, 0, MOD_LIGHTNING);
}
if ( traceEnt->takedamage && traceEnt->client ) {
tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
tent->s.otherEntityNum = traceEnt->s.number;
tent->s.eventParm = DirToByte( tr.plane.normal );
tent->s.weapon = ent->s.weapon;
if( LogAccuracyHit( traceEnt, ent ) ) {
ent->client->accuracy_hits++;
}
} else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
tent->s.eventParm = DirToByte( tr.plane.normal );
}
break;
}
}
//======================================================================
/*
===============
LogAccuracyHit
===============
*/
qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
if( !target->takedamage ) {
return qfalse;
}
if ( target == attacker ) {
return qfalse;
}
if( !target->client ) {
return qfalse;
}
if( !attacker->client ) {
return qfalse;
}
if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
return qfalse;
}
if ( OnSameTeam( target, attacker ) ) {
return qfalse;
}
return qtrue;
}
/*
===============
CalcMuzzlePoint
set muzzle location relative to pivoting eye
===============
*/
void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
VectorCopy( ent->s.pos.trBase, muzzlePoint );
muzzlePoint[2] += ent->client->ps.viewheight;
VectorMA( muzzlePoint, 14, forward, muzzlePoint );
// snap to integer coordinates for more efficient network bandwidth usage
SnapVector( muzzlePoint );
}
/*
===============
CalcMuzzlePointOrigin
set muzzle location relative to pivoting eye
===============
*/
void CalcMuzzlePointOrigin ( gentity_t *ent, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
VectorCopy( ent->s.pos.trBase, muzzlePoint );
muzzlePoint[2] += ent->client->ps.viewheight;
VectorMA( muzzlePoint, 14, forward, muzzlePoint );
// snap to integer coordinates for more efficient network bandwidth usage
SnapVector( muzzlePoint );
}
/*
===============
FireWeapon
===============
*/
void FireWeapon( gentity_t *ent ) {
if (ent->client->ps.powerups[PW_QUAD] ) {
s_quadFactor = g_quadfactor.value;
} else {
s_quadFactor = 1;
}
// track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked
if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) {
ent->client->accuracy_shots++;
}
// set aiming directions
AngleVectors (ent->client->ps.viewangles, forward, right, up);
CalcMuzzlePointOrigin ( ent, ent->client->oldOrigin, forward, right, up, muzzle );
// fire the specific weapon
switch( ent->s.weapon ) {
case WP_GAUNTLET:
Weapon_Gauntlet( ent );
break;
case WP_LIGHTNING:
Weapon_LightningFire( ent );
break;
case WP_SHOTGUN:
weapon_supershotgun_fire( ent );
break;
case WP_MACHINEGUN:
if ( g_gametype.integer != GT_TEAM ) {
Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_DAMAGE );
} else {
Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_TEAM_DAMAGE );
}
break;
case WP_GRENADE_LAUNCHER:
weapon_grenadelauncher_fire( ent );
break;
case WP_ROCKET_LAUNCHER:
Weapon_RocketLauncher_Fire( ent );
break;
case WP_PLASMAGUN:
Weapon_Plasmagun_Fire( ent );
break;
case WP_RAILGUN:
weapon_railgun_fire( ent );
break;
case WP_BFG:
BFG_Fire( ent );
break;
case WP_GRAPPLING_HOOK:
Weapon_GrapplingHook_Fire( ent );
break;
default:
// FIXME G_Error( "Bad ent->s.weapon" );
break;
}
}