* Added extreme mod * `g_modBuildableHealth`: Buildable health *(Percent)* * `g_modBuildableSpeed`: Buildable fire rate *(Percent)* * `g_modHumanStamina`: Human stamina *(Percent)* * `g_modHumanHealth`: Human health *(Percent)* * `g_modAlienHealth`: Alien health *(Percent)* * `g_modHumanRate`: Human fire rate *(Percent)* * `g_modAlienRate`: Alien fire rate *(Percent)* * `g_modWeaponAmmo`: Weapon ammo per clip *(Percent)* * `g_modWeaponReload`: Adjust's Weapon reload time *(Percent)* Wiki updated as well. This matches release 1.5.0.4
1641 lines
40 KiB
C
1641 lines
40 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2000-2006 Tim Angus
|
|
|
|
This file is part of Tremulous.
|
|
|
|
Tremulous 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.
|
|
|
|
Tremulous 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 Tremulous; 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 vec3_t forward, right, up;
|
|
static vec3_t muzzle;
|
|
|
|
/*
|
|
================
|
|
G_ForceWeaponChange
|
|
================
|
|
*/
|
|
void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon )
|
|
{
|
|
int i;
|
|
|
|
if( ent )
|
|
{
|
|
ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH;
|
|
|
|
if( weapon == WP_NONE
|
|
|| !BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ))
|
|
{
|
|
//switch to the first non blaster weapon
|
|
for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
|
|
{
|
|
if( i == WP_BLASTER )
|
|
continue;
|
|
|
|
if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) )
|
|
{
|
|
ent->client->ps.persistant[ PERS_NEWWEAPON ] = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//only got the blaster to switch to
|
|
if( i == WP_NUM_WEAPONS )
|
|
ent->client->ps.persistant[ PERS_NEWWEAPON ] = WP_BLASTER;
|
|
}
|
|
else
|
|
ent->client->ps.persistant[ PERS_NEWWEAPON ] = weapon;
|
|
|
|
// force this here to prevent flamer effect from continuing
|
|
ent->client->ps.generic1 = WPM_NOTFIRING;
|
|
|
|
ent->client->ps.weapon = ent->client->ps.persistant[ PERS_NEWWEAPON ];
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_GiveClientMaxAmmo
|
|
=================
|
|
*/
|
|
void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo )
|
|
{
|
|
int i;
|
|
int maxAmmo, maxClips;
|
|
qboolean weaponType, restoredAmmo = qfalse;
|
|
|
|
for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
|
|
{
|
|
if( buyingEnergyAmmo )
|
|
weaponType = BG_FindUsesEnergyForWeapon( i );
|
|
else
|
|
weaponType = !BG_FindUsesEnergyForWeapon( i );
|
|
|
|
if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) &&
|
|
weaponType && !BG_FindInfinteAmmoForWeapon( i ) &&
|
|
!BG_WeaponIsFull( i, ent->client->ps.stats,
|
|
ent->client->ps.ammo, ent->client->ps.powerups ) )
|
|
{
|
|
BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips );
|
|
|
|
if( buyingEnergyAmmo )
|
|
{
|
|
G_AddEvent( ent, EV_RPTUSE_SOUND, 0 );
|
|
|
|
if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
|
|
maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
|
|
}
|
|
|
|
BG_PackAmmoArray( i, ent->client->ps.ammo, ent->client->ps.powerups,
|
|
maxAmmo, maxClips );
|
|
|
|
restoredAmmo = qtrue;
|
|
}
|
|
}
|
|
|
|
if( restoredAmmo )
|
|
G_ForceWeaponChange( ent, ent->client->ps.weapon );
|
|
}
|
|
|
|
/*
|
|
================
|
|
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);
|
|
}
|
|
|
|
/*
|
|
======================
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
meleeAttack
|
|
===============
|
|
*/
|
|
void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
vec3_t mins, maxs;
|
|
|
|
VectorSet( mins, -width, -width, -width );
|
|
VectorSet( maxs, width, width, width );
|
|
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
|
|
VectorMA( muzzle, range, forward, end );
|
|
|
|
G_UnlaggedOn( muzzle, range );
|
|
trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
|
|
if( tr.surfaceFlags & SURF_NOIMPACT )
|
|
return;
|
|
|
|
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;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
|
|
if( traceEnt->takedamage )
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod );
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
MACHINEGUN
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void bulletFire( gentity_t *ent, float spread, int damage, int mod )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
float r;
|
|
float u;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
|
|
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 );
|
|
|
|
// don't use unlagged if this is not a client (e.g. turret)
|
|
if( ent->client )
|
|
{
|
|
G_UnlaggedOn( muzzle, 8192 * 16 );
|
|
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
}
|
|
else
|
|
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, 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;
|
|
}
|
|
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 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
SHOTGUN
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
// 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;
|
|
trace_t tr;
|
|
gentity_t *traceEnt;
|
|
|
|
// 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 );
|
|
|
|
// generate the "random" spread pattern
|
|
for( i = 0; i < SHOTGUN_PELLETS; i++ )
|
|
{
|
|
r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
|
|
u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
|
|
VectorMA( origin, 8192 * 16, forward, end );
|
|
VectorMA( end, r, right, end );
|
|
VectorMA( end, u, up, end );
|
|
|
|
trap_Trace( &tr, origin, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
// send bullet impact
|
|
if( !( tr.surfaceFlags & SURF_NOIMPACT ) )
|
|
{
|
|
if( traceEnt->takedamage )
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, SHOTGUN_DMG, 0, MOD_SHOTGUN );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void shotgunFire( 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;
|
|
G_UnlaggedOn( muzzle, 8192 * 16 );
|
|
ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
|
|
G_UnlaggedOff();
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
MASS DRIVER
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void massDriverFire( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
|
|
VectorMA( muzzle, 8192 * 16, forward, end );
|
|
|
|
G_UnlaggedOn( muzzle, 8192 * 16 );
|
|
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
|
|
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 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;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
else
|
|
{
|
|
tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
|
|
tent->s.eventParm = DirToByte( tr.plane.normal );
|
|
tent->s.weapon = ent->s.weapon;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
|
|
if( traceEnt->takedamage )
|
|
{
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
|
|
MDRIVER_DMG, 0, MOD_MDRIVER );
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LOCKBLOB
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void lockBlobLauncherFire( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = fire_lockblob( ent, muzzle, forward );
|
|
|
|
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
HIVE
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void hiveFire( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = fire_hive( ent, muzzle, forward );
|
|
|
|
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
BLASTER PISTOL
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void blasterFire( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = fire_blaster( ent, muzzle, forward );
|
|
|
|
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
PULSE RIFLE
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void pulseRifleFire( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = fire_pulseRifle( ent, muzzle, forward );
|
|
|
|
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
FLAME THROWER
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void flamerFire( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = fire_flamer( ent, muzzle, forward );
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
GRENADE
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void throwGrenade( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = launch_grenade( ent, muzzle, forward );
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LAS GUN
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
lasGunFire
|
|
===============
|
|
*/
|
|
void lasGunFire( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
|
|
VectorMA( muzzle, 8192 * 16, forward, end );
|
|
|
|
G_UnlaggedOn( muzzle, 8192 * 16 );
|
|
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
|
|
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 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;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
else
|
|
{
|
|
tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
|
|
tent->s.eventParm = DirToByte( tr.plane.normal );
|
|
tent->s.weapon = ent->s.weapon;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
|
|
if( traceEnt->takedamage )
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, LASGUN_DAMAGE, 0, MOD_LASGUN );
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
PAIN SAW
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
void painSawFire( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
|
|
VectorMA( muzzle, PAINSAW_RANGE, forward, end );
|
|
|
|
G_UnlaggedOn( muzzle, PAINSAW_RANGE );
|
|
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
|
|
if( tr.surfaceFlags & SURF_NOIMPACT )
|
|
return;
|
|
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
// send blood impact
|
|
if( traceEnt->takedamage )
|
|
{
|
|
vec3_t temp;
|
|
|
|
//hack to get the particle system to line up with the weapon
|
|
VectorCopy( tr.endpos, temp );
|
|
temp[ 2 ] -= 10.0f;
|
|
|
|
if( traceEnt->client )
|
|
{
|
|
tent = G_TempEntity( temp, EV_MISSILE_HIT );
|
|
tent->s.otherEntityNum = traceEnt->s.number;
|
|
}
|
|
else
|
|
tent = G_TempEntity( temp, EV_MISSILE_MISS );
|
|
|
|
tent->s.eventParm = DirToByte( tr.plane.normal );
|
|
tent->s.weapon = ent->s.weapon;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
|
|
if( traceEnt->takedamage )
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW );
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LUCIFER CANNON
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
LCChargeFire
|
|
===============
|
|
*/
|
|
void LCChargeFire( gentity_t *ent, qboolean secondary )
|
|
{
|
|
gentity_t *m;
|
|
|
|
if( secondary )
|
|
{
|
|
m = fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE,
|
|
LCANNON_SECONDARY_RADIUS );
|
|
ent->client->ps.weaponTime = LCANNON_REPEAT;
|
|
}
|
|
else
|
|
{
|
|
m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS );
|
|
ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT;
|
|
}
|
|
|
|
ent->client->ps.stats[ STAT_MISC ] = 0;
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
TESLA GENERATOR
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
|
|
void teslaFire( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *traceEnt, *tent;
|
|
|
|
VectorMA( muzzle, TESLAGEN_RANGE, forward, end );
|
|
|
|
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
|
|
if( tr.entityNum == ENTITYNUM_NONE )
|
|
return;
|
|
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
if( !traceEnt->client )
|
|
return;
|
|
|
|
if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
|
|
return;
|
|
|
|
//so the client side knows
|
|
ent->s.eFlags |= EF_FIRING;
|
|
|
|
if( traceEnt->takedamage )
|
|
{
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
|
|
TESLAGEN_DMG, 0, MOD_TESLAGEN );
|
|
}
|
|
|
|
// snap the endpos to integers to save net bandwidth, but nudged towards the line
|
|
SnapVectorTowards( tr.endpos, muzzle );
|
|
|
|
// send railgun beam effect
|
|
tent = G_TempEntity( tr.endpos, EV_TESLATRAIL );
|
|
|
|
VectorCopy( muzzle, tent->s.origin2 );
|
|
|
|
tent->s.generic1 = ent->s.number; //src
|
|
tent->s.clientNum = traceEnt->s.number; //dest
|
|
|
|
// move origin a bit to come closer to the drawn gun muzzle
|
|
VectorMA( tent->s.origin2, 28, up, tent->s.origin2 );
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
BUILD GUN
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
cancelBuildFire
|
|
===============
|
|
*/
|
|
void cancelBuildFire( gentity_t *ent )
|
|
{
|
|
vec3_t forward, end;
|
|
trace_t tr;
|
|
gentity_t *traceEnt;
|
|
int bHealth;
|
|
|
|
if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE )
|
|
{
|
|
ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
|
|
return;
|
|
}
|
|
|
|
//repair buildable
|
|
if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
|
|
{
|
|
AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
|
|
VectorMA( ent->client->ps.origin, 100, forward, end );
|
|
|
|
trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
if( tr.fraction < 1.0 &&
|
|
( traceEnt->s.eType == ET_BUILDABLE ) &&
|
|
( traceEnt->biteam == ent->client->ps.stats[ STAT_PTEAM ] ) &&
|
|
( ( ent->client->ps.weapon >= WP_HBUILD2 ) &&
|
|
( ent->client->ps.weapon <= WP_HBUILD ) ) &&
|
|
traceEnt->spawned && traceEnt->health > 0 )
|
|
{
|
|
if( ent->client->ps.stats[ STAT_MISC ] > 0 )
|
|
{
|
|
G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
|
|
return;
|
|
}
|
|
|
|
bHealth = BG_FindHealthForBuildable( traceEnt->s.modelindex );
|
|
|
|
traceEnt->health += HBUILD_HEALRATE;
|
|
ent->client->pers.statscounters.repairspoisons++;
|
|
level.humanStatsCounters.repairspoisons++;
|
|
|
|
if( traceEnt->health > bHealth )
|
|
traceEnt->health = bHealth;
|
|
|
|
if( traceEnt->health == bHealth )
|
|
G_AddEvent( ent, EV_BUILD_REPAIRED, 0 );
|
|
else
|
|
G_AddEvent( ent, EV_BUILD_REPAIR, 0 );
|
|
}
|
|
}
|
|
else if( ent->client->ps.weapon == WP_ABUILD2 )
|
|
meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH,
|
|
ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); //melee attack for alien builder
|
|
}
|
|
|
|
/*
|
|
===============
|
|
buildFire
|
|
===============
|
|
*/
|
|
void buildFire( gentity_t *ent, dynMenu_t menu )
|
|
{
|
|
if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
|
|
{
|
|
if( ent->client->ps.stats[ STAT_MISC ] > 0 )
|
|
{
|
|
G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
|
|
return;
|
|
}
|
|
|
|
if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) )
|
|
{
|
|
if( g_cheats.integer )
|
|
{
|
|
ent->client->ps.stats[ STAT_MISC ] = 0;
|
|
}
|
|
else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) )
|
|
{
|
|
ent->client->ps.stats[ STAT_MISC ] +=
|
|
BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
|
|
}
|
|
else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_IsPowered( muzzle ) &&
|
|
( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack
|
|
{
|
|
ent->client->ps.stats[ STAT_MISC ] +=
|
|
BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
|
|
}
|
|
else
|
|
ent->client->ps.stats[ STAT_MISC ] +=
|
|
BG_FindBuildDelayForWeapon( ent->s.weapon );
|
|
|
|
ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
|
|
|
|
// don't want it bigger than 32k
|
|
if( ent->client->ps.stats[ STAT_MISC ] > 30000 )
|
|
ent->client->ps.stats[ STAT_MISC ] = 30000;
|
|
}
|
|
return;
|
|
}
|
|
|
|
G_TriggerMenu( ent->client->ps.clientNum, menu );
|
|
}
|
|
|
|
void slowBlobFire( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = fire_slowBlob( ent, muzzle, forward );
|
|
|
|
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LEVEL0
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
CheckVenomAttack
|
|
===============
|
|
*/
|
|
qboolean CheckVenomAttack( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
vec3_t mins, maxs;
|
|
int damage = LEVEL0_BITE_DMG;
|
|
|
|
VectorSet( mins, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH );
|
|
VectorSet( maxs, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH );
|
|
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
|
|
VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end );
|
|
|
|
G_UnlaggedOn( muzzle, LEVEL0_BITE_RANGE );
|
|
trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
|
|
if( tr.surfaceFlags & SURF_NOIMPACT )
|
|
return qfalse;
|
|
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
if( !traceEnt->takedamage )
|
|
return qfalse;
|
|
|
|
if( !traceEnt->client && !traceEnt->s.eType == ET_BUILDABLE )
|
|
return qfalse;
|
|
|
|
//allow bites to work against defensive buildables only
|
|
if( traceEnt->s.eType == ET_BUILDABLE )
|
|
{
|
|
if( traceEnt->s.modelindex != BA_H_MGTURRET &&
|
|
traceEnt->s.modelindex != BA_H_TESLAGEN )
|
|
return qfalse;
|
|
|
|
//hackery
|
|
damage *= 0.5f;
|
|
}
|
|
|
|
if( traceEnt->client )
|
|
{
|
|
if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
|
|
return qfalse;
|
|
if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )
|
|
return qfalse;
|
|
}
|
|
|
|
// 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;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LEVEL1
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
CheckGrabAttack
|
|
===============
|
|
*/
|
|
void CheckGrabAttack( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end, dir;
|
|
gentity_t *traceEnt;
|
|
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
|
|
VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end );
|
|
|
|
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
if( tr.surfaceFlags & SURF_NOIMPACT )
|
|
return;
|
|
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
if( !traceEnt->takedamage )
|
|
return;
|
|
|
|
if( traceEnt->client )
|
|
{
|
|
if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
|
|
return;
|
|
|
|
if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )
|
|
return;
|
|
|
|
if( !( traceEnt->client->ps.stats[ STAT_STATE ] & SS_GRABBED ) )
|
|
{
|
|
AngleVectors( traceEnt->client->ps.viewangles, dir, NULL, NULL );
|
|
traceEnt->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir );
|
|
|
|
//event for client side grab effect
|
|
G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 );
|
|
}
|
|
|
|
traceEnt->client->ps.stats[ STAT_STATE ] |= SS_GRABBED;
|
|
|
|
if( ent->client->ps.weapon == WP_ALEVEL1 )
|
|
traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_TIME;
|
|
else if( ent->client->ps.weapon == WP_ALEVEL1_UPG )
|
|
traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME;
|
|
}
|
|
else if( traceEnt->s.eType == ET_BUILDABLE &&
|
|
traceEnt->s.modelindex == BA_H_MGTURRET )
|
|
{
|
|
if( !traceEnt->lev1Grabbed )
|
|
G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 );
|
|
|
|
traceEnt->lev1Grabbed = qtrue;
|
|
traceEnt->lev1GrabTime = level.time;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
poisonCloud
|
|
===============
|
|
*/
|
|
void poisonCloud( gentity_t *ent )
|
|
{
|
|
int entityList[ MAX_GENTITIES ];
|
|
vec3_t range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE };
|
|
vec3_t mins, maxs;
|
|
int i, num;
|
|
gentity_t *humanPlayer;
|
|
trace_t tr;
|
|
|
|
VectorAdd( ent->client->ps.origin, range, maxs );
|
|
VectorSubtract( ent->client->ps.origin, range, mins );
|
|
|
|
G_UnlaggedOn( ent->client->ps.origin, LEVEL1_PCLOUD_RANGE );
|
|
num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
for( i = 0; i < num; i++ )
|
|
{
|
|
humanPlayer = &g_entities[ entityList[ i ] ];
|
|
|
|
if( humanPlayer->client && humanPlayer->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
|
|
{
|
|
if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, humanPlayer->client->ps.stats ) )
|
|
continue;
|
|
|
|
if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, humanPlayer->client->ps.stats ) )
|
|
continue;
|
|
|
|
trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->s.origin, humanPlayer->s.number, MASK_SHOT );
|
|
|
|
//can't see target from here
|
|
if( tr.entityNum == ENTITYNUM_WORLD )
|
|
continue;
|
|
|
|
if( !( humanPlayer->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) )
|
|
{
|
|
humanPlayer->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED;
|
|
humanPlayer->client->lastPoisonCloudedTime = level.time;
|
|
humanPlayer->client->lastPoisonCloudedClient = ent;
|
|
trap_SendServerCommand( humanPlayer->client->ps.clientNum, "poisoncloud" );
|
|
}
|
|
}
|
|
}
|
|
G_UnlaggedOff( );
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LEVEL2
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
#define MAX_ZAPS 64
|
|
|
|
static zap_t zaps[ MAX_CLIENTS ];
|
|
|
|
/*
|
|
===============
|
|
G_FindNewZapTarget
|
|
===============
|
|
*/
|
|
static gentity_t *G_FindNewZapTarget( gentity_t *ent )
|
|
{
|
|
int entityList[ MAX_GENTITIES ];
|
|
vec3_t range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE };
|
|
vec3_t mins, maxs;
|
|
int i, j, k, num;
|
|
gentity_t *enemy;
|
|
trace_t tr;
|
|
|
|
VectorScale( range, 1.0f / M_ROOT3, range );
|
|
VectorAdd( ent->s.origin, range, maxs );
|
|
VectorSubtract( ent->s.origin, range, mins );
|
|
|
|
num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
|
|
for( i = 0; i < num; i++ )
|
|
{
|
|
enemy = &g_entities[ entityList[ i ] ];
|
|
|
|
if( ( ( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
|
|
( enemy->s.eType == ET_BUILDABLE &&
|
|
BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) && enemy->health > 0 )
|
|
{
|
|
qboolean foundOldTarget = qfalse;
|
|
|
|
trap_Trace( &tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT );
|
|
|
|
//can't see target from here
|
|
if( tr.entityNum == ENTITYNUM_WORLD )
|
|
continue;
|
|
|
|
for( j = 0; j < MAX_ZAPS; j++ )
|
|
{
|
|
zap_t *zap = &zaps[ j ];
|
|
|
|
for( k = 0; k < zap->numTargets; k++ )
|
|
{
|
|
if( zap->targets[ k ] == enemy )
|
|
{
|
|
foundOldTarget = qtrue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( foundOldTarget )
|
|
break;
|
|
}
|
|
|
|
// enemy is already targetted
|
|
if( foundOldTarget )
|
|
continue;
|
|
|
|
return enemy;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_UpdateZapEffect
|
|
===============
|
|
*/
|
|
static void G_UpdateZapEffect( zap_t *zap )
|
|
{
|
|
int j;
|
|
gentity_t *effect = zap->effectChannel;
|
|
|
|
effect->s.eType = ET_LEV2_ZAP_CHAIN;
|
|
effect->classname = "lev2zapchain";
|
|
G_SetOrigin( effect, zap->creator->s.origin );
|
|
effect->s.powerups = zap->creator->s.number;
|
|
|
|
effect->s.time = effect->s.time2 = effect->s.constantLight = -1;
|
|
|
|
for( j = 0; j < zap->numTargets; j++ )
|
|
{
|
|
int number = zap->targets[ j ]->s.number;
|
|
|
|
switch( j )
|
|
{
|
|
case 0: effect->s.time = number; break;
|
|
case 1: effect->s.time2 = number; break;
|
|
case 2: effect->s.constantLight = number; break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
trap_LinkEntity( effect );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_CreateNewZap
|
|
===============
|
|
*/
|
|
static void G_CreateNewZap( gentity_t *creator, gentity_t *target )
|
|
{
|
|
int i, j;
|
|
zap_t *zap;
|
|
|
|
for( i = 0; i < MAX_ZAPS; i++ )
|
|
{
|
|
zap = &zaps[ i ];
|
|
|
|
if( !zap->used )
|
|
{
|
|
zap->used = qtrue;
|
|
|
|
zap->timeToLive = LEVEL2_AREAZAP_TIME;
|
|
if( g_modAlienRate.integer > 0 )
|
|
zap->timeToLive = zap->timeToLive * g_modAlienRate.integer / 100;
|
|
zap->damageUsed = 0;
|
|
|
|
zap->creator = creator;
|
|
|
|
zap->targets[ 0 ] = target;
|
|
zap->numTargets = 1;
|
|
|
|
for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ )
|
|
{
|
|
zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] );
|
|
|
|
if( zap->targets[ j ] )
|
|
zap->numTargets++;
|
|
}
|
|
|
|
zap->effectChannel = G_Spawn( );
|
|
G_UpdateZapEffect( zap );
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
G_UpdateZaps
|
|
===============
|
|
*/
|
|
void G_UpdateZaps( int msec )
|
|
{
|
|
int i, j;
|
|
zap_t *zap;
|
|
int damage;
|
|
|
|
for( i = 0; i < MAX_ZAPS; i++ )
|
|
{
|
|
zap = &zaps[ i ];
|
|
|
|
if( zap->used )
|
|
{
|
|
//check each target is valid
|
|
for( j = 0; j < zap->numTargets; j++ )
|
|
{
|
|
gentity_t *source;
|
|
gentity_t *target = zap->targets[ j ];
|
|
|
|
if( j == 0 )
|
|
source = zap->creator;
|
|
else
|
|
source = zap->targets[ j - 1 ];
|
|
|
|
if( target->health <= 0 || !target->inuse || //early out
|
|
Distance( source->s.origin, target->s.origin ) > LEVEL2_AREAZAP_RANGE )
|
|
{
|
|
target = zap->targets[ j ] = G_FindNewZapTarget( source );
|
|
|
|
//couldn't find a target, so forget about the rest of the chain
|
|
if( !target )
|
|
zap->numTargets = j;
|
|
}
|
|
}
|
|
|
|
if( zap->numTargets )
|
|
{
|
|
for( j = 0; j < zap->numTargets; j++ )
|
|
{
|
|
gentity_t *source;
|
|
gentity_t *target = zap->targets[ j ];
|
|
float r = 1.0f / zap->numTargets;
|
|
float damageFraction = 2 * r - 2 * j * r * r - r * r;
|
|
vec3_t forward;
|
|
|
|
if( j == 0 )
|
|
source = zap->creator;
|
|
else
|
|
source = zap->targets[ j - 1 ];
|
|
|
|
damage = ceil( ( (float)msec / LEVEL2_AREAZAP_TIME ) *
|
|
LEVEL2_AREAZAP_DMG * damageFraction );
|
|
|
|
// don't let a high msec value inflate the total damage
|
|
if( damage + zap->damageUsed > LEVEL2_AREAZAP_DMG )
|
|
damage = LEVEL2_AREAZAP_DMG - zap->damageUsed;
|
|
|
|
VectorSubtract( target->s.origin, source->s.origin, forward );
|
|
VectorNormalize( forward );
|
|
|
|
//do the damage
|
|
if( damage )
|
|
{
|
|
G_Damage( target, source, zap->creator, forward, target->s.origin,
|
|
damage, DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP );
|
|
if( g_modAlienRate.integer > 0 )
|
|
damage = damage * 100 / g_modAlienRate.integer;
|
|
zap->damageUsed += damage;
|
|
}
|
|
}
|
|
}
|
|
|
|
G_UpdateZapEffect( zap );
|
|
|
|
zap->timeToLive -= msec;
|
|
|
|
if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 )
|
|
{
|
|
zap->used = qfalse;
|
|
G_FreeEntity( zap->effectChannel );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
areaZapFire
|
|
===============
|
|
*/
|
|
void areaZapFire( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *traceEnt;
|
|
vec3_t mins, maxs;
|
|
|
|
VectorSet( mins, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH );
|
|
VectorSet( maxs, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH );
|
|
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
|
|
VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end );
|
|
|
|
G_UnlaggedOn( muzzle, LEVEL2_AREAZAP_RANGE );
|
|
trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
|
|
if( tr.surfaceFlags & SURF_NOIMPACT )
|
|
return;
|
|
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
|
|
( traceEnt->s.eType == ET_BUILDABLE &&
|
|
BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 )
|
|
{
|
|
G_CreateNewZap( ent, traceEnt );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LEVEL3
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
CheckPounceAttack
|
|
===============
|
|
*/
|
|
qboolean CheckPounceAttack( gentity_t *ent )
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
gentity_t *tent;
|
|
gentity_t *traceEnt;
|
|
int damage;
|
|
vec3_t mins, maxs;
|
|
|
|
VectorSet( mins, -LEVEL3_POUNCE_WIDTH, -LEVEL3_POUNCE_WIDTH, -LEVEL3_POUNCE_WIDTH );
|
|
VectorSet( maxs, LEVEL3_POUNCE_WIDTH, LEVEL3_POUNCE_WIDTH, LEVEL3_POUNCE_WIDTH );
|
|
|
|
if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
|
|
{
|
|
ent->client->allowedToPounce = qfalse;
|
|
ent->client->pmext.pouncePayload = 0;
|
|
}
|
|
|
|
if( !ent->client->allowedToPounce )
|
|
return qfalse;
|
|
|
|
if( ent->client->ps.weaponTime )
|
|
return qfalse;
|
|
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
|
|
VectorMA( muzzle, LEVEL3_POUNCE_RANGE, forward, end );
|
|
|
|
G_UnlaggedOn( muzzle, LEVEL3_POUNCE_RANGE );
|
|
trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
|
|
G_UnlaggedOff( );
|
|
|
|
//miss
|
|
if( tr.fraction >= 1.0 )
|
|
return qfalse;
|
|
|
|
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;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
|
|
if( !traceEnt->takedamage )
|
|
return qfalse;
|
|
|
|
damage = (int)( ( (float)ent->client->pmext.pouncePayload
|
|
/ (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG );
|
|
|
|
ent->client->pmext.pouncePayload = 0;
|
|
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
|
|
DAMAGE_NO_KNOCKBACK|DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE );
|
|
|
|
ent->client->allowedToPounce = qfalse;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
void bounceBallFire( gentity_t *ent )
|
|
{
|
|
gentity_t *m;
|
|
|
|
m = fire_bounceBall( ent, muzzle, forward );
|
|
|
|
// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
LEVEL4
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
ChargeAttack
|
|
===============
|
|
*/
|
|
void ChargeAttack( gentity_t *ent, gentity_t *victim )
|
|
{
|
|
gentity_t *tent;
|
|
int damage;
|
|
vec3_t forward, normal;
|
|
|
|
if( level.time < victim->chargeRepeat )
|
|
return;
|
|
|
|
victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT;
|
|
|
|
VectorSubtract( victim->s.origin, ent->s.origin, forward );
|
|
VectorNormalize( forward );
|
|
VectorNegate( forward, normal );
|
|
|
|
if( victim->client )
|
|
{
|
|
tent = G_TempEntity( victim->s.origin, EV_MISSILE_HIT );
|
|
tent->s.otherEntityNum = victim->s.number;
|
|
tent->s.eventParm = DirToByte( normal );
|
|
tent->s.weapon = ent->s.weapon;
|
|
tent->s.generic1 = ent->s.generic1; //weaponMode
|
|
}
|
|
|
|
if( !victim->takedamage )
|
|
return;
|
|
|
|
damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG );
|
|
|
|
G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE );
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
===============
|
|
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, 1, forward, muzzlePoint );
|
|
VectorMA( muzzlePoint, 1, right, muzzlePoint );
|
|
// snap to integer coordinates for more efficient network bandwidth usage
|
|
SnapVector( muzzlePoint );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
FireWeapon3
|
|
===============
|
|
*/
|
|
void FireWeapon3( gentity_t *ent )
|
|
{
|
|
if( ent->client )
|
|
{
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
}
|
|
else
|
|
{
|
|
AngleVectors( ent->s.angles2, forward, right, up );
|
|
VectorCopy( ent->s.pos.trBase, muzzle );
|
|
}
|
|
|
|
// fire the specific weapon
|
|
switch( ent->s.weapon )
|
|
{
|
|
case WP_ALEVEL3_UPG:
|
|
bounceBallFire( ent );
|
|
break;
|
|
|
|
case WP_ABUILD2:
|
|
slowBlobFire( ent );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
FireWeapon2
|
|
===============
|
|
*/
|
|
void FireWeapon2( gentity_t *ent )
|
|
{
|
|
if( ent->client )
|
|
{
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
}
|
|
else
|
|
{
|
|
AngleVectors( ent->s.angles2, forward, right, up );
|
|
VectorCopy( ent->s.pos.trBase, muzzle );
|
|
}
|
|
|
|
// fire the specific weapon
|
|
switch( ent->s.weapon )
|
|
{
|
|
case WP_ALEVEL1_UPG:
|
|
poisonCloud( ent );
|
|
break;
|
|
case WP_ALEVEL2_UPG:
|
|
areaZapFire( ent );
|
|
break;
|
|
|
|
case WP_LUCIFER_CANNON:
|
|
LCChargeFire( ent, qtrue );
|
|
break;
|
|
|
|
case WP_ABUILD:
|
|
case WP_ABUILD2:
|
|
case WP_HBUILD:
|
|
case WP_HBUILD2:
|
|
cancelBuildFire( ent );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
FireWeapon
|
|
===============
|
|
*/
|
|
void FireWeapon( gentity_t *ent )
|
|
{
|
|
if( ent->client )
|
|
{
|
|
// set aiming directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
}
|
|
else
|
|
{
|
|
AngleVectors( ent->turretAim, forward, right, up );
|
|
VectorCopy( ent->s.pos.trBase, muzzle );
|
|
}
|
|
|
|
// paused
|
|
if ( level.paused ) return;
|
|
|
|
// fire the specific weapon
|
|
switch( ent->s.weapon )
|
|
{
|
|
case WP_ALEVEL1:
|
|
case WP_ALEVEL1_UPG:
|
|
meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
|
|
break;
|
|
case WP_ALEVEL3:
|
|
case WP_ALEVEL3_UPG:
|
|
meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
|
|
break;
|
|
case WP_ALEVEL2:
|
|
meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
|
|
break;
|
|
case WP_ALEVEL2_UPG:
|
|
meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );
|
|
break;
|
|
case WP_ALEVEL4:
|
|
meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW );
|
|
break;
|
|
|
|
case WP_BLASTER:
|
|
blasterFire( ent );
|
|
break;
|
|
case WP_MACHINEGUN:
|
|
bulletFire( ent, RIFLE_SPREAD, RIFLE_DMG, MOD_MACHINEGUN );
|
|
break;
|
|
case WP_SHOTGUN:
|
|
shotgunFire( ent );
|
|
break;
|
|
case WP_CHAINGUN:
|
|
bulletFire( ent, CHAINGUN_SPREAD, CHAINGUN_DMG, MOD_CHAINGUN );
|
|
break;
|
|
case WP_FLAMER:
|
|
flamerFire( ent );
|
|
break;
|
|
case WP_PULSE_RIFLE:
|
|
pulseRifleFire( ent );
|
|
break;
|
|
case WP_MASS_DRIVER:
|
|
massDriverFire( ent );
|
|
break;
|
|
case WP_LUCIFER_CANNON:
|
|
LCChargeFire( ent, qfalse );
|
|
break;
|
|
case WP_LAS_GUN:
|
|
lasGunFire( ent );
|
|
break;
|
|
case WP_PAIN_SAW:
|
|
painSawFire( ent );
|
|
break;
|
|
case WP_GRENADE:
|
|
throwGrenade( ent );
|
|
break;
|
|
|
|
case WP_LOCKBLOB_LAUNCHER:
|
|
lockBlobLauncherFire( ent );
|
|
break;
|
|
case WP_HIVE:
|
|
hiveFire( ent );
|
|
break;
|
|
case WP_TESLAGEN:
|
|
teslaFire( ent );
|
|
break;
|
|
case WP_MGTURRET:
|
|
bulletFire( ent, MGTURRET_SPREAD, MGTURRET_DMG, MOD_MGTURRET );
|
|
break;
|
|
|
|
case WP_ABUILD:
|
|
case WP_ABUILD2:
|
|
buildFire( ent, MN_A_BUILD );
|
|
break;
|
|
case WP_HBUILD:
|
|
case WP_HBUILD2:
|
|
buildFire( ent, MN_H_BUILD );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void Blow_up( gentity_t *ent )
|
|
{
|
|
|
|
gentity_t *m;
|
|
|
|
|
|
// set directions
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
CalcMuzzlePoint( ent, forward, right, up, muzzle );
|
|
|
|
m = launch_grenade2( ent, muzzle, forward );
|
|
}
|