/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2002-2015 Q3Rally Team (Per Thormann - q3rally@gmail.com) This file is part of q3rally source code. q3rally source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. q3rally source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with q3rally; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // g_weapon.c // perform the server side effects of a weapon firing #include "g_local.h" static float s_quadFactor; vec3_t forward, right, up; static vec3_t muzzle; #define NUM_NAILSHOTS 15 /* ================ G_BounceProjectile ================ */ void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) { vec3_t v, newv; float dot; VectorSubtract( impact, start, v ); dot = DotProduct( v, dir ); VectorMA( v, -2*dot, dir, newv ); VectorNormalize(newv); VectorMA(impact, 8192, newv, endout); } /* ====================================================================== GAUNTLET ====================================================================== */ void Weapon_Gauntlet( gentity_t *ent ) { } /* =============== CheckGauntletAttack =============== */ qboolean CheckGauntletAttack( gentity_t *ent ) { trace_t tr; vec3_t end; gentity_t *tent; gentity_t *traceEnt; int damage; // set aiming directions AngleVectors (ent->client->ps.viewangles, forward, right, up); CalcMuzzlePoint ( ent, forward, right, up, muzzle ); VectorMA (muzzle, CAR_LENGTH/2, forward, end); trap_Trace (&tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT); if ( tr.surfaceFlags & SURF_NOIMPACT ) { return qfalse; } if ( ent->client->noclip ) { return qfalse; } if (g_entities[ tr.entityNum ].flags & FL_EXTRA_BBOX) traceEnt = &g_entities[ g_entities[ tr.entityNum ].r.ownerNum ]; else traceEnt = &g_entities[ tr.entityNum ]; // send blood impact if ( traceEnt->takedamage && traceEnt->client ) { tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); tent->s.otherEntityNum = traceEnt->s.number; tent->s.eventParm = DirToByte( tr.plane.normal ); tent->s.weapon = ent->s.weapon; } if ( !traceEnt->takedamage) { return qfalse; } if (ent->client->ps.powerups[PW_QUAD] ) { G_AddEvent( ent, EV_POWERUP_QUAD, 0 ); s_quadFactor = g_quadfactor.value; } else { s_quadFactor = 1; } #ifdef MISSIONPACK if( ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER ) { s_quadFactor *= 2; } #endif damage = 50 * s_quadFactor; G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_WEAPON, MOD_GAUNTLET ); return qtrue; } //Q3Rally Code Start /* ======================================================================= FLAME THROWER ======================================================================= */ void Weapon_fire_flame (gentity_t *ent ) { gentity_t *m; m = fire_flame(ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; } /* ======================================================================= FLAME THROWER SPREAD - Altfire ======================================================================= */ void Weapon_cluster_fire_flame (gentity_t *ent ) { gentity_t *m, *n, *o; vec3_t temp; m = fire_cluster_flame(ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; VectorAdd(forward, right, temp); n = fire_cluster_flame(ent, muzzle, temp); n->damage *= s_quadFactor; n->splashDamage *= s_quadFactor; VectorInverse(right); VectorAdd(forward, right, temp); o = fire_cluster_flame(ent, muzzle, temp); o->damage *= s_quadFactor; o->splashDamage *= s_quadFactor; } /* ====================================================================== MACHINEGUN ====================================================================== */ /* ====================== SnapVectorTowards Round a vector to integers for more efficient network transmission, but make sure that it rounds towards a given point rather than blindly truncating. This prevents it from truncating into a wall. ====================== */ void SnapVectorTowards( vec3_t v, vec3_t to ) { int i; for ( i = 0 ; i < 3 ; i++ ) { if ( to[i] <= v[i] ) { v[i] = floor(v[i]); } else { v[i] = ceil(v[i]); } } } #ifdef MISSIONPACK #define CHAINGUN_SPREAD 600 #define CHAINGUN_DAMAGE 7 #endif #define MACHINEGUN_SPREAD 200 #define MACHINEGUN_DAMAGE 7 #define MACHINEGUN_TEAM_DAMAGE 5 // wimpier MG in teamplay void Bullet_Fire (gentity_t *ent, float spread, int damage, int mod ) { trace_t tr; vec3_t end; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif float r; float u; gentity_t *tent; gentity_t *traceEnt; int i, passent; damage *= s_quadFactor; r = random() * M_PI * 2.0f; u = sin(r) * crandom() * spread * 16; r = cos(r) * crandom() * spread * 16; VectorMA (muzzle, 8192*16, forward, end); VectorMA (end, r, right, end); VectorMA (end, u, up, end); passent = ent->s.number; for (i = 0; i < 10; i++) { trap_Trace (&tr, muzzle, NULL, NULL, end, passent, MASK_SHOT); if ( tr.surfaceFlags & SURF_NOIMPACT ) { return; } if (g_entities[ tr.entityNum ].flags & FL_EXTRA_BBOX){ traceEnt = &g_entities[ g_entities[ tr.entityNum ].r.ownerNum ]; } else traceEnt = &g_entities[ tr.entityNum ]; // snap the endpos to integers, but nudged towards the line SnapVectorTowards( tr.endpos, muzzle ); // send bullet impact if ( traceEnt->takedamage && traceEnt->client ) { tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH ); tent->s.eventParm = traceEnt->s.number; if( LogAccuracyHit( traceEnt, ent ) ) { ent->client->accuracy_hits++; } } else { tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL ); tent->s.eventParm = DirToByte( tr.plane.normal ); } tent->s.otherEntityNum = ent->s.number; if ( traceEnt->takedamage) { #ifdef MISSIONPACK if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) { if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) { G_BounceProjectile( muzzle, impactpoint, bouncedir, end ); VectorCopy( impactpoint, muzzle ); // the player can hit him/herself with the bounced rail passent = ENTITYNUM_NONE; } else { VectorCopy( tr.endpos, muzzle ); passent = traceEnt->s.number; } continue; } else { #endif G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_WEAPON, mod); #ifdef MISSIONPACK } #endif } break; } } /* ====================================================================== BFG ====================================================================== */ void BFG_Fire ( gentity_t *ent ) { gentity_t *m; m = fire_bfg (ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== SHOTGUN ====================================================================== */ // DEFAULT_SHOTGUN_SPREAD and DEFAULT_SHOTGUN_COUNT are in bg_public.h, because // client predicts same spreads #define DEFAULT_SHOTGUN_DAMAGE 10 qboolean ShotgunPellet( vec3_t start, vec3_t end, gentity_t *ent ) { trace_t tr; int damage, i, passent; gentity_t *traceEnt; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif vec3_t tr_start, tr_end; qboolean hitClient = qfalse; passent = ent->s.number; VectorCopy( start, tr_start ); VectorCopy( end, tr_end ); for (i = 0; i < 10; i++) { trap_Trace (&tr, tr_start, NULL, NULL, tr_end, passent, MASK_SHOT); if (g_entities[ tr.entityNum ].flags & FL_EXTRA_BBOX) traceEnt = &g_entities[ g_entities[ tr.entityNum ].r.ownerNum ]; else traceEnt = &g_entities[ tr.entityNum ]; // send bullet impact if ( tr.surfaceFlags & SURF_NOIMPACT ) { return qfalse; } if ( traceEnt->takedamage) { damage = DEFAULT_SHOTGUN_DAMAGE * s_quadFactor; #ifdef MISSIONPACK if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) { if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) { G_BounceProjectile( tr_start, impactpoint, bouncedir, tr_end ); VectorCopy( impactpoint, tr_start ); // the player can hit him/herself with the bounced rail passent = ENTITYNUM_NONE; } else { VectorCopy( tr.endpos, tr_start ); passent = traceEnt->s.number; } continue; } #endif if( LogAccuracyHit( traceEnt, ent ) ) { hitClient = qtrue; } G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_WEAPON, MOD_SHOTGUN); return hitClient; } return qfalse; } return qfalse; } // this should match CG_ShotgunPattern void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent ) { int i; float r, u; vec3_t end; vec3_t forward, right, up; qboolean hitClient = qfalse; // derive the right and up vectors from the forward vector, because // the client won't have any other information VectorNormalize2( origin2, forward ); PerpendicularVector( right, forward ); CrossProduct( forward, right, up ); // generate the "random" spread pattern for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) { r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; VectorMA( origin, 8192 * 16, forward, end); VectorMA (end, r, right, end); VectorMA (end, u, up, end); if( ShotgunPellet( origin, end, ent ) && !hitClient ) { hitClient = qtrue; ent->client->accuracy_hits++; } } } void weapon_supershotgun_fire (gentity_t *ent) { gentity_t *tent; // send shotgun blast tent = G_TempEntity( muzzle, EV_SHOTGUN ); VectorScale( forward, 4096, tent->s.origin2 ); SnapVector( tent->s.origin2 ); tent->s.eventParm = rand() & 255; // seed for spread pattern tent->s.otherEntityNum = ent->s.number; ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent ); } /* ====================================================================== GRENADE LAUNCHER ====================================================================== */ void weapon_grenadelauncher_fire (gentity_t *ent) { gentity_t *m; // extra vertical velocity forward[2] += 0.2f; VectorNormalize( forward ); m = fire_grenade (ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== CLUSTER GRENADE LAUNCHER - Altfire ====================================================================== */ void weapon_cluster_grenadelauncher_fire (gentity_t *ent) { gentity_t *m; // extra vertical velocity forward[2] += 0.2f; VectorNormalize( forward ); m = fire_cluster_grenade (ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; VectorScale(forward, 2000, m->s.pos.trDelta) ; //VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== ROCKET ====================================================================== */ void Weapon_RocketLauncher_Fire (gentity_t *ent) { gentity_t *m; m = fire_rocket (ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== HOMING ROCKET - Altfire ====================================================================== */ void Weapon_Homing_RocketLauncher_Fire (gentity_t *ent) { gentity_t *m; m = fire_homing_rocket (ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== PLASMA GUN ====================================================================== */ void Weapon_Plasmagun_Fire (gentity_t *ent) { gentity_t *m; m = fire_plasma (ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== PLASMA GUN - Altfire ====================================================================== */ void Weapon_Plasmagun_Circular_Fire (gentity_t *ent) { gentity_t *m; // gentity_t *n; vec3_t temp, temp2; //forward[0] += (float)sin( m->s.pos.trTime / m->s.pos.trDuration ); temp[0] = (float) sin( level.time * 2 * 3.14 / 1000.0); temp[1] = 0; temp[2] = 0; temp2[0] = (float) cos( level.time * 2 * 3.14 / 1000.0); temp2[1] = 0; temp2[2] = 0; VectorAdd( forward, temp, temp); VectorAdd( right, temp2, temp2); VectorAdd( temp, temp2, temp2); //VectorMA( m->s.pos.trBase, 100, forward, temp2); m = fire_plasma_bounce(ent, muzzle, temp2); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; /* //right[0] += (float)cos( n->s.pos.trTime / n->s.pos.trDuration ); //VectorMA(n->s.pos.trBase, 10, right, temp2); n = fire_plasma_circular_def(ent, muzzle, temp2); n->damage *= s_quadFactor; n->splashDamage *= s_quadFactor;*/ //VectorCopy(forward, temp); //temp[0] = forward[2]; //temp[2] = -forward[0]; //forward[2] -= 0.1f; //VectorCopy(temp,forward); //VectorNormalize(forward); /* m->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; m->s.pos.trBase = 0; m->s.pos.trDuration = 100; */ // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== RAILGUN ====================================================================== */ /* ================= weapon_railgun_fire ================= */ #define MAX_RAIL_HITS 4 void weapon_railgun_fire (gentity_t *ent) { vec3_t end; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif trace_t trace; gentity_t *tent; gentity_t *traceEnt; int damage; int i; int hits; int unlinked; int passent; gentity_t *unlinkedEntities[MAX_RAIL_HITS]; damage = 75 * s_quadFactor; VectorMA (muzzle, 8192, forward, end); // trace only against the solids, so the railgun will go through people unlinked = 0; hits = 0; passent = ent->s.number; do { trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT ); if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) { break; } if (g_entities[ trace.entityNum ].flags & FL_EXTRA_BBOX) traceEnt = &g_entities[ g_entities[ trace.entityNum ].r.ownerNum ]; else traceEnt = &g_entities[ trace.entityNum ]; if ( traceEnt->takedamage ) { #ifdef MISSIONPACK if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) { if ( G_InvulnerabilityEffect( traceEnt, forward, trace.endpos, impactpoint, bouncedir ) ) { G_BounceProjectile( muzzle, impactpoint, bouncedir, end ); // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards( trace.endpos, muzzle ); // send railgun beam effect tent = G_TempEntity( trace.endpos, EV_RAILTRAIL ); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy( muzzle, tent->s.origin2 ); // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 4, right, tent->s.origin2 ); VectorMA( tent->s.origin2, -1, up, tent->s.origin2 ); tent->s.eventParm = 255; // don't make the explosion at the end // VectorCopy( impactpoint, muzzle ); // the player can hit him/herself with the bounced rail passent = ENTITYNUM_NONE; } } else { if( LogAccuracyHit( traceEnt, ent ) ) { hits++; } G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, DAMAGE_WEAPON, MOD_RAILGUN); } #else if( LogAccuracyHit( traceEnt, ent ) ) { hits++; } G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, DAMAGE_WEAPON, MOD_RAILGUN); #endif } if ( trace.contents & CONTENTS_SOLID ) { break; // we hit something solid enough to stop the beam } // unlink this entity, so the next trace will go past it trap_UnlinkEntity( traceEnt ); unlinkedEntities[unlinked] = traceEnt; unlinked++; } while ( unlinked < MAX_RAIL_HITS ); // link back in any entities we unlinked for ( i = 0 ; i < unlinked ; i++ ) { trap_LinkEntity( unlinkedEntities[i] ); } // the final trace endpos will be the terminal point of the rail trail // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards( trace.endpos, muzzle ); // send railgun beam effect tent = G_TempEntity( trace.endpos, EV_RAILTRAIL ); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy( muzzle, tent->s.origin2 ); // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 4, right, tent->s.origin2 ); VectorMA( tent->s.origin2, -1, up, tent->s.origin2 ); // no explosion at end if SURF_NOIMPACT, but still make the trail if ( trace.surfaceFlags & SURF_NOIMPACT ) { tent->s.eventParm = 255; // don't make the explosion at the end } else { tent->s.eventParm = DirToByte( trace.plane.normal ); } tent->s.clientNum = ent->s.clientNum; // give the shooter a reward sound if they have made two railgun hits in a row if ( hits == 0 ) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; if ( ent->client->accurateCount >= 2 ) { ent->client->accurateCount -= 2; ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; // add the sprite over the player's head ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE; ent->client->rewardTime = level.time + REWARD_SPRITE_TIME; } ent->client->accuracy_hits++; } } /* ====================================================================== TELEFRAG GUN - Altfire to Railgun ====================================================================== */ /* void TelefragPlayer( gentity_t *player, vec3_t origin ) { gentity_t *tent; // use temp events at source and destination to prevent the effect // from getting dropped by a second player event if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = player->s.clientNum; tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = player->s.clientNum; } // unlink to make sure it can't possibly interfere with G_KillBox trap_UnlinkEntity (player); VectorCopy ( origin, player->client->ps.origin ); player->client->ps.origin[2] += 1; // toggle the teleport bit so the client knows to not lerp player->client->ps.eFlags ^= EF_TELEPORT_BIT; // kill anything at the destination if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { G_KillBox (player); } // save results of pmove BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue ); // use the precise origin for linking VectorCopy( player->client->ps.origin, player->r.currentOrigin ); if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { trap_LinkEntity (player); } } */ void weapon_telefrag_fire (gentity_t *ent,vec3_t muzzle,vec3_t forward,vec3_t right,vec3_t up) { vec3_t end; trace_t trace; gentity_t *tent; gentity_t *traceEnt; int damage = 1000; int hits = 0; int passent = ent->s.number; VectorMA (muzzle, 8192, forward, end); // Trace the projectile trap_Trace (&trace, muzzle, NULL, NULL, end, passent, MASK_SHOT ); // The entity is a valid entity if ( trace.entityNum < ENTITYNUM_MAX_NORMAL ) { // Who exactly is this entity (a reference to the entity structure) traceEnt = &g_entities[ trace.entityNum ]; // Can this entity be damaged? if ( traceEnt->takedamage ) { if(LogAccuracyHit(traceEnt,ent)) { hits++; } // It is important that all this checking is done. If you try and // telefrag a door or a spectator, the game *will* crash. // is the entity a client, alive and not a spectator if((traceEnt->client) && (traceEnt->client->ps.pm_type != PM_DEAD) && (traceEnt->client->sess.sessionTeam != TEAM_SPECTATOR)) { // if the attacker was on the same team and TF_NO_FRIENDLY_FIRE is set // do not telefrag, just do a normal damage if (OnSameTeam(traceEnt,ent) && (!g_friendlyFire.integer)) { G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); // Not on same team, or on same team and can friendly fire // Damage, then telefrag }else{ G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); TelefragPlayer(ent, traceEnt->r.currentOrigin); } }else{ // Damage G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0, MOD_RAILGUN); } } } // snap the endpos to integers to save net bandwidth, but nudged towards the line SnapVectorTowards( trace.endpos, muzzle ); // send railgun beam effect tent = G_TempEntity( trace.endpos, EV_RAILTRAIL ); // set player number for custom colors on the railtrail tent->s.clientNum = ent->s.clientNum; VectorCopy( muzzle, tent->s.origin2 ); // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 4, right, tent->s.origin2 ); VectorMA( tent->s.origin2, -1, up, tent->s.origin2 ); // no explosion at end if SURF_NOIMPACT, but still make the trail if ( trace.surfaceFlags & SURF_NOIMPACT ) { tent->s.eventParm = 255; // don't make the explosion at the end } else { tent->s.eventParm = DirToByte( trace.plane.normal ); } tent->s.clientNum = ent->s.clientNum; // give the shooter a reward sound if they have made two railgun hits in a row if ( hits == 0 ) { // complete miss ent->client->accurateCount = 0; } else { // check for "impressive" reward sound ent->client->accurateCount += hits; if ( ent->client->accurateCount >= 2 ) { ent->client->accurateCount -= 2; ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; // add the sprite over the player's head ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE; ent->client->rewardTime = level.time + REWARD_SPRITE_TIME; } ent->client->accuracy_hits++; } } /* ====================================================================== LIGHTNING GUN ====================================================================== */ void Weapon_LightningFire( gentity_t *ent ) { trace_t tr; vec3_t end; #ifdef MISSIONPACK vec3_t impactpoint, bouncedir; #endif gentity_t *traceEnt, *tent; int damage, i, passent; damage = 8 * s_quadFactor; passent = ent->s.number; for (i = 0; i < 10; i++) { VectorMA( muzzle, LIGHTNING_RANGE, forward, end ); trap_Trace( &tr, muzzle, NULL, NULL, end, passent, MASK_SHOT ); #ifdef MISSIONPACK // if not the first trace (the lightning bounced of an invulnerability sphere) if (i) { // add bounced off lightning bolt temp entity // the first lightning bolt is a cgame only visual // tent = G_TempEntity( muzzle, EV_LIGHTNINGBOLT ); VectorCopy( tr.endpos, end ); SnapVector( end ); VectorCopy( end, tent->s.origin2 ); } #endif if ( tr.entityNum == ENTITYNUM_NONE ) { return; } // STONELANCE // traceEnt = &g_entities[ tr.entityNum ]; if (g_entities[ tr.entityNum ].flags & FL_EXTRA_BBOX) traceEnt = &g_entities[ g_entities[ tr.entityNum ].r.ownerNum ]; else traceEnt = &g_entities[ tr.entityNum ]; // END if ( traceEnt->takedamage) { #ifdef MISSIONPACK if ( traceEnt->client && traceEnt->client->invulnerabilityTime > level.time ) { if (G_InvulnerabilityEffect( traceEnt, forward, tr.endpos, impactpoint, bouncedir )) { G_BounceProjectile( muzzle, impactpoint, bouncedir, end ); VectorCopy( impactpoint, muzzle ); VectorSubtract( end, impactpoint, forward ); VectorNormalize(forward); // the player can hit him/herself with the bounced lightning passent = ENTITYNUM_NONE; } else { VectorCopy( tr.endpos, muzzle ); passent = traceEnt->s.number; } continue; } #endif if( LogAccuracyHit( traceEnt, ent ) ) { ent->client->accuracy_hits++; } G_Damage( traceEnt, ent, ent, forward, tr.endpos, // STONELANCE // damage, 0, MOD_LIGHTNING); damage, DAMAGE_WEAPON, MOD_LIGHTNING); // END } 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; } else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) { tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); tent->s.eventParm = DirToByte( tr.plane.normal ); } break; } } #ifdef MISSIONPACK /* ====================================================================== NAILGUN ====================================================================== */ void Weapon_Nailgun_Fire (gentity_t *ent) { gentity_t *m; int count; for( count = 0; count < NUM_NAILSHOTS; count++ ) { m = fire_nail (ent, muzzle, forward, right, up ); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; } // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } /* ====================================================================== PROXIMITY MINE LAUNCHER ====================================================================== */ void weapon_proxlauncher_fire (gentity_t *ent) { gentity_t *m; // extra vertical velocity forward[2] += 0.2f; VectorNormalize( forward ); m = fire_prox (ent, muzzle, forward); m->damage *= s_quadFactor; m->splashDamage *= s_quadFactor; // VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta ); // "real" physics } #endif //====================================================================== /* =============== LogAccuracyHit =============== */ qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) { if( !target->takedamage ) { return qfalse; } if ( target == attacker ) { return qfalse; } if( !target->client ) { return qfalse; } if( !attacker->client ) { return qfalse; } if( target->client->ps.stats[STAT_HEALTH] <= 0 ) { return qfalse; } if ( OnSameTeam( target, attacker ) ) { return qfalse; } return qtrue; } /* =============== CalcMuzzlePoint set muzzle location relative to pivoting eye =============== */ void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { VectorCopy( ent->s.pos.trBase, muzzlePoint ); // STONELANCE // muzzlePoint[2] += ent->client->ps.viewheight; // VectorMA( muzzlePoint, 14, forward, muzzlePoint ); VectorMA( muzzlePoint, CAR_HEIGHT/2, up, muzzlePoint ); VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // END // snap to integer coordinates for more efficient network bandwidth usage SnapVector( muzzlePoint ); } /* =============== CalcMuzzlePointOrigin set muzzle location relative to pivoting eye =============== */ void CalcMuzzlePointOrigin ( gentity_t *ent, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) { VectorCopy( ent->s.pos.trBase, muzzlePoint ); // STONELANCE // muzzlePoint[2] += ent->client->ps.viewheight; VectorMA( muzzlePoint, CAR_HEIGHT/2, up, muzzlePoint ); // END VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // snap to integer coordinates for more efficient network bandwidth usage SnapVector( muzzlePoint ); } /* =============== FireWeapon =============== */ void FireWeapon( gentity_t *ent ) { // STONELANCE vec3_t delta, angles, end, mins, maxs; trace_t tr; int entNumber; gentity_t *traceEnt; int count = 0; // END if (ent->client->ps.powerups[PW_QUAD] ) { s_quadFactor = g_quadfactor.value; } else { s_quadFactor = 1; } #ifdef MISSIONPACK if( ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER ) { s_quadFactor *= 2; } #endif // track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked // STONELANCE // if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) { if( ent->s.weapon != WP_GAUNTLET ) { // END #ifdef MISSIONPACK if( ent->s.weapon == WP_NAILGUN ) { ent->client->accuracy_shots += NUM_NAILSHOTS; } else { ent->client->accuracy_shots++; } #else ent->client->accuracy_shots++; #endif } // set aiming directions AngleVectors (ent->client->ps.viewangles, forward, right, up); CalcMuzzlePointOrigin ( ent, ent->client->oldOrigin, forward, right, up, muzzle ); // STONELANCE (vertical autoaim) VectorSet(mins, -8, -8, -128); VectorSet(maxs, 8, 8, 128); VectorMA(muzzle, 8000, forward, end); trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_PLAYERCLIP | CONTENTS_BODY ); while ( tr.fraction < 1.0f && count < 10000 ){ if ( tr.fraction < 0.0001f ) break; if ( tr.allsolid ) break; count++; // if ( g_entities[ tr.entityNum ].flags & FL_EXTRA_BBOX ) // entNumber = g_entities[ tr.entityNum ].r.ownerNum; // else entNumber = tr.entityNum; traceEnt = &g_entities[ entNumber ]; if ( g_gametype.integer > GT_TEAM && traceEnt->client ){ // skip team members if (ent->client->sess.sessionTeam == traceEnt->client->sess.sessionTeam) continue; } if ( traceEnt->takedamage ){ VectorSubtract( traceEnt->r.currentOrigin, muzzle, delta ); vectoangles( delta, angles ); angles[YAW] = ent->client->ps.viewangles[YAW]; AngleVectors( angles, forward, right, up ); break; } trap_Trace( &tr, tr.endpos, mins, maxs, end, entNumber, CONTENTS_PLAYERCLIP | CONTENTS_BODY ); } if ( count == 10000 ){ Com_Printf( "Detected long loop in verticle autoaiming\n" ); Com_Printf( "fraction %f, startsolid %i, contents %i, entityNum %i\n", tr.fraction, tr.startsolid, tr.contents, tr.entityNum ); } // END // fire the specific weapon switch( ent->s.weapon ) { case WP_GAUNTLET: Weapon_Gauntlet( ent ); break; case WP_LIGHTNING: Weapon_LightningFire( ent ); break; case WP_SHOTGUN: weapon_supershotgun_fire( ent ); break; case WP_MACHINEGUN: if ( g_gametype.integer != GT_TEAM ) { Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_DAMAGE, MOD_MACHINEGUN ); } else { Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_TEAM_DAMAGE, MOD_MACHINEGUN ); } break; case WP_GRENADE_LAUNCHER: weapon_grenadelauncher_fire( ent ); break; case WP_ROCKET_LAUNCHER: Weapon_RocketLauncher_Fire( ent ); break; case WP_PLASMAGUN: Weapon_Plasmagun_Fire( ent ); break; case WP_RAILGUN: weapon_railgun_fire( ent ); break; case WP_BFG: BFG_Fire( ent ); break; case WP_FLAME_THROWER: Weapon_fire_flame( ent ); break; #ifdef MISSIONPACK case WP_NAILGUN: Weapon_Nailgun_Fire( ent ); break; case WP_PROX_LAUNCHER: weapon_proxlauncher_fire( ent ); break; case WP_CHAINGUN: Bullet_Fire( ent, CHAINGUN_SPREAD, CHAINGUN_DAMAGE, MOD_CHAINGUN ); break; #endif default: // FIXME G_Error( "Bad ent->s.weapon" ); break; } } /* =============== FireAltWeapon =============== */ void FireAltWeapon( gentity_t *ent ) { // STONELANCE vec3_t delta, angles, end, mins, maxs; trace_t tr; int entNumber; gentity_t *traceEnt; int count = 0; // END if (ent->client->ps.powerups[PW_QUAD] ) { s_quadFactor = g_quadfactor.value; } else { s_quadFactor = 1; } #ifdef MISSIONPACK if( ent->client->persistantPowerup && ent->client->persistantPowerup->item && ent->client->persistantPowerup->item->giTag == PW_DOUBLER ) { s_quadFactor *= 2; } #endif // track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked // STONELANCE // if( ent->s.weapon != WP_GRAPPLING_HOOK && ent->s.weapon != WP_GAUNTLET ) { if( ent->s.weapon != WP_GAUNTLET ) { // END #ifdef MISSIONPACK if( ent->s.weapon == WP_NAILGUN ) { ent->client->accuracy_shots += NUM_NAILSHOTS; } else { ent->client->accuracy_shots++; } #else ent->client->accuracy_shots++; #endif } // set aiming directions AngleVectors (ent->client->ps.viewangles, forward, right, up); CalcMuzzlePointOrigin ( ent, ent->client->oldOrigin, forward, right, up, muzzle ); // STONELANCE (vertical autoaim) VectorSet(mins, -8, -8, -128); VectorSet(maxs, 8, 8, 128); VectorMA(muzzle, 8000, forward, end); trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_PLAYERCLIP | CONTENTS_BODY ); while ( tr.fraction < 1.0f && count < 10000 ){ if ( tr.fraction < 0.0001f ) break; if ( tr.allsolid ) break; count++; // if ( g_entities[ tr.entityNum ].flags & FL_EXTRA_BBOX ) // entNumber = g_entities[ tr.entityNum ].r.ownerNum; // else entNumber = tr.entityNum; traceEnt = &g_entities[ entNumber ]; if ( g_gametype.integer > GT_TEAM && traceEnt->client ){ // skip team members if (ent->client->sess.sessionTeam == traceEnt->client->sess.sessionTeam) continue; } if ( traceEnt->takedamage ){ VectorSubtract( traceEnt->r.currentOrigin, muzzle, delta ); vectoangles( delta, angles ); angles[YAW] = ent->client->ps.viewangles[YAW]; AngleVectors( angles, forward, right, up ); break; } trap_Trace( &tr, tr.endpos, mins, maxs, end, entNumber, CONTENTS_PLAYERCLIP | CONTENTS_BODY ); } if ( count == 10000 ){ Com_Printf( "Detected long loop in verticle autoaiming\n" ); Com_Printf( "fraction %f, startsolid %i, contents %i, entityNum %i\n", tr.fraction, tr.startsolid, tr.contents, tr.entityNum ); } // END // fire the specific weapon switch( ent->s.weapon ) { case WP_GAUNTLET: Weapon_Gauntlet( ent ); break; case WP_LIGHTNING: Weapon_LightningFire( ent ); break; case WP_SHOTGUN: weapon_supershotgun_fire( ent ); break; case WP_MACHINEGUN: if ( g_gametype.integer != GT_TEAM ) { Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_DAMAGE, MOD_MACHINEGUN ); } else { Bullet_Fire( ent, MACHINEGUN_SPREAD, MACHINEGUN_TEAM_DAMAGE, MOD_MACHINEGUN ); } break; case WP_GRENADE_LAUNCHER: weapon_cluster_grenadelauncher_fire( ent ); break; case WP_ROCKET_LAUNCHER: Weapon_Homing_RocketLauncher_Fire( ent ); break; case WP_PLASMAGUN: Weapon_Plasmagun_Circular_Fire( ent ); break; case WP_RAILGUN: weapon_telefrag_fire( ent,muzzle,forward,right,up ); break; case WP_BFG: BFG_Fire( ent ); break; case WP_FLAME_THROWER: Weapon_cluster_fire_flame( ent ); break; #ifdef MISSIONPACK case WP_NAILGUN: Weapon_Nailgun_Fire( ent ); break; case WP_PROX_LAUNCHER: weapon_proxlauncher_fire( ent ); break; case WP_CHAINGUN: Bullet_Fire( ent, CHAINGUN_SPREAD, CHAINGUN_DAMAGE, MOD_CHAINGUN ); break; #endif default: // FIXME G_Error( "Bad ent->s.weapon" ); break; } } #ifdef MISSIONPACK /* =============== KamikazeRadiusDamage =============== */ static void KamikazeRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius ) { float dist; gentity_t *ent; int entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; vec3_t v; vec3_t dir; int i, e; if ( radius < 1 ) { radius = 1; } for ( i = 0 ; i < 3 ; i++ ) { mins[i] = origin[i] - radius; maxs[i] = origin[i] + radius; } numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0 ; e < numListedEntities ; e++ ) { ent = &g_entities[entityList[ e ]]; if (!ent->takedamage) { continue; } // dont hit things we have already hit if( ent->kamikazeTime > level.time ) { continue; } // find the distance from the edge of the bounding box for ( i = 0 ; i < 3 ; i++ ) { if ( origin[i] < ent->r.absmin[i] ) { v[i] = ent->r.absmin[i] - origin[i]; } else if ( origin[i] > ent->r.absmax[i] ) { v[i] = origin[i] - ent->r.absmax[i]; } else { v[i] = 0; } } dist = VectorLength( v ); if ( dist >= radius ) { continue; } // if( CanDamage (ent, origin) ) { VectorSubtract (ent->r.currentOrigin, origin, dir); // push the center of mass higher than the origin so players // get knocked into the air more dir[2] += 24; G_Damage( ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS|DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE ); ent->kamikazeTime = level.time + 3000; // } } } /* =============== KamikazeShockWave =============== */ static void KamikazeShockWave( vec3_t origin, gentity_t *attacker, float damage, float push, float radius ) { float dist; gentity_t *ent; int entityList[MAX_GENTITIES]; int numListedEntities; vec3_t mins, maxs; vec3_t v; vec3_t dir; int i, e; if ( radius < 1 ) radius = 1; for ( i = 0 ; i < 3 ; i++ ) { mins[i] = origin[i] - radius; maxs[i] = origin[i] + radius; } numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for ( e = 0 ; e < numListedEntities ; e++ ) { ent = &g_entities[entityList[ e ]]; // dont hit things we have already hit if( ent->kamikazeShockTime > level.time ) { continue; } // find the distance from the edge of the bounding box for ( i = 0 ; i < 3 ; i++ ) { if ( origin[i] < ent->r.absmin[i] ) { v[i] = ent->r.absmin[i] - origin[i]; } else if ( origin[i] > ent->r.absmax[i] ) { v[i] = origin[i] - ent->r.absmax[i]; } else { v[i] = 0; } } dist = VectorLength( v ); if ( dist >= radius ) { continue; } // if( CanDamage (ent, origin) ) { VectorSubtract (ent->r.currentOrigin, origin, dir); dir[2] += 24; G_Damage( ent, NULL, attacker, dir, origin, damage, DAMAGE_RADIUS|DAMAGE_NO_TEAM_PROTECTION, MOD_KAMIKAZE ); // dir[2] = 0; VectorNormalize(dir); if ( ent->client ) { ent->client->ps.velocity[0] = dir[0] * push; ent->client->ps.velocity[1] = dir[1] * push; ent->client->ps.velocity[2] = 100; } ent->kamikazeShockTime = level.time + 3000; // } } } /* =============== KamikazeDamage =============== */ static void KamikazeDamage( gentity_t *self ) { int i; float t; gentity_t *ent; vec3_t newangles; self->count += 100; if (self->count >= KAMI_SHOCKWAVE_STARTTIME) { // shockwave push back t = self->count - KAMI_SHOCKWAVE_STARTTIME; KamikazeShockWave(self->s.pos.trBase, self->activator, 25, 400, (int) (float) t * KAMI_SHOCKWAVE_MAXRADIUS / (KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME) ); } // if (self->count >= KAMI_EXPLODE_STARTTIME) { // do our damage t = self->count - KAMI_EXPLODE_STARTTIME; KamikazeRadiusDamage( self->s.pos.trBase, self->activator, 400, (int) (float) t * KAMI_BOOMSPHERE_MAXRADIUS / (KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME) ); } // either cycle or kill self if( self->count >= KAMI_SHOCKWAVE_ENDTIME ) { G_FreeEntity( self ); return; } self->nextthink = level.time + 100; // add earth quake effect newangles[0] = crandom() * 2; newangles[1] = crandom() * 2; newangles[2] = 0; for (i = 0; i < MAX_CLIENTS; i++) { ent = &g_entities[i]; if (!ent->inuse) continue; if (!ent->client) continue; if (ent->client->ps.groundEntityNum != ENTITYNUM_NONE) { ent->client->ps.velocity[0] += crandom() * 120; ent->client->ps.velocity[1] += crandom() * 120; ent->client->ps.velocity[2] = 30 + random() * 25; } ent->client->ps.delta_angles[0] += ANGLE2SHORT(newangles[0] - self->movedir[0]); ent->client->ps.delta_angles[1] += ANGLE2SHORT(newangles[1] - self->movedir[1]); ent->client->ps.delta_angles[2] += ANGLE2SHORT(newangles[2] - self->movedir[2]); } VectorCopy(newangles, self->movedir); } /* =============== G_StartKamikaze =============== */ void G_StartKamikaze( gentity_t *ent ) { gentity_t *explosion; gentity_t *te; vec3_t snapped; // start up the explosion logic explosion = G_Spawn(); explosion->s.eType = ET_EVENTS + EV_KAMIKAZE; explosion->eventTime = level.time; if ( ent->client ) { VectorCopy( ent->s.pos.trBase, snapped ); } else { VectorCopy( ent->activator->s.pos.trBase, snapped ); } SnapVector( snapped ); // save network bandwidth G_SetOrigin( explosion, snapped ); explosion->classname = "kamikaze"; explosion->s.pos.trType = TR_STATIONARY; explosion->kamikazeTime = level.time; explosion->think = KamikazeDamage; explosion->nextthink = level.time + 100; explosion->count = 0; VectorClear(explosion->movedir); trap_LinkEntity( explosion ); if (ent->client) { // explosion->activator = ent; // ent->s.eFlags &= ~EF_KAMIKAZE; // nuke the guy that used it G_Damage( ent, ent, ent, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_KAMIKAZE ); } else { if ( !strcmp(ent->activator->classname, "bodyque") ) { explosion->activator = &g_entities[ent->activator->r.ownerNum]; } else { explosion->activator = ent->activator; } } // play global sound at all clients te = G_TempEntity(snapped, EV_GLOBAL_TEAM_SOUND ); te->r.svFlags |= SVF_BROADCAST; te->s.eventParm = GTS_KAMIKAZE; } #endif