summaryrefslogtreecommitdiff
path: root/src/game/g_weapon.c
diff options
context:
space:
mode:
authorIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
committerIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
commit425decdf7e9284d15aa726e3ae96b9942fb0e3ea (patch)
tree6c0dd7edfefff1be7b9e75fe0b3a0a85fe1595f3 /src/game/g_weapon.c
parentccb0b2e4d6674a7a00c9bf491f08fc73b6898c54 (diff)
create tremded branch
Diffstat (limited to 'src/game/g_weapon.c')
-rw-r--r--src/game/g_weapon.c1158
1 files changed, 552 insertions, 606 deletions
diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c
index ddad4af..302c47e 100644
--- a/src/game/g_weapon.c
+++ b/src/game/g_weapon.c
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,8 +17,8 @@ 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
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
@@ -36,51 +37,24 @@ G_ForceWeaponChange
*/
void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon )
{
- int i;
-
- if( !ent )
- return;
-
- if( ent->client->ps.weaponstate == WEAPON_RELOADING )
- {
- ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE;
- ent->client->ps.weaponTime = 250;
- ent->client->ps.weaponstate = WEAPON_READY;
- }
-
- ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH;
+ playerState_t *ps = &ent->client->ps;
- if( weapon == WP_NONE
- || !BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ))
+ // stop a reload in progress
+ if( ps->weaponstate == WEAPON_RELOADING )
{
- //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;
+ ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE;
+ ps->weaponTime = 250;
+ ps->weaponstate = WEAPON_READY;
}
- else
- ent->client->ps.persistant[ PERS_NEWWEAPON ] = weapon;
- // Lak: The following hack has been moved to PM_BeginWeaponChange, but I'm going to
- // redundantly leave it here as well just in case there's a case I'm forgetting
- // because I don't want to face the gameplay consequences such an error would have
+ ps->persistant[ PERS_NEWWEAPON ] = ( weapon == WP_BLASTER ) ?
+ WP_BLASTER : ps->stats[ STAT_WEAPON ];
// force this here to prevent flamer effect from continuing
- ent->client->ps.generic1 = WPM_NOTFIRING;
+ ps->generic1 = WPM_NOTFIRING;
- ent->client->ps.weapon = ent->client->ps.persistant[ PERS_NEWWEAPON ];
+ // The PMove will do an animated drop, raise, and set the new weapon
+ ps->pm_flags |= PMF_WEAPON_SWITCH;
}
/*
@@ -90,43 +64,35 @@ G_GiveClientMaxAmmo
*/
void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo )
{
- int i;
- int maxAmmo, maxClips;
- qboolean weaponType, restoredAmmo = qfalse;
+ int maxAmmo;
+ weapon_t weapon = ent->client->ps.stats[ STAT_WEAPON ];
- // GH FIXME
+ if( BG_Weapon( weapon )->infiniteAmmo )
+ return;
- for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
- {
- if( buyingEnergyAmmo )
- weaponType = BG_FindUsesEnergyForWeapon( i );
- else
- weaponType = !BG_FindUsesEnergyForWeapon( i );
+ if( buyingEnergyAmmo && !BG_Weapon( weapon )->usesEnergy )
+ return;
- 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.clips ) )
- {
- BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips );
+ if( BG_WeaponIsFull( weapon, ent->client->ps.stats, ent->client->ps.ammo,
+ ent->client->ps.clips ) )
+ return;
- if( buyingEnergyAmmo )
- {
- G_AddEvent( ent, EV_RPTUSE_SOUND, 0 );
+ maxAmmo = BG_Weapon( weapon )->maxAmmo;
- if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
- maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
- }
+ // Apply battery pack modifier
+ if( BG_Weapon( weapon )->usesEnergy &&
+ BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
+ {
+ maxAmmo *= BATTPACK_MODIFIER;
+ }
- ent->client->ps.ammo = maxAmmo;
- ent->client->ps.clips = maxClips;
+ ent->client->ps.ammo = maxAmmo;
+ ent->client->ps.clips = BG_Weapon( weapon )->maxClips;
- restoredAmmo = qtrue;
- }
- }
+ G_ForceWeaponChange( ent, ent->client->ps.weapon );
- if( restoredAmmo )
- G_ForceWeaponChange( ent, ent->client->ps.weapon );
+ if( BG_Weapon( weapon )->usesEnergy )
+ G_AddEvent( ent, EV_RPTUSE_SOUND, 0 );
}
/*
@@ -155,50 +121,46 @@ Trace a bounding box against entities, but not the world
Also check there is a line of sight between the start and end point
================
*/
-static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, float width, gentity_t **target )
+static void G_WideTrace( trace_t *tr, gentity_t *ent, float range,
+ float width, float height, gentity_t **target )
{
vec3_t mins, maxs;
vec3_t end;
- VectorSet( mins, -width, -width, -width );
- VectorSet( maxs, width, width, width );
+ VectorSet( mins, -width, -width, -height );
+ VectorSet( maxs, width, width, height );
*target = NULL;
if( !ent->client )
return;
- // Set aiming directions
- AngleVectors( ent->client->ps.viewangles, forward, right, up );
- CalcMuzzlePoint( ent, forward, right, up, muzzle );
- VectorMA( muzzle, range, forward, end );
+ G_UnlaggedOn( ent, muzzle, range + width );
- G_UnlaggedOn( ent, muzzle, range );
+ VectorMA( muzzle, range, forward, end );
// Trace against entities
trap_Trace( tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY );
if( tr->entityNum != ENTITYNUM_NONE )
- {
*target = &g_entities[ tr->entityNum ];
- // Set range to the trace length plus the width, so that the end of the
- // LOS trace is close to the exterior of the target's bounding box
- range = Distance( muzzle, tr->endpos ) + width;
- VectorMA( muzzle, range, forward, end );
+ // Set range to the trace length plus the width, so that the end of the
+ // LOS trace is close to the exterior of the target's bounding box
+ range = Distance( muzzle, tr->endpos ) + width;
+ VectorMA( muzzle, range, forward, end );
- // Trace for line of sight against the world
- trap_Trace( tr, muzzle, NULL, NULL, end, 0, CONTENTS_SOLID );
- if( tr->fraction < 1.0f )
- *target = NULL;
- }
+ // Trace for line of sight against the world
+ trap_Trace( tr, muzzle, NULL, NULL, end, ent->s.number, CONTENTS_SOLID );
+ if( tr->entityNum != ENTITYNUM_NONE )
+ *target = &g_entities[ tr->entityNum ];
G_UnlaggedOff( );
}
-
/*
======================
SnapVectorTowards
+SnapVectorNormal
Round a vector to integers for more efficient network
transmission, but make sure that it rounds towards a given point
@@ -212,57 +174,120 @@ void SnapVectorTowards( vec3_t v, vec3_t to )
for( i = 0 ; i < 3 ; i++ )
{
- if( to[ i ] <= v[ i ] )
- v[ i ] = (int)v[ i ];
+ if( v[ i ] >= 0 )
+ v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? 0 : 1 ) );
+ else
+ v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? -1 : 0 ) );
+ }
+}
+
+void SnapVectorNormal( vec3_t v, vec3_t normal )
+{
+ int i;
+
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ if( v[ i ] >= 0 )
+ v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? 0 : 1 ) );
else
- v[ i ] = (int)v[ i ] + 1;
+ v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? -1 : 0 ) );
}
}
/*
===============
-meleeAttack
+BloodSpurt
+
+Generates a blood spurt event for traces with accurate end points
===============
*/
-void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod )
+static void BloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr )
{
- 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 );
+ if( !attacker->client )
+ return;
- // set aiming directions
- AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ if( victim->health <= 0 )
+ return;
- CalcMuzzlePoint( ent, forward, right, up, muzzle );
+ tent = G_TempEntity( tr->endpos, EV_MISSILE_HIT );
+ tent->s.otherEntityNum = victim->s.number;
+ tent->s.eventParm = DirToByte( tr->plane.normal );
+ tent->s.weapon = attacker->s.weapon;
+ tent->s.generic1 = attacker->s.generic1; // weaponMode
+}
- VectorMA( muzzle, range, forward, end );
+/*
+===============
+WideBloodSpurt
- G_UnlaggedOn( ent, muzzle, range );
- trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
- G_UnlaggedOff( );
+Calculates the position of a blood spurt for wide traces and generates an event
+===============
+*/
+static void WideBloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr )
+{
+ gentity_t *tent;
+ vec3_t normal, origin;
+ float mag, radius;
- if( tr.surfaceFlags & SURF_NOIMPACT )
+ if( !attacker->client )
return;
- traceEnt = &g_entities[ tr.entityNum ];
+ if( victim->health <= 0 )
+ return;
- // send blood impact
- if( traceEnt->takedamage && traceEnt->client )
+ if( tr )
+ VectorSubtract( tr->endpos, victim->r.currentOrigin, normal );
+ else
+ VectorSubtract( attacker->client->ps.origin,
+ victim->r.currentOrigin, normal );
+
+ // Normalize the horizontal components of the vector difference to the
+ // "radius" of the bounding box
+ mag = sqrt( normal[ 0 ] * normal[ 0 ] + normal[ 1 ] * normal[ 1 ] );
+ radius = victim->r.maxs[ 0 ] * 1.21f;
+ if( mag > radius )
{
- 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
+ normal[ 0 ] = normal[ 0 ] / mag * radius;
+ normal[ 1 ] = normal[ 1 ] / mag * radius;
}
- if( traceEnt->takedamage )
- G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, mod );
+ // Clamp origin to be within bounding box vertically
+ if( normal[ 2 ] > victim->r.maxs[ 2 ] )
+ normal[ 2 ] = victim->r.maxs[ 2 ];
+ if( normal[ 2 ] < victim->r.mins[ 2 ] )
+ normal[ 2 ] = victim->r.mins[ 2 ];
+
+ VectorAdd( victim->r.currentOrigin, normal, origin );
+ VectorNegate( normal, normal );
+ VectorNormalize( normal );
+
+ // Create the blood spurt effect entity
+ tent = G_TempEntity( origin, EV_MISSILE_HIT );
+ tent->s.eventParm = DirToByte( normal );
+ tent->s.otherEntityNum = victim->s.number;
+ tent->s.weapon = attacker->s.weapon;
+ tent->s.generic1 = attacker->s.generic1; // weaponMode
+}
+
+/*
+===============
+meleeAttack
+===============
+*/
+void meleeAttack( gentity_t *ent, float range, float width, float height,
+ int damage, meansOfDeath_t mod )
+{
+ trace_t tr;
+ gentity_t *traceEnt;
+
+ G_WideTrace( &tr, ent, range, width, height, &traceEnt );
+ if( traceEnt == NULL || !traceEnt->takedamage )
+ return;
+
+ WideBloodSpurt( ent, traceEnt, &tr );
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod );
}
/*
@@ -308,7 +333,9 @@ void bulletFire( gentity_t *ent, float spread, int damage, int mod )
SnapVectorTowards( tr.endpos, muzzle );
// send bullet impact
- if( traceEnt->takedamage && traceEnt->client )
+ if( traceEnt->takedamage &&
+ (traceEnt->s.eType == ET_PLAYER ||
+ traceEnt->s.eType == ET_BUILDABLE ) )
{
tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
tent->s.eventParm = traceEnt->s.number;
@@ -356,7 +383,7 @@ void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent )
{
r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
- VectorMA( origin, 8192 * 16, forward, end );
+ VectorMA( origin, SHOTGUN_RANGE, forward, end );
VectorMA( end, r, right, end );
VectorMA( end, u, up, end );
@@ -381,9 +408,9 @@ void shotgunFire( gentity_t *ent )
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.eventParm = rand() / ( RAND_MAX / 0x100 + 1 ); // seed for spread pattern
tent->s.otherEntityNum = ent->s.number;
- G_UnlaggedOn( ent, muzzle, 8192 * 16 );
+ G_UnlaggedOn( ent, muzzle, SHOTGUN_RANGE );
ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );
G_UnlaggedOff();
}
@@ -403,9 +430,9 @@ void massDriverFire( gentity_t *ent )
gentity_t *tent;
gentity_t *traceEnt;
- VectorMA( muzzle, 8192 * 16, forward, end );
+ VectorMA( muzzle, 8192.0f * 16.0f, forward, end );
- G_UnlaggedOn( ent, muzzle, 8192 * 16 );
+ G_UnlaggedOn( ent, muzzle, 8192.0f * 16.0f );
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
G_UnlaggedOff( );
@@ -418,13 +445,11 @@ void massDriverFire( gentity_t *ent )
SnapVectorTowards( tr.endpos, muzzle );
// send impact
- if( traceEnt->takedamage && traceEnt->client )
+ if( traceEnt->takedamage &&
+ (traceEnt->s.eType == ET_BUILDABLE ||
+ traceEnt->s.eType == ET_PLAYER ) )
{
- 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
+ BloodSpurt( ent, traceEnt, &tr );
}
else
{
@@ -451,11 +476,7 @@ 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
+ fire_lockblob( ent, muzzle, forward );
}
/*
@@ -468,11 +489,12 @@ HIVE
void hiveFire( gentity_t *ent )
{
- gentity_t *m;
-
- m = fire_hive( ent, muzzle, forward );
+ vec3_t origin;
-// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics
+ // Fire from the hive tip, not the center
+ VectorMA( muzzle, ent->r.maxs[ 2 ], ent->s.origin2, origin );
+
+ fire_hive( ent, origin, forward );
}
/*
@@ -485,11 +507,7 @@ 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
+ fire_blaster( ent, muzzle, forward );
}
/*
@@ -502,11 +520,7 @@ 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
+ fire_pulseRifle( ent, muzzle, forward );
}
/*
@@ -519,9 +533,15 @@ FLAME THROWER
void flamerFire( gentity_t *ent )
{
- gentity_t *m;
+ vec3_t origin;
+
+ // Correct muzzle so that the missile does not start in the ceiling
+ VectorMA( muzzle, -7.0f, up, origin );
+
+ // Correct muzzle so that the missile fires from the player's hand
+ VectorMA( origin, 4.5f, right, origin );
- m = fire_flamer( ent, muzzle, forward );
+ fire_flamer( ent, origin, forward );
}
/*
@@ -534,9 +554,7 @@ GRENADE
void throwGrenade( gentity_t *ent )
{
- gentity_t *m;
-
- m = launch_grenade( ent, muzzle, forward );
+ launch_grenade( ent, muzzle, forward );
}
/*
@@ -574,13 +592,11 @@ void lasGunFire( gentity_t *ent )
SnapVectorTowards( tr.endpos, muzzle );
// send impact
- if( traceEnt->takedamage && traceEnt->client )
+ if( traceEnt->takedamage &&
+ (traceEnt->s.eType == ET_BUILDABLE ||
+ traceEnt->s.eType == ET_PLAYER ) )
{
- 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
+ BloodSpurt( ent, traceEnt, &tr );
}
else
{
@@ -605,50 +621,32 @@ 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( ent, muzzle, PAINSAW_RANGE );
- trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
- G_UnlaggedOff( );
+ vec3_t temp;
+ gentity_t *tent, *traceEnt;
- if( tr.surfaceFlags & SURF_NOIMPACT )
+ G_WideTrace( &tr, ent, PAINSAW_RANGE, PAINSAW_WIDTH, PAINSAW_HEIGHT,
+ &traceEnt );
+ if( !traceEnt || !traceEnt->takedamage )
return;
- traceEnt = &g_entities[ tr.entityNum ];
+ // hack to line up particle system with weapon model
+ tr.endpos[ 2 ] -= 5.0f;
// send blood impact
- if( traceEnt->takedamage )
+ if( traceEnt->s.eType == ET_PLAYER || traceEnt->s.eType == ET_BUILDABLE )
+ {
+ BloodSpurt( ent, traceEnt, &tr );
+ }
+ else
{
- 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 = 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, 0, MOD_PAINSAW );
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW );
}
/*
@@ -666,19 +664,14 @@ 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;
- }
+ if( secondary && ent->client->ps.stats[ STAT_MISC ] <= 0 )
+ fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE,
+ LCANNON_SECONDARY_RADIUS, LCANNON_SECONDARY_SPEED );
else
- {
- m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS );
- ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT;
- }
+ fire_luciferCannon( ent, muzzle, forward,
+ ent->client->ps.stats[ STAT_MISC ] *
+ LCANNON_DAMAGE / LCANNON_CHARGE_TIME_MAX,
+ LCANNON_RADIUS, LCANNON_SPEED );
ent->client->ps.stats[ STAT_MISC ] = 0;
}
@@ -692,49 +685,44 @@ TESLA GENERATOR
*/
-void teslaFire( gentity_t *ent )
+void teslaFire( gentity_t *self )
{
- 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 );
+ trace_t tr;
+ vec3_t origin, target;
+ gentity_t *tent;
- if( tr.entityNum == ENTITYNUM_NONE )
+ if( !self->enemy )
return;
- traceEnt = &g_entities[ tr.entityNum ];
+ // Move the muzzle from the entity origin up a bit to fire over turrets
+ VectorMA( muzzle, self->r.maxs[ 2 ], self->s.origin2, origin );
- if( !traceEnt->client )
- return;
+ // Don't aim for the center, aim at the top of the bounding box
+ VectorCopy( self->enemy->r.currentOrigin, target );
+ target[ 2 ] += self->enemy->r.maxs[ 2 ];
- if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ // Trace to the target entity
+ trap_Trace( &tr, origin, NULL, NULL, target, self->s.number, MASK_SHOT );
+ if( tr.entityNum != self->enemy->s.number )
return;
- //so the client side knows
- ent->s.eFlags |= EF_FIRING;
+ // Client side firing effect
+ self->s.eFlags |= EF_FIRING;
- if( traceEnt->takedamage )
+ // Deal damage
+ if( self->enemy->takedamage )
{
- G_Damage( traceEnt, ent, ent, forward, tr.endpos,
- TESLAGEN_DMG, 0, MOD_TESLAGEN );
- }
+ vec3_t dir;
- // snap the endpos to integers to save net bandwidth, but nudged towards the line
- SnapVectorTowards( tr.endpos, muzzle );
+ VectorSubtract( target, origin, dir );
+ G_Damage( self->enemy, self, self, dir, tr.endpos,
+ TESLAGEN_DMG, 0, MOD_TESLAGEN );
+ }
- // send railgun beam effect
+ // Send tesla zap trail
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 );
+ tent->s.misc = self->s.number; // src
+ tent->s.clientNum = self->enemy->s.number; // dest
}
@@ -745,65 +733,62 @@ BUILD GUN
======================================================================
*/
-
-/*
-===============
-cancelBuildFire
-===============
-*/
-void cancelBuildFire( gentity_t *ent )
+void CheckCkitRepair( gentity_t *ent )
{
- vec3_t forward, end;
+ vec3_t viewOrigin, 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;
+ if( ent->client->ps.weaponTime > 0 ||
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
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 );
+ BG_GetClientViewOrigin( &ent->client->ps, viewOrigin );
+ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
+ VectorMA( viewOrigin, 100, forward, end );
- trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
- traceEnt = &g_entities[ tr.entityNum ];
+ trap_Trace( &tr, viewOrigin, 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( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 &&
+ traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS )
+ {
+ bHealth = BG_Buildable( traceEnt->s.modelindex )->health;
+ if( traceEnt->health < bHealth )
{
- 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 )
+ 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 );
+
+ ent->client->ps.weaponTime += BG_Weapon( ent->client->ps.weapon )->repeatRate1;
}
}
- else if( ent->client->ps.weapon == WP_ABUILD2 )
+}
+
+/*
+===============
+cancelBuildFire
+===============
+*/
+void cancelBuildFire( gentity_t *ent )
+{
+ // Cancel ghost buildable
+ if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE )
+ {
+ ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
+ return;
+ }
+
+ if( ent->client->ps.weapon == WP_ABUILD ||
+ 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
+ ABUILDER_CLAW_WIDTH, ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW );
}
/*
@@ -813,7 +798,10 @@ buildFire
*/
void buildFire( gentity_t *ent, dynMenu_t menu )
{
- if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
+ buildable_t buildable = ( ent->client->ps.stats[ STAT_BUILDABLE ]
+ & ~SB_VALID_TOGGLEBIT );
+
+ if( buildable > BA_NONE )
{
if( ent->client->ps.stats[ STAT_MISC ] > 0 )
{
@@ -821,33 +809,17 @@ void buildFire( gentity_t *ent, dynMenu_t menu )
return;
}
- if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) )
+ if( G_BuildIfValid( ent, buildable ) )
{
- 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
+ if( !g_cheats.integer )
{
ent->client->ps.stats[ STAT_MISC ] +=
- BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2;
+ BG_Buildable( buildable )->buildTime;
}
- 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;
}
@@ -856,11 +828,7 @@ void buildFire( gentity_t *ent, dynMenu_t 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
+ fire_slowBlob( ent, muzzle, forward );
}
@@ -880,65 +848,51 @@ 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 );
+ if( ent->client->ps.weaponTime )
+ return qfalse;
- // set aiming directions
+ // Calculate muzzle point
AngleVectors( ent->client->ps.viewangles, forward, right, up );
-
CalcMuzzlePoint( ent, forward, right, up, muzzle );
- VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end );
-
- G_UnlaggedOn( ent, muzzle, LEVEL0_BITE_RANGE );
- trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
- G_UnlaggedOff( );
+ G_WideTrace( &tr, ent, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH,
+ LEVEL0_BITE_WIDTH, &traceEnt );
- if( tr.surfaceFlags & SURF_NOIMPACT )
+ if( traceEnt == NULL )
return qfalse;
- traceEnt = &g_entities[ tr.entityNum ];
-
if( !traceEnt->takedamage )
return qfalse;
- //allow bites to work against defensive buildables only
+ if( traceEnt->health <= 0 )
+ return qfalse;
+
+ // only allow bites to work against buildings as they are constructing
if( traceEnt->s.eType == ET_BUILDABLE )
{
- if( traceEnt->s.modelindex != BA_H_MGTURRET &&
- traceEnt->s.modelindex != BA_H_TESLAGEN )
+ if( traceEnt->spawned )
return qfalse;
- //hackery
- damage *= 0.5f;
+ if( traceEnt->buildableTeam == TEAM_ALIENS )
+ return qfalse;
}
if( traceEnt->client )
{
- if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_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, 0, MOD_LEVEL0_BITE );
+ WideBloodSpurt( ent, traceEnt, &tr );
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE );
+ ent->client->ps.weaponTime += LEVEL0_BITE_REPEAT;
return qtrue;
}
@@ -966,7 +920,10 @@ void CheckGrabAttack( gentity_t *ent )
CalcMuzzlePoint( ent, forward, right, up, muzzle );
- VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end );
+ if( ent->client->ps.weapon == WP_ALEVEL1 )
+ VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end );
+ else if( ent->client->ps.weapon == WP_ALEVEL1_UPG )
+ VectorMA( muzzle, LEVEL1_GRAB_U_RANGE, forward, end );
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
if( tr.surfaceFlags & SURF_NOIMPACT )
@@ -979,7 +936,7 @@ void CheckGrabAttack( gentity_t *ent )
if( traceEnt->client )
{
- if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )
return;
if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )
@@ -1001,15 +958,6 @@ void CheckGrabAttack( gentity_t *ent )
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;
- }
}
/*
@@ -1023,7 +971,7 @@ void poisonCloud( gentity_t *ent )
vec3_t range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE };
vec3_t mins, maxs;
int i, num;
- gentity_t *target;
+ gentity_t *humanPlayer;
trace_t tr;
VectorAdd( ent->client->ps.origin, range, maxs );
@@ -1033,39 +981,23 @@ void poisonCloud( gentity_t *ent )
num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
for( i = 0; i < num; i++ )
{
- target = &g_entities[ entityList[ i ] ];
+ humanPlayer = &g_entities[ entityList[ i ] ];
- if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ if( humanPlayer->client &&
+ humanPlayer->client->pers.teamSelection == TEAM_HUMANS )
{
- if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, target->client->ps.stats ) )
- continue;
-
- if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, target->client->ps.stats ) )
- continue;
-
- trap_Trace( &tr, muzzle, NULL, NULL, target->s.origin, target->s.number, MASK_SHOT );
+ trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->r.currentOrigin,
+ humanPlayer->s.number, CONTENTS_SOLID );
//can't see target from here
if( tr.entityNum == ENTITYNUM_WORLD )
continue;
- if( !( target->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) )
- {
- target->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED;
- target->client->lastPoisonCloudedTime = level.time;
- target->client->lastPoisonCloudedClient = ent;
- trap_SendServerCommand( target->client->ps.clientNum, "poisoncloud" );
- }
- }
- if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- {
- trap_Trace( &tr, muzzle, NULL, NULL, target->s.origin, target->s.number, MASK_SOLID );
-
- if( tr.entityNum == ENTITYNUM_WORLD )
- continue;
+ humanPlayer->client->ps.eFlags |= EF_POISONCLOUDED;
+ humanPlayer->client->lastPoisonCloudedTime = level.time;
- target->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;
- target->client->lastBoostedTime = MAX( target->client->lastBoostedTime, level.time - BOOST_TIME + LEVEL1_PCLOUD_BOOST_TIME );
+ trap_SendServerCommand( humanPlayer->client->ps.clientNum,
+ "poisoncloud" );
}
}
G_UnlaggedOff( );
@@ -1080,72 +1012,60 @@ LEVEL2
======================================================================
*/
-#define MAX_ZAPS 64
-
-static zap_t zaps[ MAX_CLIENTS ];
+zap_t zaps[ MAX_ZAPS ];
/*
===============
-G_FindNewZapTarget
+G_FindZapChainTargets
===============
*/
-static gentity_t *G_FindNewZapTarget( gentity_t *ent )
+static void G_FindZapChainTargets( zap_t *zap )
{
+ gentity_t *ent = zap->targets[ 0 ]; // the source
int entityList[ MAX_GENTITIES ];
- vec3_t range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE };
+ vec3_t range = { LEVEL2_AREAZAP_CHAIN_RANGE,
+ LEVEL2_AREAZAP_CHAIN_RANGE,
+ LEVEL2_AREAZAP_CHAIN_RANGE };
vec3_t mins, maxs;
- int i, j, k, num;
+ int i, num;
gentity_t *enemy;
trace_t tr;
+ float distance;
- VectorScale( range, 1.0f / M_ROOT3, range );
- VectorAdd( ent->s.origin, range, maxs );
- VectorSubtract( ent->s.origin, range, mins );
+ VectorAdd( ent->r.currentOrigin, range, maxs );
+ VectorSubtract( ent->r.currentOrigin, 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 )
+ // don't chain to self; noclippers can be listed, don't chain to them either
+ if( enemy == ent || ( enemy->client && enemy->client->noclip ) )
+ continue;
+
+ distance = Distance( ent->r.currentOrigin, enemy->r.currentOrigin );
+
+ if( ( ( enemy->client &&
+ enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) ||
+ ( enemy->s.eType == ET_BUILDABLE &&
+ BG_Buildable( enemy->s.modelindex )->team == TEAM_HUMANS ) ) &&
+ enemy->health > 0 && // only chain to living targets
+ distance <= LEVEL2_AREAZAP_CHAIN_RANGE )
{
- 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;
+ // world-LOS check: trace against the world, ignoring other BODY entities
+ trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL,
+ enemy->r.currentOrigin, ent->s.number, CONTENTS_SOLID );
- for( j = 0; j < MAX_ZAPS; j++ )
+ if( tr.entityNum == ENTITYNUM_NONE )
{
- zap_t *zap = &zaps[ j ];
-
- for( k = 0; k < zap->numTargets; k++ )
- {
- if( zap->targets[ k ] == enemy )
- {
- foundOldTarget = qtrue;
- break;
- }
- }
-
- if( foundOldTarget )
- break;
+ zap->targets[ zap->numTargets ] = enemy;
+ zap->distances[ zap->numTargets ] = distance;
+ if( ++zap->numTargets >= LEVEL2_AREAZAP_MAX_TARGETS )
+ return;
}
-
- // enemy is already targetted
- if( foundOldTarget )
- continue;
-
- return enemy;
}
}
-
- return NULL;
}
/*
@@ -1155,30 +1075,19 @@ 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.misc = zap->creator->s.number;
+ int i;
+ int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ];
- effect->s.time = effect->s.time2 = effect->s.constantLight = -1;
+ entityNums[ 0 ] = zap->creator->s.number;
- for( j = 0; j < zap->numTargets; j++ )
- {
- int number = zap->targets[ j ]->s.number;
+ for( i = 0; i < zap->numTargets; i++ )
+ entityNums[ i + 1 ] = zap->targets[ i ]->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;
- }
- }
+ BG_PackEntityNumbers( &zap->effectChannel->s,
+ entityNums, zap->numTargets + 1 );
- trap_LinkEntity( effect );
+ VectorCopy( zap->creator->r.currentOrigin, zap->effectChannel->r.currentOrigin );
+ trap_LinkEntity( zap->effectChannel );
}
/*
@@ -1188,38 +1097,48 @@ G_CreateNewZap
*/
static void G_CreateNewZap( gentity_t *creator, gentity_t *target )
{
- int i, j;
- zap_t *zap;
+ int i;
+ zap_t *zap;
for( i = 0; i < MAX_ZAPS; i++ )
{
zap = &zaps[ i ];
+ if( zap->used )
+ continue;
- if( !zap->used )
- {
- zap->used = qtrue;
+ zap->used = qtrue;
+ zap->timeToLive = LEVEL2_AREAZAP_TIME;
- zap->timeToLive = LEVEL2_AREAZAP_TIME;
- zap->damageUsed = 0;
+ zap->creator = creator;
+ zap->targets[ 0 ] = target;
+ zap->numTargets = 1;
- zap->creator = creator;
+ // the zap chains only through living entities
+ if( target->health > 0 )
+ {
+ G_Damage( target, creator, creator, forward,
+ target->r.currentOrigin, LEVEL2_AREAZAP_DMG,
+ DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE,
+ MOD_LEVEL2_ZAP );
- zap->targets[ 0 ] = target;
- zap->numTargets = 1;
+ G_FindZapChainTargets( zap );
- for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ )
+ for( i = 1; i < zap->numTargets; i++ )
{
- zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] );
-
- if( zap->targets[ j ] )
- zap->numTargets++;
+ G_Damage( zap->targets[ i ], target, zap->creator, forward, target->r.currentOrigin,
+ LEVEL2_AREAZAP_DMG * ( 1 - pow( (zap->distances[ i ] /
+ LEVEL2_AREAZAP_CHAIN_RANGE ), LEVEL2_AREAZAP_CHAIN_FALLOFF ) ) + 1,
+ DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE,
+ MOD_LEVEL2_ZAP );
}
+ }
- zap->effectChannel = G_Spawn( );
- G_UpdateZapEffect( zap );
+ zap->effectChannel = G_Spawn( );
+ zap->effectChannel->s.eType = ET_LEV2_ZAP_CHAIN;
+ zap->effectChannel->classname = "lev2zapchain";
+ G_UpdateZapEffect( zap );
- return;
- }
+ return;
}
}
@@ -1233,80 +1152,67 @@ 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 )
+ continue;
- if( zap->used )
+ zap->timeToLive -= msec;
+
+ // first, the disappearance of players is handled immediately in G_ClearPlayerZapEffects()
+
+ // the deconstruction or gibbing of a directly targeted buildable destroys the whole zap effect
+ if( zap->timeToLive <= 0 || !zap->targets[ 0 ]->inuse )
{
- //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;
- }
- }
+ G_FreeEntity( zap->effectChannel );
+ zap->used = qfalse;
+ continue;
+ }
- 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_LOCDAMAGE, MOD_LEVEL2_ZAP );
- zap->damageUsed += damage;
- }
- }
- }
+ // the deconstruction or gibbing of chained buildables destroy the appropriate beams
+ for( j = 1; j < zap->numTargets; j++ )
+ {
+ if( !zap->targets[ j ]->inuse )
+ zap->targets[ j-- ] = zap->targets[ --zap->numTargets ];
+ }
- G_UpdateZapEffect( zap );
+ G_UpdateZapEffect( zap );
+ }
+}
- zap->timeToLive -= msec;
+/*
+===============
+G_ClearPlayerZapEffects
- if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 )
- {
- zap->used = qfalse;
- G_FreeEntity( zap->effectChannel );
- }
+called from G_LeaveTeam() and TeleportPlayer()
+===============
+*/
+void G_ClearPlayerZapEffects( gentity_t *player )
+{
+ int i, j;
+ zap_t *zap;
+
+ for( i = 0; i < MAX_ZAPS; i++ )
+ {
+ zap = &zaps[ i ];
+ if( !zap->used )
+ continue;
+
+ // the disappearance of the creator or the first target destroys the whole zap effect
+ if( zap->creator == player || zap->targets[ 0 ] == player )
+ {
+ G_FreeEntity( zap->effectChannel );
+ zap->used = qfalse;
+ continue;
+ }
+
+ // the disappearance of chained players destroy the appropriate beams
+ for( j = 1; j < zap->numTargets; j++ )
+ {
+ if( zap->targets[ j ] == player )
+ zap->targets[ j-- ] = zap->targets[ --zap->numTargets ];
}
}
}
@@ -1319,32 +1225,16 @@ 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_WideTrace( &tr, ent, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, &traceEnt );
- G_UnlaggedOn( ent, muzzle, LEVEL2_AREAZAP_RANGE );
- trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
- G_UnlaggedOff( );
-
- if( tr.surfaceFlags & SURF_NOIMPACT )
+ if( traceEnt == NULL )
return;
- traceEnt = &g_entities[ tr.entityNum ];
-
- if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
+ if( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) ||
( traceEnt->s.eType == ET_BUILDABLE &&
- BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 )
+ BG_Buildable( traceEnt->s.modelindex )->team == TEAM_HUMANS ) )
{
G_CreateNewZap( ent, traceEnt );
}
@@ -1366,61 +1256,51 @@ CheckPounceAttack
*/
qboolean CheckPounceAttack( gentity_t *ent )
{
- trace_t tr;
- gentity_t *tent;
+ trace_t tr;
gentity_t *traceEnt;
- int damage;
-
- if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
- {
- ent->client->allowedToPounce = qfalse;
- ent->client->pmext.pouncePayload = 0;
- }
+ int damage, timeMax, pounceRange, payload;
- if( !ent->client->allowedToPounce )
+ if( ent->client->pmext.pouncePayload <= 0 )
return qfalse;
- if( ent->client->ps.weaponTime )
- return qfalse;
+ // In case the goon lands on his target, he gets one shot after landing
+ payload = ent->client->pmext.pouncePayload;
+ if( !( ent->client->ps.pm_flags & PMF_CHARGE ) )
+ ent->client->pmext.pouncePayload = 0;
- G_WideTrace( &tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH, &traceEnt );
+ // Calculate muzzle point
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
+ // Trace from muzzle to see what we hit
+ pounceRange = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_RANGE :
+ LEVEL3_POUNCE_UPG_RANGE;
+ G_WideTrace( &tr, ent, pounceRange, LEVEL3_POUNCE_WIDTH,
+ LEVEL3_POUNCE_WIDTH, &traceEnt );
if( traceEnt == NULL )
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
- }
+ // Send blood impact
+ if( traceEnt->takedamage )
+ WideBloodSpurt( ent, traceEnt, &tr );
if( !traceEnt->takedamage )
return qfalse;
-
- damage = (int)( ( (float)ent->client->pmext.pouncePayload
- / (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG );
-
+
+ // Deal damage
+ timeMax = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME :
+ LEVEL3_POUNCE_TIME_UPG;
+ damage = payload * LEVEL3_POUNCE_DMG / timeMax;
ent->client->pmext.pouncePayload = 0;
-
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
- DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE );
-
- ent->client->allowedToPounce = qfalse;
+ DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE );
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
+ fire_bounceBall( ent, muzzle, forward );
}
@@ -1434,39 +1314,96 @@ LEVEL4
/*
===============
-ChargeAttack
+G_ChargeAttack
===============
*/
-void ChargeAttack( gentity_t *ent, gentity_t *victim )
+void G_ChargeAttack( gentity_t *ent, gentity_t *victim )
{
- gentity_t *tent;
int damage;
- vec3_t forward, normal;
+ int i;
+ vec3_t forward;
- if( level.time < victim->chargeRepeat )
+ if( ent->client->ps.stats[ STAT_MISC ] <= 0 ||
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_CHARGING ) ||
+ ent->client->ps.weaponTime )
return;
- victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT;
-
- VectorSubtract( victim->s.origin, ent->s.origin, forward );
+ VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, forward );
VectorNormalize( forward );
- VectorNegate( forward, normal );
- if( victim->client )
+ if( !victim->takedamage )
+ return;
+
+ // For buildables, track the last MAX_TRAMPLE_BUILDABLES_TRACKED buildables
+ // hit, and do not do damage if the current buildable is in that list
+ // in order to prevent dancing over stuff to kill it very quickly
+ 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
+ for( i = 0; i < MAX_TRAMPLE_BUILDABLES_TRACKED; i++ )
+ {
+ if( ent->client->trampleBuildablesHit[ i ] == victim - g_entities )
+ return;
+ }
+
+ ent->client->trampleBuildablesHit[
+ ent->client->trampleBuildablesHitPos++ % MAX_TRAMPLE_BUILDABLES_TRACKED ] =
+ victim - g_entities;
}
- if( !victim->takedamage )
+ WideBloodSpurt( ent, victim, NULL );
+
+ damage = LEVEL4_TRAMPLE_DMG * ent->client->ps.stats[ STAT_MISC ] /
+ LEVEL4_TRAMPLE_DURATION;
+
+ G_Damage( victim, ent, ent, forward, victim->r.currentOrigin, damage,
+ DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_TRAMPLE );
+
+ ent->client->ps.weaponTime += LEVEL4_TRAMPLE_REPEAT;
+}
+
+/*
+===============
+G_CrushAttack
+
+Should only be called if there was an impact between a tyrant and another player
+===============
+*/
+void G_CrushAttack( gentity_t *ent, gentity_t *victim )
+{
+ vec3_t dir;
+ float jump;
+ int damage;
+
+ if( !victim->takedamage ||
+ ent->client->ps.origin[ 2 ] + ent->r.mins[ 2 ] <
+ victim->r.currentOrigin[ 2 ] + victim->r.maxs[ 2 ] ||
+ ( victim->client &&
+ victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) )
return;
- damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG );
+ // Deal velocity based damage to target
+ jump = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->jumpMagnitude;
+ damage = ( ent->client->pmext.fallVelocity + jump ) *
+ -LEVEL4_CRUSH_DAMAGE_PER_V;
+
+ if( damage < 0 )
+ damage = 0;
+
+ // Players also get damaged periodically
+ if( victim->client &&
+ ent->client->lastCrushTime + LEVEL4_CRUSH_REPEAT < level.time )
+ {
+ ent->client->lastCrushTime = level.time;
+ damage += LEVEL4_CRUSH_DAMAGE;
+ }
+
+ if( damage < 1 )
+ return;
- G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE );
+ // Crush the victim over a period of time
+ VectorSubtract( victim->r.currentOrigin, ent->client->ps.origin, dir );
+ G_Damage( victim, ent, ent, dir, victim->r.currentOrigin, damage,
+ DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_CRUSH );
}
//======================================================================
@@ -1480,10 +1417,12 @@ 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;
+ vec3_t normal;
+
+ VectorCopy( ent->client->ps.origin, muzzlePoint );
+ BG_GetClientNormal( &ent->client->ps, normal );
+ VectorMA( muzzlePoint, ent->client->ps.viewheight, normal, muzzlePoint );
VectorMA( muzzlePoint, 1, forward, muzzlePoint );
- VectorMA( muzzlePoint, 1, right, muzzlePoint );
// snap to integer coordinates for more efficient network bandwidth usage
SnapVector( muzzlePoint );
}
@@ -1548,18 +1487,18 @@ void FireWeapon2( gentity_t *ent )
case WP_ALEVEL1_UPG:
poisonCloud( ent );
break;
- case WP_ALEVEL2_UPG:
- areaZapFire( ent );
- break;
case WP_LUCIFER_CANNON:
LCChargeFire( ent, qtrue );
break;
+ case WP_ALEVEL2_UPG:
+ areaZapFire( ent );
+ break;
+
case WP_ABUILD:
case WP_ABUILD2:
case WP_HBUILD:
- case WP_HBUILD2:
cancelBuildFire( ent );
break;
default:
@@ -1574,13 +1513,11 @@ FireWeapon
*/
void FireWeapon( gentity_t *ent )
{
- if( level.paused ) return;
-
if( ent->client )
{
// set aiming directions
AngleVectors( ent->client->ps.viewangles, forward, right, up );
- CalcMuzzlePoint( ent, forward, right, up, muzzle );
+ CalcMuzzlePoint( ent, forward, right, up, muzzle );
}
else
{
@@ -1592,21 +1529,32 @@ void FireWeapon( gentity_t *ent )
switch( ent->s.weapon )
{
case WP_ALEVEL1:
+ meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH,
+ LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
+ break;
case WP_ALEVEL1_UPG:
- meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
+ meleeAttack( ent, LEVEL1_CLAW_U_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH,
+ LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );
break;
case WP_ALEVEL3:
+ meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_WIDTH,
+ LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
+ break;
case WP_ALEVEL3_UPG:
- meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );
+ meleeAttack( ent, LEVEL3_CLAW_UPG_RANGE, LEVEL3_CLAW_WIDTH,
+ 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 );
+ meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, 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 );
+ meleeAttack( ent, LEVEL2_CLAW_U_RANGE, LEVEL2_CLAW_WIDTH, 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 );
+ meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH,
+ LEVEL4_CLAW_HEIGHT, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW );
break;
case WP_BLASTER:
@@ -1661,11 +1609,9 @@ void FireWeapon( gentity_t *ent )
buildFire( ent, MN_A_BUILD );
break;
case WP_HBUILD:
- case WP_HBUILD2:
buildFire( ent, MN_H_BUILD );
break;
default:
break;
}
}
-