diff options
Diffstat (limited to 'src/game/g_weapon.c')
-rw-r--r-- | src/game/g_weapon.c | 1553 |
1 files changed, 1553 insertions, 0 deletions
diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c new file mode 100644 index 00000000..a18a8c39 --- /dev/null +++ b/src/game/g_weapon.c @@ -0,0 +1,1553 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// g_weapon.c +// perform the server side effects of a weapon firing + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "g_local.h" + +static vec3_t forward, right, up; +static vec3_t muzzle; + +/* +================ +G_ForceWeaponChange +================ +*/ +void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon ) +{ + int i; + + if( ent ) + { + ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH; + + if( weapon == WP_NONE ) + { + //switch to the first non blaster weapon + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( i == WP_BLASTER ) + continue; + + if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) ) + { + ent->client->ps.persistant[ PERS_NEWWEAPON ] = i; + break; + } + } + + //only got the blaster to switch to + if( i == WP_NUM_WEAPONS ) + ent->client->ps.persistant[ PERS_NEWWEAPON ] = WP_BLASTER; + } + else + ent->client->ps.persistant[ PERS_NEWWEAPON ] = weapon; + } +} + +/* +================= +G_GiveClientMaxAmmo +================= +*/ +void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo ) +{ + int i; + int maxAmmo, maxClips; + qboolean weaponType, restoredAmmo = qfalse; + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( buyingEnergyAmmo ) + weaponType = BG_FindUsesEnergyForWeapon( i ); + else + weaponType = !BG_FindUsesEnergyForWeapon( i ); + + if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && + weaponType && !BG_FindInfinteAmmoForWeapon( i ) && + !BG_WeaponIsFull( i, ent->client->ps.stats, + ent->client->ps.ammo, ent->client->ps.powerups ) ) + { + BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips ); + + if( buyingEnergyAmmo ) + { + G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); + + if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) + maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); + } + + BG_PackAmmoArray( i, ent->client->ps.ammo, ent->client->ps.powerups, + maxAmmo, maxClips ); + + restoredAmmo = qtrue; + } + } + + if( restoredAmmo ) + G_ForceWeaponChange( ent, ent->client->ps.weapon ); +} + +/* +================ +G_BounceProjectile +================ +*/ +void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) +{ + vec3_t v, newv; + float dot; + + VectorSubtract( impact, start, v ); + dot = DotProduct( v, dir ); + VectorMA( v, -2 * dot, dir, newv ); + + VectorNormalize(newv); + VectorMA(impact, 8192, newv, endout); +} + +/* +====================== +SnapVectorTowards + +Round a vector to integers for more efficient network +transmission, but make sure that it rounds towards a given point +rather than blindly truncating. This prevents it from truncating +into a wall. +====================== +*/ +void SnapVectorTowards( vec3_t v, vec3_t to ) +{ + int i; + + for( i = 0 ; i < 3 ; i++ ) + { + if( to[ i ] <= v[ i ] ) + v[ i ] = (int)v[ i ]; + else + v[ i ] = (int)v[ i ] + 1; + } +} + +/* +=============== +meleeAttack +=============== +*/ +void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + vec3_t mins, maxs; + + VectorSet( mins, -width, -width, -width ); + VectorSet( maxs, width, width, width ); + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + VectorMA( muzzle, range, forward, end ); + + trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // send blood impact + if( traceEnt->takedamage && traceEnt->client ) + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = traceEnt->s.number; + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( traceEnt->takedamage ) + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod ); +} + +/* +====================================================================== + +MACHINEGUN + +====================================================================== +*/ + +void bulletFire( gentity_t *ent, float spread, int damage, int mod ) +{ + trace_t tr; + vec3_t end; + float r; + float u; + gentity_t *tent; + gentity_t *traceEnt; + + r = random( ) * M_PI * 2.0f; + u = sin( r ) * crandom( ) * spread * 16; + r = cos( r ) * crandom( ) * spread * 16; + VectorMA( muzzle, 8192 * 16, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send bullet impact + if( traceEnt->takedamage && traceEnt->client ) + { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); + tent->s.eventParm = traceEnt->s.number; + } + else + { + tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL ); + tent->s.eventParm = DirToByte( tr.plane.normal ); + } + tent->s.otherEntityNum = ent->s.number; + + if( traceEnt->takedamage ) + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + damage, 0, mod ); + } +} + +/* +====================================================================== + +SHOTGUN + +====================================================================== +*/ + +// this should match CG_ShotgunPattern +void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) +{ + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + trace_t tr; + gentity_t *traceEnt; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // generate the "random" spread pattern + for( i = 0; i < SHOTGUN_PELLETS; i++ ) + { + r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; + u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; + VectorMA( origin, 8192 * 16, forward, end ); + VectorMA( end, r, right, end ); + VectorMA( end, u, up, end ); + + trap_Trace( &tr, origin, NULL, NULL, end, ent->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + + // send bullet impact + if( !( tr.surfaceFlags & SURF_NOIMPACT ) ) + { + if( traceEnt->takedamage ) + G_Damage( traceEnt, ent, ent, forward, tr.endpos, SHOTGUN_DMG, 0, MOD_SHOTGUN ); + } + } +} + + +void shotgunFire( gentity_t *ent ) +{ + gentity_t *tent; + + // send shotgun blast + tent = G_TempEntity( muzzle, EV_SHOTGUN ); + VectorScale( forward, 4096, tent->s.origin2 ); + SnapVector( tent->s.origin2 ); + tent->s.eventParm = rand() & 255; // seed for spread pattern + tent->s.otherEntityNum = ent->s.number; + + ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent ); +} + +/* +====================================================================== + +MASS DRIVER + +====================================================================== +*/ + +void massDriverFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + + VectorMA( muzzle, 8192 * 16, forward, end ); + + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send impact + if( traceEnt->takedamage && traceEnt->client ) + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = traceEnt->s.number; + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + else + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( traceEnt->takedamage ) + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + MDRIVER_DMG, 0, MOD_MDRIVER ); + } +} + +/* +====================================================================== + +LOCKBLOB + +====================================================================== +*/ + +void lockBlobLauncherFire( gentity_t *ent ) +{ + gentity_t *m; + + m = fire_lockblob( ent, muzzle, forward ); + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + +/* +====================================================================== + +HIVE + +====================================================================== +*/ + +void hiveFire( gentity_t *ent ) +{ + gentity_t *m; + + m = fire_hive( ent, muzzle, forward ); + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + +/* +====================================================================== + +BLASTER PISTOL + +====================================================================== +*/ + +void blasterFire( gentity_t *ent ) +{ + gentity_t *m; + + m = fire_blaster( ent, muzzle, forward ); + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + +/* +====================================================================== + +PULSE RIFLE + +====================================================================== +*/ + +void pulseRifleFire( gentity_t *ent ) +{ + gentity_t *m; + + m = fire_pulseRifle( ent, muzzle, forward ); + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + +/* +====================================================================== + +FLAME THROWER + +====================================================================== +*/ + +void flamerFire( gentity_t *ent ) +{ + gentity_t *m; + + m = fire_flamer( ent, muzzle, forward ); +} + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +void throwGrenade( gentity_t *ent ) +{ + gentity_t *m; + + m = launch_grenade( ent, muzzle, forward ); +} + +/* +====================================================================== + +LAS GUN + +====================================================================== +*/ + +/* +=============== +lasGunFire +=============== +*/ +void lasGunFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + + VectorMA( muzzle, 8192 * 16, forward, end ); + + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // snap the endpos to integers, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send impact + if( traceEnt->takedamage && traceEnt->client ) + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = traceEnt->s.number; + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + else + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( traceEnt->takedamage ) + G_Damage( traceEnt, ent, ent, forward, tr.endpos, LASGUN_DAMAGE, 0, MOD_LASGUN ); +} + +/* +====================================================================== + +PAIN SAW + +====================================================================== +*/ + +void painSawFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + VectorMA( muzzle, PAINSAW_RANGE, forward, end ); + + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + // send blood impact + if( traceEnt->takedamage ) + { + vec3_t temp; + + //hack to get the particle system to line up with the weapon + VectorCopy( tr.endpos, temp ); + temp[ 2 ] -= 10.0f; + + if( traceEnt->client ) + { + tent = G_TempEntity( temp, EV_MISSILE_HIT ); + tent->s.otherEntityNum = traceEnt->s.number; + } + else + tent = G_TempEntity( temp, EV_MISSILE_MISS ); + + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( traceEnt->takedamage ) + G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW ); +} + +/* +====================================================================== + +LUCIFER CANNON + +====================================================================== +*/ + +/* +=============== +LCChargeFire +=============== +*/ +void LCChargeFire( gentity_t *ent, qboolean secondary ) +{ + gentity_t *m; + + if( secondary ) + m = fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE, LCANNON_SECONDARY_RADIUS ); + else + m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS ); + + ent->client->ps.stats[ STAT_MISC ] = 0; +} + +/* +====================================================================== + +TESLA GENERATOR + +====================================================================== +*/ + + +void teslaFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *traceEnt, *tent; + + VectorMA( muzzle, TESLAGEN_RANGE, forward, end ); + + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + + if( tr.entityNum == ENTITYNUM_NONE ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + if( !traceEnt->client ) + return; + + if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + return; + + //so the client side knows + ent->s.eFlags |= EF_FIRING; + + if( traceEnt->takedamage ) + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, + TESLAGEN_DMG, 0, MOD_TESLAGEN ); + } + + // snap the endpos to integers to save net bandwidth, but nudged towards the line + SnapVectorTowards( tr.endpos, muzzle ); + + // send railgun beam effect + tent = G_TempEntity( tr.endpos, EV_TESLATRAIL ); + + VectorCopy( muzzle, tent->s.origin2 ); + + tent->s.generic1 = ent->s.number; //src + tent->s.clientNum = traceEnt->s.number; //dest + + // move origin a bit to come closer to the drawn gun muzzle + VectorMA( tent->s.origin2, 28, up, tent->s.origin2 ); +} + + +/* +====================================================================== + +BUILD GUN + +====================================================================== +*/ + +/* +=============== +cancelBuildFire +=============== +*/ +void cancelBuildFire( gentity_t *ent ) +{ + vec3_t forward, end; + trace_t tr; + gentity_t *traceEnt; + int bHealth; + + if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) + { + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + return; + } + + //repair buildable + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( ent->client->ps.origin, 100, forward, end ); + + trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + + if( tr.fraction < 1.0 && + ( traceEnt->s.eType == ET_BUILDABLE ) && + ( traceEnt->biteam == ent->client->ps.stats[ STAT_PTEAM ] ) && + ( ( ent->client->ps.weapon >= WP_HBUILD2 ) && + ( ent->client->ps.weapon <= WP_HBUILD ) ) && + traceEnt->spawned && traceEnt->health > 0 ) + { + if( ent->client->ps.stats[ STAT_MISC ] > 0 ) + { + G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); + return; + } + + bHealth = BG_FindHealthForBuildable( traceEnt->s.modelindex ); + + traceEnt->health += HBUILD_HEALRATE; + + if( traceEnt->health > bHealth ) + traceEnt->health = bHealth; + + if( traceEnt->health == bHealth ) + G_AddEvent( ent, EV_BUILD_REPAIRED, 0 ); + else + G_AddEvent( ent, EV_BUILD_REPAIR, 0 ); + } + } + else if( ent->client->ps.weapon == WP_ABUILD2 ) + meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH, + ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); //melee attack for alien builder +} + +/* +=============== +buildFire +=============== +*/ +void buildFire( gentity_t *ent, dynMenu_t menu ) +{ + if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) + { + if( ent->client->ps.stats[ STAT_MISC ] > 0 ) + { + G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); + return; + } + + if( G_ValidateBuild( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) ) + { + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_isOvermind( ) ) + { + ent->client->ps.stats[ STAT_MISC ] += + BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; + } + else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_isPower( muzzle ) && + ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack + { + ent->client->ps.stats[ STAT_MISC ] += + BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; + } + else + ent->client->ps.stats[ STAT_MISC ] += + BG_FindBuildDelayForWeapon( ent->s.weapon ); + + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + + // don't want it bigger than 32k + if( ent->client->ps.stats[ STAT_MISC ] > 30000 ) + ent->client->ps.stats[ STAT_MISC ] = 30000; + } + return; + } + + G_TriggerMenu( ent->client->ps.clientNum, menu ); +} + +void slowBlobFire( gentity_t *ent ) +{ + gentity_t *m; + + m = fire_slowBlob( ent, muzzle, forward ); + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + + +/* +====================================================================== + +LEVEL0 + +====================================================================== +*/ + +/* +=============== +CheckVenomAttack +=============== +*/ +qboolean CheckVenomAttack( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + vec3_t mins, maxs; + int damage = LEVEL0_BITE_DMG; + + VectorSet( mins, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH ); + VectorSet( maxs, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH ); + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end ); + + trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + + if( tr.surfaceFlags & SURF_NOIMPACT ) + return qfalse; + + traceEnt = &g_entities[ tr.entityNum ]; + + if( !traceEnt->takedamage ) + return qfalse; + + if( !traceEnt->client && !traceEnt->s.eType == ET_BUILDABLE ) + return qfalse; + + //allow bites to work against defensive buildables only + if( traceEnt->s.eType == ET_BUILDABLE ) + { + if( traceEnt->s.modelindex != BA_H_MGTURRET && + traceEnt->s.modelindex != BA_H_TESLAGEN ) + return qfalse; + + //hackery + damage *= 0.5f; + } + + if( traceEnt->client ) + { + if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + return qfalse; + if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return qfalse; + } + + // send blood impact + if( traceEnt->takedamage && traceEnt->client ) + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = traceEnt->s.number; + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE ); + + return qtrue; +} + +/* +====================================================================== + +LEVEL1 + +====================================================================== +*/ + +/* +=============== +CheckGrabAttack +=============== +*/ +void CheckGrabAttack( gentity_t *ent ) +{ + trace_t tr; + vec3_t end, dir; + gentity_t *traceEnt; + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end ); + + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + if( !traceEnt->takedamage ) + return; + + if( traceEnt->client ) + { + if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + return; + + if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + if( !( traceEnt->client->ps.stats[ STAT_STATE ] & SS_GRABBED ) ) + { + AngleVectors( traceEnt->client->ps.viewangles, dir, NULL, NULL ); + traceEnt->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir ); + + //event for client side grab effect + G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 ); + } + + traceEnt->client->ps.stats[ STAT_STATE ] |= SS_GRABBED; + + if( ent->client->ps.weapon == WP_ALEVEL1 ) + traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_TIME; + else if( ent->client->ps.weapon == WP_ALEVEL1_UPG ) + traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME; + } + else if( traceEnt->s.eType == ET_BUILDABLE && + traceEnt->s.modelindex == BA_H_MGTURRET ) + { + if( !traceEnt->lev1Grabbed ) + G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 ); + + traceEnt->lev1Grabbed = qtrue; + traceEnt->lev1GrabTime = level.time; + } +} + +/* +=============== +poisonCloud +=============== +*/ +void poisonCloud( gentity_t *ent ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE }; + vec3_t mins, maxs; + int i, num; + gentity_t *humanPlayer; + trace_t tr; + + VectorAdd( ent->client->ps.origin, range, maxs ); + VectorSubtract( ent->client->ps.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + humanPlayer = &g_entities[ entityList[ i ] ]; + + if( humanPlayer->client && humanPlayer->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, humanPlayer->client->ps.stats ) ) + continue; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, humanPlayer->client->ps.stats ) ) + continue; + + trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->s.origin, humanPlayer->s.number, MASK_SHOT ); + + //can't see target from here + if( tr.entityNum == ENTITYNUM_WORLD ) + continue; + + if( !( humanPlayer->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) ) + { + humanPlayer->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED; + humanPlayer->client->lastPoisonCloudedTime = level.time; + humanPlayer->client->lastPoisonCloudedClient = ent; + G_SendCommandFromServer( humanPlayer->client->ps.clientNum, "poisoncloud" ); + } + } + } +} + + +/* +====================================================================== + +LEVEL2 + +====================================================================== +*/ + +#define MAX_ZAPS 64 + +static zap_t zaps[ MAX_CLIENTS ]; + +/* +=============== +G_FindNewZapTarget +=============== +*/ +static gentity_t *G_FindNewZapTarget( gentity_t *ent ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE }; + vec3_t mins, maxs; + int i, j, k, num; + gentity_t *enemy; + trace_t tr; + + VectorScale( range, 1.0f / M_ROOT3, range ); + VectorAdd( ent->s.origin, range, maxs ); + VectorSubtract( ent->s.origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for( i = 0; i < num; i++ ) + { + enemy = &g_entities[ entityList[ i ] ]; + + if( ( ( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || + ( enemy->s.eType == ET_BUILDABLE && + BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) && enemy->health > 0 ) + { + qboolean foundOldTarget = qfalse; + + trap_Trace( &tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT ); + + //can't see target from here + if( tr.entityNum == ENTITYNUM_WORLD ) + continue; + + for( j = 0; j < MAX_ZAPS; j++ ) + { + zap_t *zap = &zaps[ j ]; + + for( k = 0; k < zap->numTargets; k++ ) + { + if( zap->targets[ k ] == enemy ) + { + foundOldTarget = qtrue; + break; + } + } + + if( foundOldTarget ) + break; + } + + // enemy is already targetted + if( foundOldTarget ) + continue; + + return enemy; + } + } + + return NULL; +} + +/* +=============== +G_UpdateZapEffect +=============== +*/ +static void G_UpdateZapEffect( zap_t *zap ) +{ + int j; + gentity_t *effect = zap->effectChannel; + + effect->s.eType = ET_LEV2_ZAP_CHAIN; + effect->classname = "lev2zapchain"; + G_SetOrigin( effect, zap->creator->s.origin ); + effect->s.powerups = zap->creator->s.number; + + effect->s.time = effect->s.time2 = effect->s.constantLight = -1; + + for( j = 0; j < zap->numTargets; j++ ) + { + int number = zap->targets[ j ]->s.number; + + switch( j ) + { + case 0: effect->s.time = number; break; + case 1: effect->s.time2 = number; break; + case 2: effect->s.constantLight = number; break; + default: break; + } + } + + trap_LinkEntity( effect ); +} + +/* +=============== +G_CreateNewZap +=============== +*/ +static void G_CreateNewZap( gentity_t *creator, gentity_t *target ) +{ + int i, j; + zap_t *zap; + + for( i = 0; i < MAX_ZAPS; i++ ) + { + zap = &zaps[ i ]; + + if( !zap->used ) + { + zap->used = qtrue; + + zap->timeToLive = LEVEL2_AREAZAP_TIME; + + zap->creator = creator; + + zap->targets[ 0 ] = target; + zap->numTargets = 1; + + for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ ) + { + zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] ); + + if( zap->targets[ j ] ) + zap->numTargets++; + } + + zap->effectChannel = G_Spawn( ); + G_UpdateZapEffect( zap ); + + return; + } + } +} + + +/* +=============== +G_UpdateZaps +=============== +*/ +void G_UpdateZaps( int msec ) +{ + int i, j; + zap_t *zap; + int damage; + + for( i = 0; i < MAX_ZAPS; i++ ) + { + zap = &zaps[ i ]; + + if( zap->used ) + { + //check each target is valid + for( j = 0; j < zap->numTargets; j++ ) + { + gentity_t *source; + gentity_t *target = zap->targets[ j ]; + + if( j == 0 ) + source = zap->creator; + else + source = zap->targets[ j - 1 ]; + + if( target->health <= 0 || !target->inuse || //early out + Distance( source->s.origin, target->s.origin ) > LEVEL2_AREAZAP_RANGE ) + { + target = zap->targets[ j ] = G_FindNewZapTarget( source ); + + //couldn't find a target, so forget about the rest of the chain + if( !target ) + zap->numTargets = j; + } + } + + if( zap->numTargets ) + { + for( j = 0; j < zap->numTargets; j++ ) + { + gentity_t *source; + gentity_t *target = zap->targets[ j ]; + float r = 1.0f / zap->numTargets; + float damageFraction = 2 * r - 2 * j * r * r - r * r; + vec3_t forward; + + if( j == 0 ) + source = zap->creator; + else + source = zap->targets[ j - 1 ]; + + damage = ceil( ( (float)msec / LEVEL2_AREAZAP_TIME ) * + LEVEL2_AREAZAP_DMG * damageFraction ); + + VectorSubtract( target->s.origin, source->s.origin, forward ); + VectorNormalize( forward ); + + //do the damage + if( damage ) + G_Damage( target, source, zap->creator, forward, target->s.origin, + damage, DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP ); + } + } + + G_UpdateZapEffect( zap ); + + zap->timeToLive -= msec; + + if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 ) + { + zap->used = qfalse; + G_FreeEntity( zap->effectChannel ); + } + } + } +} + +/* +=============== +areaZapFire +=============== +*/ +void areaZapFire( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *traceEnt; + vec3_t mins, maxs; + + VectorSet( mins, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH ); + VectorSet( maxs, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH ); + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end ); + + trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + + if( tr.surfaceFlags & SURF_NOIMPACT ) + return; + + traceEnt = &g_entities[ tr.entityNum ]; + + if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || + ( traceEnt->s.eType == ET_BUILDABLE && + BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 ) + { + G_CreateNewZap( ent, traceEnt ); + } +} + + +/* +====================================================================== + +LEVEL3 + +====================================================================== +*/ + +/* +=============== +CheckPounceAttack +=============== +*/ +qboolean CheckPounceAttack( gentity_t *ent ) +{ + trace_t tr; + vec3_t end; + gentity_t *tent; + gentity_t *traceEnt; + int damage; + vec3_t mins, maxs; + + VectorSet( mins, -LEVEL3_POUNCE_WIDTH, -LEVEL3_POUNCE_WIDTH, -LEVEL3_POUNCE_WIDTH ); + VectorSet( maxs, LEVEL3_POUNCE_WIDTH, LEVEL3_POUNCE_WIDTH, LEVEL3_POUNCE_WIDTH ); + + if( !ent->client->allowedToPounce ) + return qfalse; + + if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + ent->client->allowedToPounce = qfalse; + return qfalse; + } + + if( ent->client->ps.weaponTime ) + return qfalse; + + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + + VectorMA( muzzle, LEVEL3_POUNCE_RANGE, forward, end ); + + trap_Trace( &tr, ent->s.origin, mins, maxs, end, ent->s.number, MASK_SHOT ); + + //miss + if( tr.fraction >= 1.0 ) + return qfalse; + + if( tr.surfaceFlags & SURF_NOIMPACT ) + return qfalse; + + traceEnt = &g_entities[ tr.entityNum ]; + + // send blood impact + if( traceEnt->takedamage && traceEnt->client ) + { + tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); + tent->s.otherEntityNum = traceEnt->s.number; + tent->s.eventParm = DirToByte( tr.plane.normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( !traceEnt->takedamage ) + return qfalse; + + damage = (int)( ( (float)ent->client->pouncePayload / (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG ); + + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, + DAMAGE_NO_KNOCKBACK|DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE ); + + ent->client->ps.weaponTime += LEVEL3_POUNCE_TIME; + ent->client->allowedToPounce = qfalse; + + return qtrue; +} + +void bounceBallFire( gentity_t *ent ) +{ + gentity_t *m; + + m = fire_bounceBall( ent, muzzle, forward ); + +// VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics +} + + +/* +====================================================================== + +LEVEL4 + +====================================================================== +*/ + +/* +=============== +ChargeAttack +=============== +*/ +void ChargeAttack( gentity_t *ent, gentity_t *victim ) +{ + gentity_t *tent; + int damage; + vec3_t forward, normal; + + if( level.time < victim->chargeRepeat ) + return; + + victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT; + + VectorSubtract( victim->s.origin, ent->s.origin, forward ); + VectorNormalize( forward ); + VectorNegate( forward, normal ); + + if( victim->client ) + { + tent = G_TempEntity( victim->s.origin, EV_MISSILE_HIT ); + tent->s.otherEntityNum = victim->s.number; + tent->s.eventParm = DirToByte( normal ); + tent->s.weapon = ent->s.weapon; + tent->s.generic1 = ent->s.generic1; //weaponMode + } + + if( !victim->takedamage ) + return; + + damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG ); + + G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE ); +} + +//====================================================================== + +/* +=============== +CalcMuzzlePoint + +set muzzle location relative to pivoting eye +=============== +*/ +void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) +{ + VectorCopy( ent->s.pos.trBase, muzzlePoint ); + muzzlePoint[ 2 ] += ent->client->ps.viewheight; + VectorMA( muzzlePoint, 1, forward, muzzlePoint ); + VectorMA( muzzlePoint, 1, right, muzzlePoint ); + // snap to integer coordinates for more efficient network bandwidth usage + SnapVector( muzzlePoint ); +} + +/* +=============== +FireWeapon3 +=============== +*/ +void FireWeapon3( gentity_t *ent ) +{ + if( ent->client ) + { + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + } + else + { + AngleVectors( ent->s.angles2, forward, right, up ); + VectorCopy( ent->s.pos.trBase, muzzle ); + } + + // fire the specific weapon + switch( ent->s.weapon ) + { + case WP_ALEVEL3_UPG: + bounceBallFire( ent ); + break; + + case WP_ABUILD2: + slowBlobFire( ent ); + break; + + default: + break; + } +} + +/* +=============== +FireWeapon2 +=============== +*/ +void FireWeapon2( gentity_t *ent ) +{ + if( ent->client ) + { + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + } + else + { + AngleVectors( ent->s.angles2, forward, right, up ); + VectorCopy( ent->s.pos.trBase, muzzle ); + } + + // fire the specific weapon + switch( ent->s.weapon ) + { + case WP_ALEVEL1_UPG: + poisonCloud( ent ); + break; + case WP_ALEVEL2_UPG: + areaZapFire( ent ); + break; + + case WP_LUCIFER_CANNON: + LCChargeFire( ent, qtrue ); + break; + + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + case WP_HBUILD2: + cancelBuildFire( ent ); + break; + default: + break; + } +} + +/* +=============== +FireWeapon +=============== +*/ +void FireWeapon( gentity_t *ent ) +{ + if( ent->client ) + { + // set aiming directions + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, muzzle ); + } + else + { + AngleVectors( ent->turretAim, forward, right, up ); + VectorCopy( ent->s.pos.trBase, muzzle ); + } + + // fire the specific weapon + switch( ent->s.weapon ) + { + case WP_ALEVEL1: + case WP_ALEVEL1_UPG: + meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); + break; + case WP_ALEVEL3: + case WP_ALEVEL3_UPG: + meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); + break; + case WP_ALEVEL2: + meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); + break; + case WP_ALEVEL2_UPG: + meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); + break; + case WP_ALEVEL4: + meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW ); + break; + + case WP_BLASTER: + blasterFire( ent ); + break; + case WP_MACHINEGUN: + bulletFire( ent, RIFLE_SPREAD, RIFLE_DMG, MOD_MACHINEGUN ); + break; + case WP_SHOTGUN: + shotgunFire( ent ); + break; + case WP_CHAINGUN: + bulletFire( ent, CHAINGUN_SPREAD, CHAINGUN_DMG, MOD_CHAINGUN ); + break; + case WP_FLAMER: + flamerFire( ent ); + break; + case WP_PULSE_RIFLE: + pulseRifleFire( ent ); + break; + case WP_MASS_DRIVER: + massDriverFire( ent ); + break; + case WP_LUCIFER_CANNON: + LCChargeFire( ent, qfalse ); + break; + case WP_LAS_GUN: + lasGunFire( ent ); + break; + case WP_PAIN_SAW: + painSawFire( ent ); + break; + case WP_GRENADE: + throwGrenade( ent ); + break; + + case WP_LOCKBLOB_LAUNCHER: + lockBlobLauncherFire( ent ); + break; + case WP_HIVE: + hiveFire( ent ); + break; + case WP_TESLAGEN: + teslaFire( ent ); + break; + case WP_MGTURRET: + bulletFire( ent, MGTURRET_SPREAD, MGTURRET_DMG, MOD_MGTURRET ); + break; + + case WP_ABUILD: + case WP_ABUILD2: + buildFire( ent, MN_A_BUILD ); + break; + case WP_HBUILD: + case WP_HBUILD2: + buildFire( ent, MN_H_BUILD ); + break; + default: + break; + } +} + |