From 425decdf7e9284d15aa726e3ae96b9942fb0e3ea Mon Sep 17 00:00:00 2001 From: IronClawTrem Date: Sun, 16 Feb 2020 03:40:06 +0000 Subject: create tremded branch --- src/game/g_weapon.c | 1158 ++++++++++++++++++++++++--------------------------- 1 file changed, 552 insertions(+), 606 deletions(-) (limited to 'src/game/g_weapon.c') 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 + =========================================================================== */ @@ -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; } } - -- cgit