jediacademy/codemp/game/g_weapon.c
2013-04-04 18:21:13 -05:00

5027 lines
142 KiB
C

// Copyright (C) 1999-2000 Id Software, Inc.
//
// g_weapon.c
// perform the server side effects of a weapon firing
#include "g_local.h"
#include "be_aas.h"
#include "bg_saga.h"
#include "../ghoul2/G2.h"
#include "q_shared.h"
static float s_quadFactor;
static vec3_t forward, vright, up;
static vec3_t muzzle;
// Bryar Pistol
//--------
#define BRYAR_PISTOL_VEL 1600
#define BRYAR_PISTOL_DAMAGE 10
#define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove
#define BRYAR_ALT_SIZE 1.0f
// E11 Blaster
//---------
#define BLASTER_SPREAD 1.6f//1.2f
#define BLASTER_VELOCITY 2300
#define BLASTER_DAMAGE 20
// Tenloss Disruptor
//----------
#define DISRUPTOR_MAIN_DAMAGE 30 //40
#define DISRUPTOR_MAIN_DAMAGE_SIEGE 50
#define DISRUPTOR_NPC_MAIN_DAMAGE_CUT 0.25f
#define DISRUPTOR_ALT_DAMAGE 100 //125
#define DISRUPTOR_NPC_ALT_DAMAGE_CUT 0.2f
#define DISRUPTOR_ALT_TRACES 3 // can go through a max of 3 damageable(sp?) entities
#define DISRUPTOR_CHARGE_UNIT 50.0f // distruptor charging gives us one more unit every 50ms--if you change this, you'll have to do the same in bg_pmove
// Wookiee Bowcaster
//----------
#define BOWCASTER_DAMAGE 50
#define BOWCASTER_VELOCITY 1300
#define BOWCASTER_SPLASH_DAMAGE 0
#define BOWCASTER_SPLASH_RADIUS 0
#define BOWCASTER_SIZE 2
#define BOWCASTER_ALT_SPREAD 5.0f
#define BOWCASTER_VEL_RANGE 0.3f
#define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove
// Heavy Repeater
//----------
#define REPEATER_SPREAD 1.4f
#define REPEATER_DAMAGE 14
#define REPEATER_VELOCITY 1600
#define REPEATER_ALT_SIZE 3 // half of bbox size
#define REPEATER_ALT_DAMAGE 60
#define REPEATER_ALT_SPLASH_DAMAGE 60
#define REPEATER_ALT_SPLASH_RADIUS 128
#define REPEATER_ALT_SPLASH_RAD_SIEGE 80
#define REPEATER_ALT_VELOCITY 1100
// DEMP2
//----------
#define DEMP2_DAMAGE 35
#define DEMP2_VELOCITY 1800
#define DEMP2_SIZE 2 // half of bbox size
#define DEMP2_ALT_DAMAGE 8 //12 // does 12, 36, 84 at each of the 3 charge levels.
#define DEMP2_CHARGE_UNIT 700.0f // demp2 charging gives us one more unit every 700ms--if you change this, you'll have to do the same in bg_weapons
#define DEMP2_ALT_RANGE 4096
#define DEMP2_ALT_SPLASHRADIUS 256
// Golan Arms Flechette
//---------
#define FLECHETTE_SHOTS 5
#define FLECHETTE_SPREAD 4.0f
#define FLECHETTE_DAMAGE 12//15
#define FLECHETTE_VEL 3500
#define FLECHETTE_SIZE 1
#define FLECHETTE_MINE_RADIUS_CHECK 256
#define FLECHETTE_ALT_DAMAGE 60
#define FLECHETTE_ALT_SPLASH_DAM 60
#define FLECHETTE_ALT_SPLASH_RAD 128
// Personal Rocket Launcher
//---------
#define ROCKET_VELOCITY 900
#define ROCKET_DAMAGE 100
#define ROCKET_SPLASH_DAMAGE 100
#define ROCKET_SPLASH_RADIUS 160
#define ROCKET_SIZE 3
#define ROCKET_ALT_THINK_TIME 100
// Concussion Rifle
//---------
//primary
//man, this thing is too absurdly powerful. having to
//slash the values way down from sp.
#define CONC_VELOCITY 3000
#define CONC_DAMAGE 75 //150
#define CONC_NPC_DAMAGE_EASY 40
#define CONC_NPC_DAMAGE_NORMAL 80
#define CONC_NPC_DAMAGE_HARD 100
#define CONC_SPLASH_DAMAGE 40 //50
#define CONC_SPLASH_RADIUS 200 //300
//alt
#define CONC_ALT_DAMAGE 25 //100
#define CONC_ALT_NPC_DAMAGE_EASY 20
#define CONC_ALT_NPC_DAMAGE_MEDIUM 35
#define CONC_ALT_NPC_DAMAGE_HARD 50
// Stun Baton
//--------------
#define STUN_BATON_DAMAGE 20
#define STUN_BATON_ALT_DAMAGE 20
#define STUN_BATON_RANGE 8
// Melee
//--------------
#define MELEE_SWING1_DAMAGE 10
#define MELEE_SWING2_DAMAGE 12
#define MELEE_RANGE 8
// ATST Main Gun
//--------------
#define ATST_MAIN_VEL 4000 //
#define ATST_MAIN_DAMAGE 25 //
#define ATST_MAIN_SIZE 3 // make it easier to hit things
// ATST Side Gun
//---------------
#define ATST_SIDE_MAIN_DAMAGE 75
#define ATST_SIDE_MAIN_VELOCITY 1300
#define ATST_SIDE_MAIN_NPC_DAMAGE_EASY 30
#define ATST_SIDE_MAIN_NPC_DAMAGE_NORMAL 40
#define ATST_SIDE_MAIN_NPC_DAMAGE_HARD 50
#define ATST_SIDE_MAIN_SIZE 4
#define ATST_SIDE_MAIN_SPLASH_DAMAGE 10 // yeah, pretty small, either zero out or make it worth having?
#define ATST_SIDE_MAIN_SPLASH_RADIUS 16 // yeah, pretty small, either zero out or make it worth having?
#define ATST_SIDE_ALT_VELOCITY 1100
#define ATST_SIDE_ALT_NPC_VELOCITY 600
#define ATST_SIDE_ALT_DAMAGE 130
#define ATST_SIDE_ROCKET_NPC_DAMAGE_EASY 30
#define ATST_SIDE_ROCKET_NPC_DAMAGE_NORMAL 50
#define ATST_SIDE_ROCKET_NPC_DAMAGE_HARD 90
#define ATST_SIDE_ALT_SPLASH_DAMAGE 130
#define ATST_SIDE_ALT_SPLASH_RADIUS 200
#define ATST_SIDE_ALT_ROCKET_SIZE 5
#define ATST_SIDE_ALT_ROCKET_SPLASH_SCALE 0.5f // scales splash for NPC's
extern qboolean G_BoxInBounds( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t boundsMins, vec3_t boundsMaxs );
extern qboolean G_HeavyMelee( gentity_t *attacker );
extern void Jedi_Decloak( gentity_t *self );
static void WP_FireEmplaced( gentity_t *ent, qboolean altFire );
void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal );
void touch_NULL( gentity_t *ent, gentity_t *other, trace_t *trace )
{
}
void laserTrapExplode( gentity_t *self );
void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod);
extern vmCvar_t g_vehAutoAimLead;
//We should really organize weapon data into tables or parse from the ext data so we have accurate info for this,
float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire )
{
return 500;
}
//-----------------------------------------------------------------------------
void W_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs )
//-----------------------------------------------------------------------------
{
//make sure our start point isn't on the other side of a wall
trace_t tr;
vec3_t entMins;
vec3_t entMaxs;
vec3_t eyePoint;
VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins );
VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs );
if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) )
{
return;
}
if ( !ent->client )
{
return;
}
VectorCopy( ent->s.pos.trBase, eyePoint);
eyePoint[2] += ent->client->ps.viewheight;
trap_Trace( &tr, eyePoint, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP );
if ( tr.startsolid || tr.allsolid )
{
return;
}
if ( tr.fraction < 1.0f )
{
VectorCopy( tr.endpos, start );
}
}
/*
----------------------------------------------
PLAYER WEAPONS
----------------------------------------------
*/
/*
======================================================================
BRYAR PISTOL
======================================================================
*/
//----------------------------------------------
static void WP_FireBryarPistol( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
int damage = BRYAR_PISTOL_DAMAGE;
int count;
gentity_t *missile = CreateMissile( muzzle, forward, BRYAR_PISTOL_VEL, 10000, ent, altFire );
missile->classname = "bryar_proj";
missile->s.weapon = WP_BRYAR_PISTOL;
if ( altFire )
{
float boxSize = 0;
count = ( level.time - ent->client->ps.weaponChargeTime ) / BRYAR_CHARGE_UNIT;
if ( count < 1 )
{
count = 1;
}
else if ( count > 5 )
{
count = 5;
}
if (count > 1)
{
damage *= (count*1.7);
}
else
{
damage *= (count*1.5);
}
missile->s.generic1 = count; // The missile will then render according to the charge level.
boxSize = BRYAR_ALT_SIZE*(count*0.5);
VectorSet( missile->r.maxs, boxSize, boxSize, boxSize );
VectorSet( missile->r.mins, -boxSize, -boxSize, -boxSize );
}
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
if (altFire)
{
missile->methodOfDeath = MOD_BRYAR_PISTOL_ALT;
}
else
{
missile->methodOfDeath = MOD_BRYAR_PISTOL;
}
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
// we don't want it to bounce forever
missile->bounceCount = 8;
}
/*
======================================================================
GENERIC
======================================================================
*/
//---------------------------------------------------------
void WP_FireTurretMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod, gentity_t *ignore )
//---------------------------------------------------------
{
gentity_t *missile;
missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
missile->classname = "generic_proj";
missile->s.weapon = WP_TURRET;
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = mod;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
if (ignore)
{
missile->passThroughNum = ignore->s.number+1;
}
// we don't want it to bounce forever
missile->bounceCount = 8;
}
//Currently only the seeker drone uses this, but it might be useful for other things as well.
//---------------------------------------------------------
void WP_FireGenericBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, int damage, int velocity, int mod )
//---------------------------------------------------------
{
gentity_t *missile;
missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
missile->classname = "generic_proj";
missile->s.weapon = WP_BRYAR_PISTOL;
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = mod;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
// we don't want it to bounce forever
missile->bounceCount = 8;
}
/*
======================================================================
BLASTER
======================================================================
*/
//---------------------------------------------------------
void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire )
//---------------------------------------------------------
{
int velocity = BLASTER_VELOCITY;
int damage = BLASTER_DAMAGE;
gentity_t *missile;
if (ent->s.eType == ET_NPC)
{ //animent
damage = 10;
}
missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
missile->classname = "blaster_proj";
missile->s.weapon = WP_BLASTER;
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_BLASTER;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
// we don't want it to bounce forever
missile->bounceCount = 8;
}
//---------------------------------------------------------
void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir )
//---------------------------------------------------------
{
int velocity = ent->mass; //FIXME: externalize
gentity_t *missile;
missile = CreateMissile( start, dir, velocity, 10000, ent, qfalse );
//use a custom shot effect
missile->s.otherEntityNum2 = ent->genericValue14;
//use a custom impact effect
missile->s.emplacedOwner = ent->genericValue15;
missile->classname = "turbo_proj";
missile->s.weapon = WP_TURRET;
missile->damage = ent->damage; //FIXME: externalize
missile->splashDamage = ent->splashDamage; //FIXME: externalize
missile->splashRadius = ent->splashRadius; //FIXME: externalize
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_TARGET_LASER;//MOD_TURBLAST; //count as a heavy weap
missile->splashMethodOfDeath = MOD_TARGET_LASER;//MOD_TURBLAST;// ?SPLASH;
missile->clipmask = MASK_SHOT;
// we don't want it to bounce forever
missile->bounceCount = 8;
//set veh as cgame side owner for purpose of fx overrides
missile->s.owner = ent->s.number;
//don't let them last forever
missile->think = G_FreeEntity;
missile->nextthink = level.time + 5000;//at 20000 speed, that should be more than enough
}
//---------------------------------------------------------
void WP_FireEmplacedMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire, gentity_t *ignore )
//---------------------------------------------------------
{
int velocity = BLASTER_VELOCITY;
int damage = BLASTER_DAMAGE;
gentity_t *missile;
missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
missile->classname = "emplaced_gun_proj";
missile->s.weapon = WP_TURRET;//WP_EMPLACED_GUN;
missile->activator = ignore;
missile->damage = damage;
missile->dflags = (DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS);
missile->methodOfDeath = MOD_VEHICLE;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
if (ignore)
{
missile->passThroughNum = ignore->s.number+1;
}
// we don't want it to bounce forever
missile->bounceCount = 8;
}
//---------------------------------------------------------
static void WP_FireBlaster( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
vec3_t dir, angs;
vectoangles( forward, angs );
if ( altFire )
{
// add some slop to the alt-fire direction
angs[PITCH] += crandom() * BLASTER_SPREAD;
angs[YAW] += crandom() * BLASTER_SPREAD;
}
AngleVectors( angs, dir, NULL, NULL );
// FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot!
WP_FireBlasterMissile( ent, muzzle, dir, altFire );
}
int G_GetHitLocation(gentity_t *target, vec3_t ppoint);
/*
======================================================================
DISRUPTOR
======================================================================
*/
//---------------------------------------------------------
static void WP_DisruptorMainFire( gentity_t *ent )
//---------------------------------------------------------
{
int damage = DISRUPTOR_MAIN_DAMAGE;
qboolean render_impact = qtrue;
vec3_t start, end;
trace_t tr;
gentity_t *traceEnt, *tent;
float shotRange = 8192;
int ignore, traces;
if ( g_gametype.integer == GT_SIEGE )
{
damage = DISRUPTOR_MAIN_DAMAGE_SIEGE;
}
memset(&tr, 0, sizeof(tr)); //to shut the compiler up
VectorCopy( ent->client->ps.origin, start );
start[2] += ent->client->ps.viewheight;//By eyes
VectorMA( start, shotRange, forward, end );
ignore = ent->s.number;
traces = 0;
while ( traces < 10 )
{//need to loop this in case we hit a Jedi who dodges the shot
if (d_projectileGhoul2Collision.integer)
{
trap_G2Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
}
else
{
trap_Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT );
}
traceEnt = &g_entities[tr.entityNum];
if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
{ //g2 collision checks -rww
if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
{ //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
traceEnt->client->g2LastSurfaceTime = level.time;
}
if (traceEnt->ghoul2)
{
tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
}
}
if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress &&
traceEnt->client->ps.duelIndex != ent->s.number)
{
VectorCopy( tr.endpos, start );
ignore = tr.entityNum;
traces++;
continue;
}
if ( Jedi_DodgeEvasion( traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos) ) )
{//act like we didn't even hit him
VectorCopy( tr.endpos, start );
ignore = tr.entityNum;
traces++;
continue;
}
else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
{
if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR, qtrue, 0))
{ //broadcast and stop the shot because it was blocked
gentity_t *te = NULL;
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
VectorCopy( muzzle, tent->s.origin2 );
tent->s.eventParm = ent->s.number;
te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
{
te->s.angles[1] = 1;
}
te->s.eventParm = 0;
te->s.weapon = 0;//saberNum
te->s.legsAnim = 0;//bladeNum
return;
}
}
else if ( (traceEnt->flags&FL_SHIELDED) )
{//stopped cold
return;
}
//a Jedi is not dodging this shot
break;
}
if ( tr.surfaceFlags & SURF_NOIMPACT )
{
render_impact = qfalse;
}
// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
VectorCopy( muzzle, tent->s.origin2 );
tent->s.eventParm = ent->s.number;
traceEnt = &g_entities[tr.entityNum];
if ( render_impact )
{
if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
{
if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
{
ent->client->accuracy_hits++;
}
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NORMAL, MOD_DISRUPTOR );
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
tent->s.eventParm = DirToByte( tr.plane.normal );
if (traceEnt->client)
{
tent->s.weapon = 1;
}
}
else
{
// Hmmm, maybe don't make any marks on things that could break
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS );
tent->s.eventParm = DirToByte( tr.plane.normal );
tent->s.weapon = 1;
}
}
}
qboolean G_CanDisruptify(gentity_t *ent)
{
if (!ent || !ent->inuse || !ent->client || ent->s.eType != ET_NPC ||
ent->s.NPC_class != CLASS_VEHICLE || !ent->m_pVehicle)
{ //not vehicle
return qtrue;
}
if (ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
{ //animal is only type that can be disintigeiteigerated
return qtrue;
}
//don't do it to any other veh
return qfalse;
}
//---------------------------------------------------------
void WP_DisruptorAltFire( gentity_t *ent )
//---------------------------------------------------------
{
int damage = 0, skip;
qboolean render_impact = qtrue;
vec3_t start, end;
vec3_t muzzle2;
trace_t tr;
gentity_t *traceEnt, *tent;
float shotRange = 8192.0f;
int i;
int count, maxCount = 60;
int traces = DISRUPTOR_ALT_TRACES;
qboolean fullCharge = qfalse;
damage = DISRUPTOR_ALT_DAMAGE-30;
VectorCopy( muzzle, muzzle2 ); // making a backup copy
if (ent->client)
{
VectorCopy( ent->client->ps.origin, start );
start[2] += ent->client->ps.viewheight;//By eyes
count = ( level.time - ent->client->ps.weaponChargeTime ) / DISRUPTOR_CHARGE_UNIT;
if ( g_gametype.integer == GT_SIEGE )
{//maybe a full alt-charge should be a *bit* more dangerous in Siege mode?
//maxCount = ceil((200.0f-(float)damage)/2.0f);//cap at 200 damage total
maxCount = 200;//the previous line ALWAYS evaluated to 135 - was that on purpose?
}
}
else
{
VectorCopy( ent->r.currentOrigin, start );
start[2] += 24;
count = ( 100 ) / DISRUPTOR_CHARGE_UNIT;
}
count *= 2;
if ( count < 1 )
{
count = 1;
}
else if ( count >= maxCount )
{
count = maxCount;
fullCharge = qtrue;
}
// more powerful charges go through more things
if ( count < 10 )
{
traces = 1;
}
else if ( count < 20 )
{
traces = 2;
}
damage += count;
skip = ent->s.number;
for (i = 0; i < traces; i++ )
{
VectorMA( start, shotRange, forward, end );
if (d_projectileGhoul2Collision.integer)
{
trap_G2Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
}
else
{
trap_Trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT );
}
traceEnt = &g_entities[tr.entityNum];
if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
{ //g2 collision checks -rww
if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
{ //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
traceEnt->client->g2LastSurfaceTime = level.time;
}
if (traceEnt->ghoul2)
{
tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
}
}
if ( tr.surfaceFlags & SURF_NOIMPACT )
{
render_impact = qfalse;
}
if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress &&
traceEnt->client->ps.duelIndex != ent->s.number)
{
skip = tr.entityNum;
VectorCopy(tr.endpos, start);
continue;
}
if (Jedi_DodgeEvasion(traceEnt, ent, &tr, G_GetHitLocation(traceEnt, tr.endpos)))
{
skip = tr.entityNum;
VectorCopy(tr.endpos, start);
continue;
}
else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
{
if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR_SNIPER, qtrue, 0))
{ //broadcast and stop the shot because it was blocked
gentity_t *te = NULL;
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT );
VectorCopy( muzzle, tent->s.origin2 );
tent->s.shouldtarget = fullCharge;
tent->s.eventParm = ent->s.number;
te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
{
te->s.angles[1] = 1;
}
te->s.eventParm = 0;
te->s.weapon = 0;//saberNum
te->s.legsAnim = 0;//bladeNum
return;
}
}
// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT );
VectorCopy( muzzle, tent->s.origin2 );
tent->s.shouldtarget = fullCharge;
tent->s.eventParm = ent->s.number;
// If the beam hits a skybox, etc. it would look foolish to add impact effects
if ( render_impact )
{
if ( traceEnt->takedamage && traceEnt->client )
{
tent->s.otherEntityNum = traceEnt->s.number;
// Create a simple impact type mark
tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
tent->s.eventParm = DirToByte(tr.plane.normal);
tent->s.eFlags |= EF_ALT_FIRING;
if ( LogAccuracyHit( traceEnt, ent ))
{
if (ent->client)
{
ent->client->accuracy_hits++;
}
}
}
else
{
if ( traceEnt->r.svFlags & SVF_GLASS_BRUSH
|| traceEnt->takedamage
|| traceEnt->s.eType == ET_MOVER )
{
if ( traceEnt->takedamage )
{
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage,
DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER );
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
tent->s.eventParm = DirToByte( tr.plane.normal );
}
}
else
{
// Hmmm, maybe don't make any marks on things that could break
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS );
tent->s.eventParm = DirToByte( tr.plane.normal );
}
break; // and don't try any more traces
}
if ( (traceEnt->flags&FL_SHIELDED) )
{//stops us cold
break;
}
if ( traceEnt->takedamage )
{
vec3_t preAng;
int preHealth = traceEnt->health;
int preLegs = 0;
int preTorso = 0;
if (traceEnt->client)
{
preLegs = traceEnt->client->ps.legsAnim;
preTorso = traceEnt->client->ps.torsoAnim;
VectorCopy(traceEnt->client->ps.viewangles, preAng);
}
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_DISRUPTOR_SNIPER );
if (traceEnt->client && preHealth > 0 && traceEnt->health <= 0 && fullCharge &&
G_CanDisruptify(traceEnt))
{ //was killed by a fully charged sniper shot, so disintegrate
VectorCopy(preAng, traceEnt->client->ps.viewangles);
traceEnt->client->ps.eFlags |= EF_DISINTEGRATION;
VectorCopy(tr.endpos, traceEnt->client->ps.lastHitLoc);
traceEnt->client->ps.legsAnim = preLegs;
traceEnt->client->ps.torsoAnim = preTorso;
traceEnt->r.contents = 0;
VectorClear(traceEnt->client->ps.velocity);
}
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
tent->s.eventParm = DirToByte( tr.plane.normal );
if (traceEnt->client)
{
tent->s.weapon = 1;
}
}
}
else // not rendering impact, must be a skybox or other similar thing?
{
break; // don't try anymore traces
}
// Get ready for an attempt to trace through another person
VectorCopy( tr.endpos, muzzle );
VectorCopy( tr.endpos, start );
skip = tr.entityNum;
}
}
//---------------------------------------------------------
static void WP_FireDisruptor( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
if (!ent || !ent->client || ent->client->ps.zoomMode != 1)
{ //do not ever let it do the alt fire when not zoomed
altFire = qfalse;
}
if (ent && ent->s.eType == ET_NPC && !ent->client)
{ //special case for animents
WP_DisruptorAltFire( ent );
return;
}
if ( altFire )
{
WP_DisruptorAltFire( ent );
}
else
{
WP_DisruptorMainFire( ent );
}
}
/*
======================================================================
BOWCASTER
======================================================================
*/
static void WP_BowcasterAltFire( gentity_t *ent )
{
int damage = BOWCASTER_DAMAGE;
gentity_t *missile = CreateMissile( muzzle, forward, BOWCASTER_VELOCITY, 10000, ent, qfalse);
missile->classname = "bowcaster_proj";
missile->s.weapon = WP_BOWCASTER;
VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
VectorScale( missile->r.maxs, -1, missile->r.mins );
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_BOWCASTER;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
missile->flags |= FL_BOUNCE;
missile->bounceCount = 3;
}
//---------------------------------------------------------
static void WP_BowcasterMainFire( gentity_t *ent )
//---------------------------------------------------------
{
int damage = BOWCASTER_DAMAGE, count;
float vel;
vec3_t angs, dir;
gentity_t *missile;
int i;
if (!ent->client)
{
count = 1;
}
else
{
count = ( level.time - ent->client->ps.weaponChargeTime ) / BOWCASTER_CHARGE_UNIT;
}
if ( count < 1 )
{
count = 1;
}
else if ( count > 5 )
{
count = 5;
}
if ( !(count & 1 ))
{
// if we aren't odd, knock us down a level
count--;
}
//scale the damage down based on how many are about to be fired
if (count <= 1)
{
damage = 50;
}
else if (count == 2)
{
damage = 45;
}
else if (count == 3)
{
damage = 40;
}
else if (count == 4)
{
damage = 35;
}
else
{
damage = 30;
}
for (i = 0; i < count; i++ )
{
// create a range of different velocities
vel = BOWCASTER_VELOCITY * ( crandom() * BOWCASTER_VEL_RANGE + 1.0f );
vectoangles( forward, angs );
// add some slop to the alt-fire direction
angs[PITCH] += crandom() * BOWCASTER_ALT_SPREAD * 0.2f;
angs[YAW] += ((i+0.5f) * BOWCASTER_ALT_SPREAD - count * 0.5f * BOWCASTER_ALT_SPREAD );
AngleVectors( angs, dir, NULL, NULL );
missile = CreateMissile( muzzle, dir, vel, 10000, ent, qtrue );
missile->classname = "bowcaster_alt_proj";
missile->s.weapon = WP_BOWCASTER;
VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
VectorScale( missile->r.maxs, -1, missile->r.mins );
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_BOWCASTER;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
// we don't want it to bounce
missile->bounceCount = 0;
}
}
//---------------------------------------------------------
static void WP_FireBowcaster( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
if ( altFire )
{
WP_BowcasterAltFire( ent );
}
else
{
WP_BowcasterMainFire( ent );
}
}
/*
======================================================================
REPEATER
======================================================================
*/
//---------------------------------------------------------
static void WP_RepeaterMainFire( gentity_t *ent, vec3_t dir )
//---------------------------------------------------------
{
int damage = REPEATER_DAMAGE;
gentity_t *missile = CreateMissile( muzzle, dir, REPEATER_VELOCITY, 10000, ent, qfalse );
missile->classname = "repeater_proj";
missile->s.weapon = WP_REPEATER;
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_REPEATER;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
// we don't want it to bounce forever
missile->bounceCount = 8;
}
//---------------------------------------------------------
static void WP_RepeaterAltFire( gentity_t *ent )
//---------------------------------------------------------
{
int damage = REPEATER_ALT_DAMAGE;
gentity_t *missile = CreateMissile( muzzle, forward, REPEATER_ALT_VELOCITY, 10000, ent, qtrue );
missile->classname = "repeater_alt_proj";
missile->s.weapon = WP_REPEATER;
VectorSet( missile->r.maxs, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE );
VectorScale( missile->r.maxs, -1, missile->r.mins );
missile->s.pos.trType = TR_GRAVITY;
missile->s.pos.trDelta[2] += 40.0f; //give a slight boost in the upward direction
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_REPEATER_ALT;
missile->splashMethodOfDeath = MOD_REPEATER_ALT_SPLASH;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
missile->splashDamage = REPEATER_ALT_SPLASH_DAMAGE;
if ( g_gametype.integer == GT_SIEGE ) // we've been having problems with this being too hyper-potent because of it's radius
{
missile->splashRadius = REPEATER_ALT_SPLASH_RAD_SIEGE;
}
else
{
missile->splashRadius = REPEATER_ALT_SPLASH_RADIUS;
}
// we don't want it to bounce forever
missile->bounceCount = 8;
}
//---------------------------------------------------------
static void WP_FireRepeater( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
vec3_t dir, angs;
vectoangles( forward, angs );
if ( altFire )
{
WP_RepeaterAltFire( ent );
}
else
{
// add some slop to the alt-fire direction
angs[PITCH] += crandom() * REPEATER_SPREAD;
angs[YAW] += crandom() * REPEATER_SPREAD;
AngleVectors( angs, dir, NULL, NULL );
WP_RepeaterMainFire( ent, dir );
}
}
/*
======================================================================
DEMP2
======================================================================
*/
static void WP_DEMP2_MainFire( gentity_t *ent )
{
int damage = DEMP2_DAMAGE;
gentity_t *missile = CreateMissile( muzzle, forward, DEMP2_VELOCITY, 10000, ent, qfalse);
missile->classname = "demp2_proj";
missile->s.weapon = WP_DEMP2;
VectorSet( missile->r.maxs, DEMP2_SIZE, DEMP2_SIZE, DEMP2_SIZE );
VectorScale( missile->r.maxs, -1, missile->r.mins );
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_DEMP2;
missile->clipmask = MASK_SHOT;
// we don't want it to ever bounce
missile->bounceCount = 0;
}
static gentity_t *ent_list[MAX_GENTITIES];
void DEMP2_AltRadiusDamage( gentity_t *ent )
{
float frac = ( level.time - ent->genericValue5 ) / 800.0f; // / 1600.0f; // synchronize with demp2 effect
float dist, radius, fact;
gentity_t *gent;
int iEntityList[MAX_GENTITIES];
gentity_t *entityList[MAX_GENTITIES];
gentity_t *myOwner = NULL;
int numListedEntities, i, e;
vec3_t mins, maxs;
vec3_t v, dir;
if (ent->r.ownerNum >= 0 &&
ent->r.ownerNum < /*MAX_CLIENTS ... let npc's/shooters use it*/MAX_GENTITIES)
{
myOwner = &g_entities[ent->r.ownerNum];
}
if (!myOwner || !myOwner->inuse || !myOwner->client)
{
ent->think = G_FreeEntity;
ent->nextthink = level.time;
return;
}
frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end
radius = frac * 200.0f; // 200 is max radius...the model is aprox. 100 units tall...the fx draw code mults. this by 2.
fact = ent->count*0.6;
if (fact < 1)
{
fact = 1;
}
radius *= fact;
for ( i = 0 ; i < 3 ; i++ )
{
mins[i] = ent->r.currentOrigin[i] - radius;
maxs[i] = ent->r.currentOrigin[i] + radius;
}
numListedEntities = trap_EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES );
i = 0;
while (i < numListedEntities)
{
entityList[i] = &g_entities[iEntityList[i]];
i++;
}
for ( e = 0 ; e < numListedEntities ; e++ )
{
gent = entityList[ e ];
if ( !gent || !gent->takedamage || !gent->r.contents )
{
continue;
}
// find the distance from the edge of the bounding box
for ( i = 0 ; i < 3 ; i++ )
{
if ( ent->r.currentOrigin[i] < gent->r.absmin[i] )
{
v[i] = gent->r.absmin[i] - ent->r.currentOrigin[i];
}
else if ( ent->r.currentOrigin[i] > gent->r.absmax[i] )
{
v[i] = ent->r.currentOrigin[i] - gent->r.absmax[i];
}
else
{
v[i] = 0;
}
}
// shape is an ellipsoid, so cut vertical distance in half`
v[2] *= 0.5f;
dist = VectorLength( v );
if ( dist >= radius )
{
// shockwave hasn't hit them yet
continue;
}
if (dist+(16*ent->count) < ent->genericValue6)
{
// shockwave has already hit this thing...
continue;
}
VectorCopy( gent->r.currentOrigin, v );
VectorSubtract( v, ent->r.currentOrigin, dir);
// push the center of mass higher than the origin so players get knocked into the air more
dir[2] += 12;
if (gent != myOwner)
{
G_Damage( gent, myOwner, myOwner, dir, ent->r.currentOrigin, ent->damage, DAMAGE_DEATH_KNOCKBACK, ent->splashMethodOfDeath );
if ( gent->takedamage
&& gent->client )
{
if ( gent->client->ps.electrifyTime < level.time )
{//electrocution effect
if (gent->s.eType == ET_NPC && gent->s.NPC_class == CLASS_VEHICLE &&
gent->m_pVehicle && (gent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || gent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER))
{ //do some extra stuff to speeders/walkers
gent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
}
else if ( gent->s.NPC_class != CLASS_VEHICLE
|| (gent->m_pVehicle && gent->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) )
{//don't do this to fighters
gent->client->ps.electrifyTime = level.time + Q_irand( 300, 800 );
}
}
if ( gent->client->ps.powerups[PW_CLOAKED] )
{//disable cloak temporarily
Jedi_Decloak( gent );
gent->client->cloakToggleTime = level.time + Q_irand( 3000, 10000 );
}
}
}
}
// store the last fraction so that next time around we can test against those things that fall between that last point and where the current shockwave edge is
ent->genericValue6 = radius;
if ( frac < 1.0f )
{
// shock is still happening so continue letting it expand
ent->nextthink = level.time + 50;
}
else
{ //don't just leave the entity around
ent->think = G_FreeEntity;
ent->nextthink = level.time;
}
}
//---------------------------------------------------------
void DEMP2_AltDetonate( gentity_t *ent )
//---------------------------------------------------------
{
gentity_t *efEnt;
G_SetOrigin( ent, ent->r.currentOrigin );
if (!ent->pos1[0] && !ent->pos1[1] && !ent->pos1[2])
{ //don't play effect with a 0'd out directional vector
ent->pos1[1] = 1;
}
//Let's just save ourself some bandwidth and play both the effect and sphere spawn in 1 event
efEnt = G_PlayEffect( EFFECT_EXPLOSION_DEMP2ALT, ent->r.currentOrigin, ent->pos1 );
if (efEnt)
{
efEnt->s.weapon = ent->count*2;
}
ent->genericValue5 = level.time;
ent->genericValue6 = 0;
ent->nextthink = level.time + 50;
ent->think = DEMP2_AltRadiusDamage;
ent->s.eType = ET_GENERAL; // make us a missile no longer
}
//---------------------------------------------------------
static void WP_DEMP2_AltFire( gentity_t *ent )
//---------------------------------------------------------
{
int damage = DEMP2_ALT_DAMAGE;
int count, origcount;
float fact;
vec3_t start, end;
trace_t tr;
gentity_t *missile;
VectorCopy( muzzle, start );
VectorMA( start, DEMP2_ALT_RANGE, forward, end );
count = ( level.time - ent->client->ps.weaponChargeTime ) / DEMP2_CHARGE_UNIT;
origcount = count;
if ( count < 1 )
{
count = 1;
}
else if ( count > 3 )
{
count = 3;
}
fact = count*0.8;
if (fact < 1)
{
fact = 1;
}
damage *= fact;
if (!origcount)
{ //this was just a tap-fire
damage = 1;
}
trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT);
missile = G_Spawn();
G_SetOrigin(missile, tr.endpos);
//In SP the impact actually travels as a missile based on the trace fraction, but we're
//just going to be instant. -rww
VectorCopy( tr.plane.normal, missile->pos1 );
missile->count = count;
missile->classname = "demp2_alt_proj";
missile->s.weapon = WP_DEMP2;
missile->think = DEMP2_AltDetonate;
missile->nextthink = level.time;
missile->splashDamage = missile->damage = damage;
missile->splashMethodOfDeath = missile->methodOfDeath = MOD_DEMP2;
missile->splashRadius = DEMP2_ALT_SPLASHRADIUS;
missile->r.ownerNum = ent->s.number;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
// we don't want it to ever bounce
missile->bounceCount = 0;
}
//---------------------------------------------------------
static void WP_FireDEMP2( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
if ( altFire )
{
WP_DEMP2_AltFire( ent );
}
else
{
WP_DEMP2_MainFire( ent );
}
}
/*
======================================================================
FLECHETTE
======================================================================
*/
//---------------------------------------------------------
static void WP_FlechetteMainFire( gentity_t *ent )
//---------------------------------------------------------
{
vec3_t fwd, angs;
gentity_t *missile;
int i;
for (i = 0; i < FLECHETTE_SHOTS; i++ )
{
vectoangles( forward, angs );
if (i != 0)
{ //do nothing on the first shot, it will hit the crosshairs
angs[PITCH] += crandom() * FLECHETTE_SPREAD;
angs[YAW] += crandom() * FLECHETTE_SPREAD;
}
AngleVectors( angs, fwd, NULL, NULL );
missile = CreateMissile( muzzle, fwd, FLECHETTE_VEL, 10000, ent, qfalse);
missile->classname = "flech_proj";
missile->s.weapon = WP_FLECHETTE;
VectorSet( missile->r.maxs, FLECHETTE_SIZE, FLECHETTE_SIZE, FLECHETTE_SIZE );
VectorScale( missile->r.maxs, -1, missile->r.mins );
missile->damage = FLECHETTE_DAMAGE;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->methodOfDeath = MOD_FLECHETTE;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
// we don't want it to bounce forever
missile->bounceCount = Q_irand(5,8);
missile->flags |= FL_BOUNCE_SHRAPNEL;
}
}
//---------------------------------------------------------
void prox_mine_think( gentity_t *ent )
//---------------------------------------------------------
{
int count, i;
qboolean blow = qfalse;
// if it isn't time to auto-explode, do a small proximity check
if ( ent->delay > level.time )
{
count = G_RadiusList( ent->r.currentOrigin, FLECHETTE_MINE_RADIUS_CHECK, ent, qtrue, ent_list );
for ( i = 0; i < count; i++ )
{
if ( ent_list[i]->client && ent_list[i]->health > 0 && ent->activator && ent_list[i]->s.number != ent->activator->s.number )
{
blow = qtrue;
break;
}
}
}
else
{
// well, we must die now
blow = qtrue;
}
if ( blow )
{
ent->think = laserTrapExplode;
ent->nextthink = level.time + 200;
}
else
{
// we probably don't need to do this thinking logic very often...maybe this is fast enough?
ent->nextthink = level.time + 500;
}
}
//-----------------------------------------------------------------------------
static void WP_TraceSetStart( gentity_t *ent, vec3_t start, vec3_t mins, vec3_t maxs )
//-----------------------------------------------------------------------------
{
//make sure our start point isn't on the other side of a wall
trace_t tr;
vec3_t entMins;
vec3_t entMaxs;
VectorAdd( ent->r.currentOrigin, ent->r.mins, entMins );
VectorAdd( ent->r.currentOrigin, ent->r.maxs, entMaxs );
if ( G_BoxInBounds( start, mins, maxs, entMins, entMaxs ) )
{
return;
}
if ( !ent->client )
{
return;
}
trap_Trace( &tr, ent->client->ps.origin, mins, maxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP );
if ( tr.startsolid || tr.allsolid )
{
return;
}
if ( tr.fraction < 1.0f )
{
VectorCopy( tr.endpos, start );
}
}
void WP_ExplosiveDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
{
laserTrapExplode(self);
}
//----------------------------------------------
void WP_flechette_alt_blow( gentity_t *ent )
//----------------------------------------------
{
ent->s.pos.trDelta[0] = 1;
ent->s.pos.trDelta[1] = 0;
ent->s.pos.trDelta[2] = 0;
laserTrapExplode(ent);
}
//------------------------------------------------------------------------------
static void WP_CreateFlechetteBouncyThing( vec3_t start, vec3_t fwd, gentity_t *self )
//------------------------------------------------------------------------------
{
gentity_t *missile = CreateMissile( start, fwd, 700 + random() * 700, 1500 + random() * 2000, self, qtrue );
missile->think = WP_flechette_alt_blow;
missile->activator = self;
missile->s.weapon = WP_FLECHETTE;
missile->classname = "flech_alt";
missile->mass = 4;
// How 'bout we give this thing a size...
VectorSet( missile->r.mins, -3.0f, -3.0f, -3.0f );
VectorSet( missile->r.maxs, 3.0f, 3.0f, 3.0f );
missile->clipmask = MASK_SHOT;
missile->touch = touch_NULL;
// normal ones bounce, alt ones explode on impact
missile->s.pos.trType = TR_GRAVITY;
missile->flags |= FL_BOUNCE_HALF;
missile->s.eFlags |= EF_ALT_FIRING;
missile->bounceCount = 50;
missile->damage = FLECHETTE_ALT_DAMAGE;
missile->dflags = 0;
missile->splashDamage = FLECHETTE_ALT_SPLASH_DAM;
missile->splashRadius = FLECHETTE_ALT_SPLASH_RAD;
missile->r.svFlags = SVF_USE_CURRENT_ORIGIN;
missile->methodOfDeath = MOD_FLECHETTE_ALT_SPLASH;
missile->splashMethodOfDeath = MOD_FLECHETTE_ALT_SPLASH;
VectorCopy( start, missile->pos2 );
}
//---------------------------------------------------------
static void WP_FlechetteAltFire( gentity_t *self )
//---------------------------------------------------------
{
vec3_t dir, fwd, start, angs;
int i;
vectoangles( forward, angs );
VectorCopy( muzzle, start );
WP_TraceSetStart( self, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
for ( i = 0; i < 2; i++ )
{
VectorCopy( angs, dir );
dir[PITCH] -= random() * 4 + 8; // make it fly upwards
dir[YAW] += crandom() * 2;
AngleVectors( dir, fwd, NULL, NULL );
WP_CreateFlechetteBouncyThing( start, fwd, self );
}
}
//---------------------------------------------------------
static void WP_FireFlechette( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
if ( altFire )
{
//WP_FlechetteProxMine( ent );
WP_FlechetteAltFire(ent);
}
else
{
WP_FlechetteMainFire( ent );
}
}
/*
======================================================================
ROCKET LAUNCHER
======================================================================
*/
//---------------------------------------------------------
void rocketThink( gentity_t *ent )
//---------------------------------------------------------
{
vec3_t newdir, targetdir,
up={0,0,1}, right;
vec3_t org;
float dot, dot2, dis;
int i;
float vel = (ent->spawnflags&1)?ent->speed:ROCKET_VELOCITY;
if ( ent->genericValue1 && ent->genericValue1 < level.time )
{//time's up, we're done, remove us
if ( ent->genericValue2 )
{//explode when die
RocketDie( ent, &g_entities[ent->r.ownerNum], &g_entities[ent->r.ownerNum], 0, MOD_UNKNOWN );
}
else
{//just remove when die
G_FreeEntity( ent );
}
return;
}
if ( !ent->enemy
|| !ent->enemy->client
|| ent->enemy->health <= 0
|| ent->enemy->client->ps.powerups[PW_CLOAKED] )
{//no enemy or enemy not a client or enemy dead or enemy cloaked
if ( !ent->genericValue1 )
{//doesn't have its own self-kill time
ent->nextthink = level.time + 10000;
ent->think = G_FreeEntity;
}
return;
}
if ( (ent->spawnflags&1) )
{//vehicle rocket
if ( ent->enemy->client && ent->enemy->client->NPC_class == CLASS_VEHICLE )
{//tracking another vehicle
if ( ent->enemy->client->ps.speed+4000 > vel )
{
vel = ent->enemy->client->ps.speed+4000;
}
}
}
if ( ent->enemy && ent->enemy->inuse )
{
float newDirMult = ent->angle?ent->angle*2.0f:1.0f;
float oldDirMult = ent->angle?(1.0f-ent->angle)*2.0f:1.0f;
VectorCopy( ent->enemy->r.currentOrigin, org );
org[2] += (ent->enemy->r.mins[2] + ent->enemy->r.maxs[2]) * 0.5f;
VectorSubtract( org, ent->r.currentOrigin, targetdir );
VectorNormalize( targetdir );
// Now the rocket can't do a 180 in space, so we'll limit the turn to about 45 degrees.
dot = DotProduct( targetdir, ent->movedir );
if ( (ent->spawnflags&1) )
{//vehicle rocket
if ( ent->radius > -1.0f )
{//can lose the lock if DotProduct drops below this number
if ( dot < ent->radius )
{//lost the lock!!!
//HMM... maybe can re-lock on if they come in front again?
/*
//OR: should it stop trying to lock altogether?
if ( ent->genericValue1 )
{//have a timelimit, set next think to that
ent->nextthink = ent->genericValue1;
if ( ent->genericValue2 )
{//explode when die
ent->think = G_ExplodeMissile;
}
else
{
ent->think = G_FreeEntity;
}
}
else
{
ent->think = NULL;
ent->nextthink = -1;
}
*/
return;
}
}
}
// a dot of 1.0 means right-on-target.
if ( dot < 0.0f )
{
// Go in the direction opposite, start a 180.
CrossProduct( ent->movedir, up, right );
dot2 = DotProduct( targetdir, right );
if ( dot2 > 0 )
{
// Turn 45 degrees right.
VectorMA( ent->movedir, 0.4f*newDirMult, right, newdir );
}
else
{
// Turn 45 degrees left.
VectorMA( ent->movedir, -0.4f*newDirMult, right, newdir );
}
// Yeah we've adjusted horizontally, but let's split the difference vertically, so we kinda try to move towards it.
newdir[2] = ( (targetdir[2]*newDirMult) + (ent->movedir[2]*oldDirMult) ) * 0.5;
// let's also slow down a lot
vel *= 0.5f;
}
else if ( dot < 0.70f )
{
// Still a bit off, so we turn a bit softer
VectorMA( ent->movedir, 0.5f*newDirMult, targetdir, newdir );
}
else
{
// getting close, so turn a bit harder
VectorMA( ent->movedir, 0.9f*newDirMult, targetdir, newdir );
}
// add crazy drunkenness
for (i = 0; i < 3; i++ )
{
newdir[i] += crandom() * ent->random * 0.25f;
}
// decay the randomness
ent->random *= 0.9f;
if ( ent->enemy->client
&& ent->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )
{//tracking a client who's on the ground, aim at the floor...?
// Try to crash into the ground if we get close enough to do splash damage
dis = Distance( ent->r.currentOrigin, org );
if ( dis < 128 )
{
// the closer we get, the more we push the rocket down, heh heh.
newdir[2] -= (1.0f - (dis / 128.0f)) * 0.6f;
}
}
VectorNormalize( newdir );
VectorScale( newdir, vel * 0.5f, ent->s.pos.trDelta );
VectorCopy( newdir, ent->movedir );
SnapVector( ent->s.pos.trDelta ); // save net bandwidth
VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
ent->s.pos.trTime = level.time;
}
ent->nextthink = level.time + ROCKET_ALT_THINK_TIME; // Nothing at all spectacular happened, continue.
return;
}
extern void G_ExplodeMissile( gentity_t *ent );
void RocketDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
{
self->die = 0;
self->r.contents = 0;
G_ExplodeMissile( self );
self->think = G_FreeEntity;
self->nextthink = level.time;
}
//---------------------------------------------------------
static void WP_FireRocket( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
int damage = ROCKET_DAMAGE;
int vel = ROCKET_VELOCITY;
int dif = 0;
float rTime;
gentity_t *missile;
if ( altFire )
{
vel *= 0.5f;
}
missile = CreateMissile( muzzle, forward, vel, 10000, ent, altFire );
if (ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE)
{
float lockTimeInterval = ((g_gametype.integer==GT_SIEGE)?2400.0f:1200.0f)/16.0f;
rTime = ent->client->ps.rocketLockTime;
if (rTime == -1)
{
rTime = ent->client->ps.rocketLastValidTime;
}
dif = ( level.time - rTime ) / lockTimeInterval;
if (dif < 0)
{
dif = 0;
}
//It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client
if ( dif >= 10 && rTime != -1 )
{
missile->enemy = &g_entities[ent->client->ps.rocketLockIndex];
if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
{ //if enemy became invalid, died, or is on the same team, then don't seek it
missile->angle = 0.5f;
missile->think = rocketThink;
missile->nextthink = level.time + ROCKET_ALT_THINK_TIME;
}
}
ent->client->ps.rocketLockIndex = ENTITYNUM_NONE;
ent->client->ps.rocketLockTime = 0;
ent->client->ps.rocketTargetTime = 0;
}
missile->classname = "rocket_proj";
missile->s.weapon = WP_ROCKET_LAUNCHER;
// Make it easier to hit things
VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
VectorScale( missile->r.maxs, -1, missile->r.mins );
missile->damage = damage;
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
if (altFire)
{
missile->methodOfDeath = MOD_ROCKET_HOMING;
missile->splashMethodOfDeath = MOD_ROCKET_HOMING_SPLASH;
}
else
{
missile->methodOfDeath = MOD_ROCKET;
missile->splashMethodOfDeath = MOD_ROCKET_SPLASH;
}
//===testing being able to shoot rockets out of the air==================================
missile->health = 10;
missile->takedamage = qtrue;
missile->r.contents = MASK_SHOT;
missile->die = RocketDie;
//===testing being able to shoot rockets out of the air==================================
missile->clipmask = MASK_SHOT;
missile->splashDamage = ROCKET_SPLASH_DAMAGE;
missile->splashRadius = ROCKET_SPLASH_RADIUS;
// we don't want it to ever bounce
missile->bounceCount = 0;
}
/*
======================================================================
THERMAL DETONATOR
======================================================================
*/
#define TD_DAMAGE 70 //only do 70 on a direct impact
#define TD_SPLASH_RAD 128
#define TD_SPLASH_DAM 90
#define TD_VELOCITY 900
#define TD_MIN_CHARGE 0.15f
#define TD_TIME 3000//6000
#define TD_ALT_TIME 3000
#define TD_ALT_DAMAGE 60//100
#define TD_ALT_SPLASH_RAD 128
#define TD_ALT_SPLASH_DAM 50//90
#define TD_ALT_VELOCITY 600
#define TD_ALT_MIN_CHARGE 0.15f
#define TD_ALT_TIME 3000
void thermalThinkStandard(gentity_t *ent);
//---------------------------------------------------------
void thermalDetonatorExplode( gentity_t *ent )
//---------------------------------------------------------
{
if ( !ent->count )
{
G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/thermal/warning.wav" ) );
ent->count = 1;
ent->genericValue5 = level.time + 500;
ent->think = thermalThinkStandard;
ent->nextthink = level.time;
ent->r.svFlags |= SVF_BROADCAST;//so everyone hears/sees the explosion?
}
else
{
vec3_t origin;
vec3_t dir={0,0,1};
BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
origin[2] += 8;
SnapVector( origin );
G_SetOrigin( ent, origin );
ent->s.eType = ET_GENERAL;
G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
ent->freeAfterEvent = qtrue;
if (G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius,
ent, ent, ent->splashMethodOfDeath))
{
g_entities[ent->r.ownerNum].client->accuracy_hits++;
}
trap_LinkEntity( ent );
}
}
void thermalThinkStandard(gentity_t *ent)
{
if (ent->genericValue5 < level.time)
{
ent->think = thermalDetonatorExplode;
ent->nextthink = level.time;
return;
}
G_RunObject(ent);
ent->nextthink = level.time;
}
//---------------------------------------------------------
gentity_t *WP_FireThermalDetonator( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
gentity_t *bolt;
vec3_t dir, start;
float chargeAmount = 1.0f; // default of full charge
VectorCopy( forward, dir );
VectorCopy( muzzle, start );
bolt = G_Spawn();
bolt->physicsObject = qtrue;
bolt->classname = "thermal_detonator";
bolt->think = thermalThinkStandard;
bolt->nextthink = level.time;
bolt->touch = touch_NULL;
// How 'bout we give this thing a size...
VectorSet( bolt->r.mins, -3.0f, -3.0f, -3.0f );
VectorSet( bolt->r.maxs, 3.0f, 3.0f, 3.0f );
bolt->clipmask = MASK_SHOT;
W_TraceSetStart( ent, start, bolt->r.mins, bolt->r.maxs );//make sure our start point isn't on the other side of a wall
if ( ent->client )
{
chargeAmount = level.time - ent->client->ps.weaponChargeTime;
}
// get charge amount
chargeAmount = chargeAmount / (float)TD_VELOCITY;
if ( chargeAmount > 1.0f )
{
chargeAmount = 1.0f;
}
else if ( chargeAmount < TD_MIN_CHARGE )
{
chargeAmount = TD_MIN_CHARGE;
}
// normal ones bounce, alt ones explode on impact
bolt->genericValue5 = level.time + TD_TIME; // How long 'til she blows
bolt->s.pos.trType = TR_GRAVITY;
bolt->parent = ent;
bolt->r.ownerNum = ent->s.number;
VectorScale( dir, TD_VELOCITY * chargeAmount, bolt->s.pos.trDelta );
if ( ent->health >= 0 )
{
bolt->s.pos.trDelta[2] += 120;
}
if ( !altFire )
{
bolt->flags |= FL_BOUNCE_HALF;
}
bolt->s.loopSound = G_SoundIndex( "sound/weapons/thermal/thermloop.wav" );
bolt->s.loopIsSoundset = qfalse;
bolt->damage = TD_DAMAGE;
bolt->dflags = 0;
bolt->splashDamage = TD_SPLASH_DAM;
bolt->splashRadius = TD_SPLASH_RAD;
bolt->s.eType = ET_MISSILE;
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
bolt->s.weapon = WP_THERMAL;
bolt->methodOfDeath = MOD_THERMAL;
bolt->splashMethodOfDeath = MOD_THERMAL_SPLASH;
bolt->s.pos.trTime = level.time; // move a bit on the very first frame
VectorCopy( start, bolt->s.pos.trBase );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy (start, bolt->r.currentOrigin);
VectorCopy( start, bolt->pos2 );
bolt->bounceCount = -5;
return bolt;
}
gentity_t *WP_DropThermal( gentity_t *ent )
{
AngleVectors( ent->client->ps.viewangles, forward, vright, up );
return (WP_FireThermalDetonator( ent, qfalse ));
}
//---------------------------------------------------------
qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask,
vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum,
float minSpeed, float maxSpeed, float idealSpeed, qboolean mustHit )
//---------------------------------------------------------
{ //for the galak mech NPC
float targetDist, shotSpeed, speedInc = 100, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed,
vec3_t targetDir, shotVel, failCase;
trace_t trace;
trajectory_t tr;
qboolean blocked;
int elapsedTime, skipNum, timeStep = 500, hitCount = 0, maxHits = 7;
vec3_t lastPos, testPos;
gentity_t *traceEnt;
if ( !idealSpeed )
{
idealSpeed = 300;
}
else if ( idealSpeed < speedInc )
{
idealSpeed = speedInc;
}
shotSpeed = idealSpeed;
skipNum = (idealSpeed-speedInc)/speedInc;
if ( !minSpeed )
{
minSpeed = 100;
}
if ( !maxSpeed )
{
maxSpeed = 900;
}
while ( hitCount < maxHits )
{
VectorSubtract( target, start, targetDir );
targetDist = VectorNormalize( targetDir );
VectorScale( targetDir, shotSpeed, shotVel );
travelTime = targetDist/shotSpeed;
shotVel[2] += travelTime * 0.5 * g_gravity.value;
if ( !hitCount )
{//save the first (ideal) one as the failCase (fallback value)
if ( !mustHit )
{//default is fine as a return value
VectorCopy( shotVel, failCase );
}
}
if ( tracePath )
{//do a rough trace of the path
blocked = qfalse;
VectorCopy( start, tr.trBase );
VectorCopy( shotVel, tr.trDelta );
tr.trType = TR_GRAVITY;
tr.trTime = level.time;
travelTime *= 1000.0f;
VectorCopy( start, lastPos );
//This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down?
for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
{
if ( (float)elapsedTime > travelTime )
{//cap it
elapsedTime = floor( travelTime );
}
BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
trap_Trace( &trace, lastPos, mins, maxs, testPos, ignoreEntNum, clipmask );
if ( trace.allsolid || trace.startsolid )
{
blocked = qtrue;
break;
}
if ( trace.fraction < 1.0f )
{//hit something
if ( trace.entityNum == enemyNum )
{//hit the enemy, that's perfect!
break;
}
else if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, target ) < 4096 )//hit within 64 of desired location, should be okay
{//close enough!
break;
}
else
{//FIXME: maybe find the extents of this brush and go above or below it on next try somehow?
impactDist = DistanceSquared( trace.endpos, target );
if ( impactDist < bestImpactDist )
{
bestImpactDist = impactDist;
VectorCopy( shotVel, failCase );
}
blocked = qtrue;
//see if we should store this as the failCase
if ( trace.entityNum < ENTITYNUM_WORLD )
{//hit an ent
traceEnt = &g_entities[trace.entityNum];
if ( traceEnt && traceEnt->takedamage && !OnSameTeam( self, traceEnt ) )
{//hit something breakable, so that's okay
//we haven't found a clear shot yet so use this as the failcase
VectorCopy( shotVel, failCase );
}
}
break;
}
}
if ( elapsedTime == floor( travelTime ) )
{//reached end, all clear
break;
}
else
{
//all clear, try next slice
VectorCopy( testPos, lastPos );
}
}
if ( blocked )
{//hit something, adjust speed (which will change arc)
hitCount++;
shotSpeed = idealSpeed + ((hitCount-skipNum) * speedInc);//from min to max (skipping ideal)
if ( hitCount >= skipNum )
{//skip ideal since that was the first value we tested
shotSpeed += speedInc;
}
}
else
{//made it!
break;
}
}
else
{//no need to check the path, go with first calc
break;
}
}
if ( hitCount >= maxHits )
{//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?)
VectorCopy( failCase, velocity );
return qfalse;
}
VectorCopy( shotVel, velocity );
return qtrue;
}
/*
======================================================================
LASER TRAP / TRIP MINE
======================================================================
*/
#define LT_DAMAGE 100
#define LT_SPLASH_RAD 256.0f
#define LT_SPLASH_DAM 105
#define LT_VELOCITY 900.0f
#define LT_SIZE 1.5f
#define LT_ALT_TIME 2000
#define LT_ACTIVATION_DELAY 1000
#define LT_DELAY_TIME 50
void laserTrapExplode( gentity_t *self )
{
vec3_t v;
self->takedamage = qfalse;
if (self->activator)
{
G_RadiusDamage( self->r.currentOrigin, self->activator, self->splashDamage, self->splashRadius, self, self, MOD_TRIP_MINE_SPLASH/*MOD_LT_SPLASH*/ );
}
if (self->s.weapon != WP_FLECHETTE)
{
G_AddEvent( self, EV_MISSILE_MISS, 0);
}
VectorCopy(self->s.pos.trDelta, v);
//Explode outward from the surface
if (self->s.time == -2)
{
v[0] = 0;
v[1] = 0;
v[2] = 0;
}
if (self->s.weapon == WP_FLECHETTE)
{
G_PlayEffect(EFFECT_EXPLOSION_FLECHETTE, self->r.currentOrigin, v);
}
else
{
G_PlayEffect(EFFECT_EXPLOSION_TRIPMINE, self->r.currentOrigin, v);
}
self->think = G_FreeEntity;
self->nextthink = level.time;
}
void laserTrapDelayedExplode( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
{
self->enemy = attacker;
self->think = laserTrapExplode;
self->nextthink = level.time + FRAMETIME;
self->takedamage = qfalse;
if ( attacker && !attacker->s.number )
{
//less damage when shot by player
self->splashDamage /= 3;
self->splashRadius /= 3;
}
}
void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace )
{
if (other && other->s.number < ENTITYNUM_WORLD)
{ //just explode if we hit any entity. This way we don't have things happening like tripmines floating
//in the air after getting stuck to a moving door
if ( ent->activator != other )
{
ent->touch = 0;
ent->nextthink = level.time + FRAMETIME;
ent->think = laserTrapExplode;
VectorCopy(trace->plane.normal, ent->s.pos.trDelta);
}
}
else
{
ent->touch = 0;
if (trace->entityNum != ENTITYNUM_NONE)
{
ent->enemy = &g_entities[trace->entityNum];
}
laserTrapStick(ent, trace->endpos, trace->plane.normal);
}
}
void proxMineThink(gentity_t *ent)
{
int i = 0;
gentity_t *cl;
gentity_t *owner = NULL;
if (ent->r.ownerNum < ENTITYNUM_WORLD)
{
owner = &g_entities[ent->r.ownerNum];
}
ent->nextthink = level.time;
if (ent->genericValue15 < level.time ||
!owner ||
!owner->inuse ||
!owner->client ||
owner->client->pers.connected != CON_CONNECTED)
{ //time to die!
ent->think = laserTrapExplode;
return;
}
while (i < MAX_CLIENTS)
{ //eh, just check for clients, don't care about anyone else...
cl = &g_entities[i];
if (cl->inuse && cl->client && cl->client->pers.connected == CON_CONNECTED &&
owner != cl && cl->client->sess.sessionTeam != TEAM_SPECTATOR &&
cl->client->tempSpectate < level.time && cl->health > 0)
{
if (!OnSameTeam(owner, cl) || g_friendlyFire.integer)
{ //not on the same team, or friendly fire is enabled
vec3_t v;
VectorSubtract(ent->r.currentOrigin, cl->client->ps.origin, v);
if (VectorLength(v) < (ent->splashRadius/2.0f))
{
ent->think = laserTrapExplode;
return;
}
}
}
i++;
}
}
void laserTrapThink ( gentity_t *ent )
{
gentity_t *traceEnt;
vec3_t end;
trace_t tr;
//just relink it every think
trap_LinkEntity(ent);
//turn on the beam effect
if ( !(ent->s.eFlags&EF_FIRING) )
{//arm me
G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) );
ent->s.eFlags |= EF_FIRING;
}
ent->think = laserTrapThink;
ent->nextthink = level.time + FRAMETIME;
// Find the main impact point
VectorMA ( ent->s.pos.trBase, 1024, ent->movedir, end );
trap_Trace ( &tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT);
traceEnt = &g_entities[ tr.entityNum ];
ent->s.time = -1; //let all clients know to draw a beam from this guy
if ( traceEnt->client || tr.startsolid )
{
//go boom
ent->touch = 0;
ent->nextthink = level.time + LT_DELAY_TIME;
ent->think = laserTrapExplode;
}
}
void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal )
{
G_SetOrigin( ent, endpos );
VectorCopy( normal, ent->pos1 );
VectorClear( ent->s.apos.trDelta );
// This will orient the object to face in the direction of the normal
VectorCopy( normal, ent->s.pos.trDelta );
//VectorScale( normal, -1, ent->s.pos.trDelta );
ent->s.pos.trTime = level.time;
//This does nothing, cg_missile makes assumptions about direction of travel controlling angles
vectoangles( normal, ent->s.apos.trBase );
VectorClear( ent->s.apos.trDelta );
ent->s.apos.trType = TR_STATIONARY;
VectorCopy( ent->s.apos.trBase, ent->s.angles );
VectorCopy( ent->s.angles, ent->r.currentAngles );
G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/stick.wav" ) );
if ( ent->count )
{//a tripwire
//add draw line flag
VectorCopy( normal, ent->movedir );
ent->think = laserTrapThink;
ent->nextthink = level.time + LT_ACTIVATION_DELAY;//delay the activation
ent->touch = touch_NULL;
//make it shootable
ent->takedamage = qtrue;
ent->health = 5;
ent->die = laserTrapDelayedExplode;
//shove the box through the wall
VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 );
VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 );
//so that the owner can blow it up with projectiles
ent->r.svFlags |= SVF_OWNERNOTSHARED;
}
else
{
ent->touch = touchLaserTrap;
ent->think = proxMineThink;//laserTrapExplode;
ent->genericValue15 = level.time + 30000; //auto-explode after 30 seconds.
ent->nextthink = level.time + LT_ALT_TIME; // How long 'til she blows
//make it shootable
ent->takedamage = qtrue;
ent->health = 5;
ent->die = laserTrapDelayedExplode;
//shove the box through the wall
VectorSet( ent->r.mins, -LT_SIZE*2, -LT_SIZE*2, -LT_SIZE*2 );
VectorSet( ent->r.maxs, LT_SIZE*2, LT_SIZE*2, LT_SIZE*2 );
//so that the owner can blow it up with projectiles
ent->r.svFlags |= SVF_OWNERNOTSHARED;
if ( !(ent->s.eFlags&EF_FIRING) )
{//arm me
G_Sound( ent, CHAN_WEAPON, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ) );
ent->s.eFlags |= EF_FIRING;
ent->s.time = -1;
ent->s.bolt2 = 1;
}
}
}
void TrapThink(gentity_t *ent)
{ //laser trap think
ent->nextthink = level.time + 50;
G_RunObject(ent);
}
void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner )
{ //create a laser trap entity
laserTrap->classname = "laserTrap";
laserTrap->flags |= FL_BOUNCE_HALF;
laserTrap->s.eFlags |= EF_MISSILE_STICK;
laserTrap->splashDamage = LT_SPLASH_DAM;
laserTrap->splashRadius = LT_SPLASH_RAD;
laserTrap->damage = LT_DAMAGE;
laserTrap->methodOfDeath = MOD_TRIP_MINE_SPLASH;
laserTrap->splashMethodOfDeath = MOD_TRIP_MINE_SPLASH;
laserTrap->s.eType = ET_GENERAL;
laserTrap->r.svFlags = SVF_USE_CURRENT_ORIGIN;
laserTrap->s.weapon = WP_TRIP_MINE;
laserTrap->s.pos.trType = TR_GRAVITY;
laserTrap->r.contents = MASK_SHOT;
laserTrap->parent = owner;
laserTrap->activator = owner;
laserTrap->r.ownerNum = owner->s.number;
VectorSet( laserTrap->r.mins, -LT_SIZE, -LT_SIZE, -LT_SIZE );
VectorSet( laserTrap->r.maxs, LT_SIZE, LT_SIZE, LT_SIZE );
laserTrap->clipmask = MASK_SHOT;
laserTrap->s.solid = 2;
laserTrap->s.modelindex = G_ModelIndex( "models/weapons2/laser_trap/laser_trap_w.glm" );
laserTrap->s.modelGhoul2 = 1;
laserTrap->s.g2radius = 40;
laserTrap->s.genericenemyindex = owner->s.number+MAX_GENTITIES;
laserTrap->health = 1;
laserTrap->s.time = 0;
laserTrap->s.pos.trTime = level.time; // move a bit on the very first frame
VectorCopy( start, laserTrap->s.pos.trBase );
SnapVector( laserTrap->s.pos.trBase ); // save net bandwidth
SnapVector( laserTrap->s.pos.trDelta ); // save net bandwidth
VectorCopy (start, laserTrap->r.currentOrigin);
laserTrap->s.apos.trType = TR_GRAVITY;
laserTrap->s.apos.trTime = level.time;
laserTrap->s.apos.trBase[YAW] = rand()%360;
laserTrap->s.apos.trBase[PITCH] = rand()%360;
laserTrap->s.apos.trBase[ROLL] = rand()%360;
if (rand()%10 < 5)
{
laserTrap->s.apos.trBase[YAW] = -laserTrap->s.apos.trBase[YAW];
}
VectorCopy( start, laserTrap->pos2 );
laserTrap->touch = touchLaserTrap;
laserTrap->think = TrapThink;
laserTrap->nextthink = level.time + 50;
}
void WP_PlaceLaserTrap( gentity_t *ent, qboolean alt_fire )
{
gentity_t *laserTrap;
gentity_t *found = NULL;
vec3_t dir, start;
int trapcount = 0;
int foundLaserTraps[MAX_GENTITIES];
int trapcount_org;
int lowestTimeStamp;
int removeMe;
int i;
foundLaserTraps[0] = ENTITYNUM_NONE;
VectorCopy( forward, dir );
VectorCopy( muzzle, start );
laserTrap = G_Spawn();
//limit to 10 placed at any one time
//see how many there are now
while ( (found = G_Find( found, FOFS(classname), "laserTrap" )) != NULL )
{
if ( found->parent != ent )
{
continue;
}
foundLaserTraps[trapcount++] = found->s.number;
}
//now remove first ones we find until there are only 9 left
found = NULL;
trapcount_org = trapcount;
lowestTimeStamp = level.time;
while ( trapcount > 9 )
{
removeMe = -1;
for ( i = 0; i < trapcount_org; i++ )
{
if ( foundLaserTraps[i] == ENTITYNUM_NONE )
{
continue;
}
found = &g_entities[foundLaserTraps[i]];
if ( laserTrap && found->setTime < lowestTimeStamp )
{
removeMe = i;
lowestTimeStamp = found->setTime;
}
}
if ( removeMe != -1 )
{
//remove it... or blow it?
if ( &g_entities[foundLaserTraps[removeMe]] == NULL )
{
break;
}
else
{
G_FreeEntity( &g_entities[foundLaserTraps[removeMe]] );
}
foundLaserTraps[removeMe] = ENTITYNUM_NONE;
trapcount--;
}
else
{
break;
}
}
//now make the new one
CreateLaserTrap( laserTrap, start, ent );
//set player-created-specific fields
laserTrap->setTime = level.time;//remember when we placed it
if (!alt_fire)
{//tripwire
laserTrap->count = 1;
}
//move it
laserTrap->s.pos.trType = TR_GRAVITY;
if (alt_fire)
{
VectorScale( dir, 512, laserTrap->s.pos.trDelta );
}
else
{
VectorScale( dir, 256, laserTrap->s.pos.trDelta );
}
trap_LinkEntity(laserTrap);
}
/*
======================================================================
DET PACK
======================================================================
*/
void VectorNPos(vec3_t in, vec3_t out)
{
if (in[0] < 0) { out[0] = -in[0]; } else { out[0] = in[0]; }
if (in[1] < 0) { out[1] = -in[1]; } else { out[1] = in[1]; }
if (in[2] < 0) { out[2] = -in[2]; } else { out[2] = in[2]; }
}
void DetPackBlow(gentity_t *self);
void charge_stick (gentity_t *self, gentity_t *other, trace_t *trace)
{
gentity_t *tent;
if ( other
&& (other->flags&FL_BBRUSH)
&& other->s.pos.trType == TR_STATIONARY
&& other->s.apos.trType == TR_STATIONARY )
{//a perfectly still breakable brush, let us attach directly to it!
self->target_ent = other;//remember them when we blow up
}
else if ( other
&& other->s.number < ENTITYNUM_WORLD
&& other->s.eType == ET_MOVER
&& trace->plane.normal[2] > 0 )
{//stick to it?
self->s.groundEntityNum = other->s.number;
}
else if (other && other->s.number < ENTITYNUM_WORLD &&
(other->client || !other->s.weapon))
{ //hit another entity that is not stickable, "bounce" off
vec3_t vNor, tN;
VectorCopy(trace->plane.normal, vNor);
VectorNormalize(vNor);
VectorNPos(self->s.pos.trDelta, tN);
self->s.pos.trDelta[0] += vNor[0]*(tN[0]*(((float)Q_irand(1, 10))*0.1));
self->s.pos.trDelta[1] += vNor[1]*(tN[1]*(((float)Q_irand(1, 10))*0.1));
self->s.pos.trDelta[2] += vNor[1]*(tN[2]*(((float)Q_irand(1, 10))*0.1));
vectoangles(vNor, self->s.angles);
vectoangles(vNor, self->s.apos.trBase);
self->touch = charge_stick;
return;
}
else if (other && other->s.number < ENTITYNUM_WORLD)
{ //hit an entity that we just want to explode on (probably another projectile or something)
vec3_t v;
self->touch = 0;
self->think = 0;
self->nextthink = 0;
self->takedamage = qfalse;
VectorClear(self->s.apos.trDelta);
self->s.apos.trType = TR_STATIONARY;
G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH );
VectorCopy(trace->plane.normal, v);
VectorCopy(v, self->pos2);
self->count = -1;
G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v);
self->think = G_FreeEntity;
self->nextthink = level.time;
return;
}
//if we get here I guess we hit hte world so we can stick to it
self->touch = 0;
self->think = DetPackBlow;
self->nextthink = level.time + 30000;
VectorClear(self->s.apos.trDelta);
self->s.apos.trType = TR_STATIONARY;
self->s.pos.trType = TR_STATIONARY;
VectorCopy( self->r.currentOrigin, self->s.origin );
VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
VectorClear( self->s.pos.trDelta );
VectorClear( self->s.apos.trDelta );
VectorNormalize(trace->plane.normal);
vectoangles(trace->plane.normal, self->s.angles);
VectorCopy(self->s.angles, self->r.currentAngles );
VectorCopy(self->s.angles, self->s.apos.trBase);
VectorCopy(trace->plane.normal, self->pos2);
self->count = -1;
G_Sound(self, CHAN_WEAPON, G_SoundIndex("sound/weapons/detpack/stick.wav"));
tent = G_TempEntity( self->r.currentOrigin, EV_MISSILE_MISS );
tent->s.weapon = 0;
tent->parent = self;
tent->r.ownerNum = self->s.number;
//so that the owner can blow it up with projectiles
self->r.svFlags |= SVF_OWNERNOTSHARED;
}
void DetPackBlow(gentity_t *self)
{
vec3_t v;
self->pain = 0;
self->die = 0;
self->takedamage = qfalse;
if ( self->target_ent )
{//we were attached to something, do *direct* damage to it!
G_Damage( self->target_ent, self, &g_entities[self->r.ownerNum], v, self->r.currentOrigin, self->damage, 0, MOD_DET_PACK_SPLASH );
}
G_RadiusDamage( self->r.currentOrigin, self->parent, self->splashDamage, self->splashRadius, self, self, MOD_DET_PACK_SPLASH );
v[0] = 0;
v[1] = 0;
v[2] = 1;
if (self->count == -1)
{
VectorCopy(self->pos2, v);
}
G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, v);
self->think = G_FreeEntity;
self->nextthink = level.time;
}
void DetPackPain(gentity_t *self, gentity_t *attacker, int damage)
{
self->think = DetPackBlow;
self->nextthink = level.time + Q_irand(50, 100);
self->takedamage = qfalse;
}
void DetPackDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
{
self->think = DetPackBlow;
self->nextthink = level.time + Q_irand(50, 100);
self->takedamage = qfalse;
}
void drop_charge (gentity_t *self, vec3_t start, vec3_t dir)
{
gentity_t *bolt;
VectorNormalize (dir);
bolt = G_Spawn();
bolt->classname = "detpack";
bolt->nextthink = level.time + FRAMETIME;
bolt->think = G_RunObject;
bolt->s.eType = ET_GENERAL;
bolt->s.g2radius = 100;
bolt->s.modelGhoul2 = 1;
bolt->s.modelindex = G_ModelIndex("models/weapons2/detpack/det_pack_proj.glm");
bolt->parent = self;
bolt->r.ownerNum = self->s.number;
bolt->damage = 100;
bolt->splashDamage = 200;
bolt->splashRadius = 200;
bolt->methodOfDeath = MOD_DET_PACK_SPLASH;
bolt->splashMethodOfDeath = MOD_DET_PACK_SPLASH;
bolt->clipmask = MASK_SHOT;
bolt->s.solid = 2;
bolt->r.contents = MASK_SHOT;
bolt->touch = charge_stick;
bolt->physicsObject = qtrue;
bolt->s.genericenemyindex = self->s.number+MAX_GENTITIES;
//rww - so client prediction knows we own this and won't hit it
VectorSet( bolt->r.mins, -2, -2, -2 );
VectorSet( bolt->r.maxs, 2, 2, 2 );
bolt->health = 1;
bolt->takedamage = qtrue;
bolt->pain = DetPackPain;
bolt->die = DetPackDie;
bolt->s.weapon = WP_DET_PACK;
bolt->setTime = level.time;
G_SetOrigin(bolt, start);
bolt->s.pos.trType = TR_GRAVITY;
VectorCopy( start, bolt->s.pos.trBase );
VectorScale(dir, 300, bolt->s.pos.trDelta );
bolt->s.pos.trTime = level.time;
bolt->s.apos.trType = TR_GRAVITY;
bolt->s.apos.trTime = level.time;
bolt->s.apos.trBase[YAW] = rand()%360;
bolt->s.apos.trBase[PITCH] = rand()%360;
bolt->s.apos.trBase[ROLL] = rand()%360;
if (rand()%10 < 5)
{
bolt->s.apos.trBase[YAW] = -bolt->s.apos.trBase[YAW];
}
vectoangles(dir, bolt->s.angles);
VectorCopy(bolt->s.angles, bolt->s.apos.trBase);
VectorSet(bolt->s.apos.trDelta, 300, 0, 0 );
bolt->s.apos.trTime = level.time;
trap_LinkEntity(bolt);
}
void BlowDetpacks(gentity_t *ent)
{
gentity_t *found = NULL;
if ( ent->client->ps.hasDetPackPlanted )
{
while ( (found = G_Find( found, FOFS(classname), "detpack") ) != NULL )
{//loop through all ents and blow the crap out of them!
if ( found->parent == ent )
{
VectorCopy( found->r.currentOrigin, found->s.origin );
found->think = DetPackBlow;
found->nextthink = level.time + 100 + random() * 200;
G_Sound( found, CHAN_BODY, G_SoundIndex("sound/weapons/detpack/warning.wav") );
}
}
ent->client->ps.hasDetPackPlanted = qfalse;
}
}
qboolean CheatsOn(void)
{
if ( !g_cheats.integer )
{
return qfalse;
}
return qtrue;
}
void WP_DropDetPack( gentity_t *ent, qboolean alt_fire )
{
gentity_t *found = NULL;
int trapcount = 0;
int foundDetPacks[MAX_GENTITIES] = {ENTITYNUM_NONE};
int trapcount_org;
int lowestTimeStamp;
int removeMe;
int i;
if ( !ent || !ent->client )
{
return;
}
//limit to 10 placed at any one time
//see how many there are now
while ( (found = G_Find( found, FOFS(classname), "detpack" )) != NULL )
{
if ( found->parent != ent )
{
continue;
}
foundDetPacks[trapcount++] = found->s.number;
}
//now remove first ones we find until there are only 9 left
found = NULL;
trapcount_org = trapcount;
lowestTimeStamp = level.time;
while ( trapcount > 9 )
{
removeMe = -1;
for ( i = 0; i < trapcount_org; i++ )
{
if ( foundDetPacks[i] == ENTITYNUM_NONE )
{
continue;
}
found = &g_entities[foundDetPacks[i]];
if ( found->setTime < lowestTimeStamp )
{
removeMe = i;
lowestTimeStamp = found->setTime;
}
}
if ( removeMe != -1 )
{
//remove it... or blow it?
if ( &g_entities[foundDetPacks[removeMe]] == NULL )
{
break;
}
else
{
if (!CheatsOn())
{ //Let them have unlimited if cheats are enabled
G_FreeEntity( &g_entities[foundDetPacks[removeMe]] );
}
}
foundDetPacks[removeMe] = ENTITYNUM_NONE;
trapcount--;
}
else
{
break;
}
}
if ( alt_fire )
{
BlowDetpacks(ent);
}
else
{
AngleVectors( ent->client->ps.viewangles, forward, vright, up );
CalcMuzzlePoint( ent, forward, vright, up, muzzle );
VectorNormalize( forward );
VectorMA( muzzle, -4, forward, muzzle );
drop_charge( ent, muzzle, forward );
ent->client->ps.hasDetPackPlanted = qtrue;
}
}
#pragma warning(disable : 4701) //local variable may be used without having been initialized
static void WP_FireConcussionAlt( gentity_t *ent )
{//a rail-gun-like beam
int damage = CONC_ALT_DAMAGE, skip, traces = DISRUPTOR_ALT_TRACES;
qboolean render_impact = qtrue;
vec3_t start, end;
vec3_t muzzle2, dir;
trace_t tr;
gentity_t *traceEnt, *tent;
float shotRange = 8192.0f;
qboolean hitDodged = qfalse;
vec3_t shot_mins, shot_maxs;
int i;
//Shove us backwards for half a second
VectorMA( ent->client->ps.velocity, -200, forward, ent->client->ps.velocity );
ent->client->ps.groundEntityNum = ENTITYNUM_NONE;
if ( (ent->client->ps.pm_flags&PMF_DUCKED) )
{//hunkered down
ent->client->ps.pm_time = 100;
}
else
{
ent->client->ps.pm_time = 250;
}
// ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK|PMF_TIME_NOFRICTION;
//FIXME: only if on ground? So no "rocket jump"? Or: (see next FIXME)
//FIXME: instead, set a forced ucmd backmove instead of this sliding
VectorCopy( muzzle, muzzle2 ); // making a backup copy
VectorCopy( muzzle, start );
WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
skip = ent->s.number;
// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time )
// {
// // in overcharge mode, so doing double damage
// damage *= 2;
// }
//Make it a little easier to hit guys at long range
VectorSet( shot_mins, -1, -1, -1 );
VectorSet( shot_maxs, 1, 1, 1 );
for ( i = 0; i < traces; i++ )
{
VectorMA( start, shotRange, forward, end );
//NOTE: if you want to be able to hit guys in emplaced guns, use "G2_COLLIDE, 10" instead of "G2_RETURNONHIT, 0"
//alternately, if you end up hitting an emplaced_gun that has a sitter, just redo this one trace with the "G2_COLLIDE, 10" to see if we it the sitter
//gi.trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 );
if (d_projectileGhoul2Collision.integer)
{
trap_G2Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
}
else
{
trap_Trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT );
}
traceEnt = &g_entities[tr.entityNum];
if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
{ //g2 collision checks -rww
if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
{ //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
traceEnt->client->g2LastSurfaceTime = level.time;
}
if (traceEnt->ghoul2)
{
tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
}
}
if ( tr.surfaceFlags & SURF_NOIMPACT )
{
render_impact = qfalse;
}
if ( tr.entityNum == ent->s.number )
{
// should never happen, but basically we don't want to consider a hit to ourselves?
// Get ready for an attempt to trace through another person
VectorCopy( tr.endpos, muzzle2 );
VectorCopy( tr.endpos, start );
skip = tr.entityNum;
#ifdef _DEBUG
Com_Printf( "BAD! Concussion gun shot somehow traced back and hit the owner!\n" );
#endif
continue;
}
// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
//NOTE: let's just draw one beam at the end
//tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT );
//tent->svFlags |= SVF_BROADCAST;
//VectorCopy( muzzle2, tent->s.origin2 );
if ( tr.fraction >= 1.0f )
{
// draw the beam but don't do anything else
break;
}
if ( traceEnt->s.weapon == WP_SABER )//&& traceEnt->NPC
{//FIXME: need a more reliable way to know we hit a jedi?
hitDodged = Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE );
//acts like we didn't even hit him
}
if ( !hitDodged )
{
if ( render_impact )
{
if (( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
|| !Q_stricmp( traceEnt->classname, "misc_model_breakable" )
|| traceEnt->s.eType == ET_MOVER )
{
qboolean noKnockBack;
// Create a simple impact type mark that doesn't last long in the world
//G_PlayEffectID( G_EffectIndex( "concussion/alt_hit" ), tr.endpos, tr.plane.normal );
//no no no
if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
{//NOTE: hitting multiple ents can still get you over 100% accuracy
ent->client->accuracy_hits++;
}
noKnockBack = (traceEnt->flags&FL_NO_KNOCKBACK);//will be set if they die, I want to know if it was on *before* they died
if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH )
{//hehe
G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT );
break;
}
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT );
//do knockback and knockdown manually
if ( traceEnt->client )
{//only if we hit a client
vec3_t pushDir;
VectorCopy( forward, pushDir );
if ( pushDir[2] < 0.2f )
{
pushDir[2] = 0.2f;
}//hmm, re-normalize? nah...
/*
if ( !noKnockBack )
{//knock-backable
G_Throw( traceEnt, pushDir, 200 );
}
*/
if ( traceEnt->health > 0 )
{//alive
//if ( G_HasKnockdownAnims( traceEnt ) )
if (!noKnockBack && !traceEnt->localAnimIndex && traceEnt->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN &&
BG_KnockDownable(&traceEnt->client->ps)) //just check for humanoids..
{//knock-downable
//G_Knockdown( traceEnt, ent, pushDir, 400, qtrue );
vec3_t plPDif;
float pStr;
//cap it and stuff, base the strength and whether or not we can knockdown on the distance
//from the shooter to the target
VectorSubtract(traceEnt->client->ps.origin, ent->client->ps.origin, plPDif);
pStr = 500.0f-VectorLength(plPDif);
if (pStr < 150.0f)
{
pStr = 150.0f;
}
if (pStr > 200.0f)
{
traceEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
traceEnt->client->ps.forceHandExtendTime = level.time + 1100;
traceEnt->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim
}
traceEnt->client->ps.otherKiller = ent->s.number;
traceEnt->client->ps.otherKillerTime = level.time + 5000;
traceEnt->client->ps.otherKillerDebounceTime = level.time + 100;
traceEnt->client->otherKillerMOD = MOD_UNKNOWN;
traceEnt->client->otherKillerVehWeapon = 0;
traceEnt->client->otherKillerWeaponType = WP_NONE;
traceEnt->client->ps.velocity[0] += pushDir[0]*pStr;
traceEnt->client->ps.velocity[1] += pushDir[1]*pStr;
traceEnt->client->ps.velocity[2] = pStr;
}
}
}
if ( traceEnt->s.eType == ET_MOVER )
{//stop the traces on any mover
break;
}
}
else
{
// we only make this mark on things that can't break or move
// tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
// tent->s.eventParm = DirToByte(tr.plane.normal);
// tent->s.eFlags |= EF_ALT_FIRING;
//tent->svFlags |= SVF_BROADCAST;
//eh? why broadcast?
// VectorCopy( tr.plane.normal, tent->pos1 );
//mmm..no..don't do this more than once for no reason whatsoever.
break; // hit solid, but doesn't take damage, so stop the shot...we _could_ allow it to shoot through walls, might be cool?
}
}
else // not rendering impact, must be a skybox or other similar thing?
{
break; // don't try anymore traces
}
}
// Get ready for an attempt to trace through another person
VectorCopy( tr.endpos, muzzle2 );
VectorCopy( tr.endpos, start );
skip = tr.entityNum;
hitDodged = qfalse;
}
//just draw one beam all the way to the end
// tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT );
// tent->svFlags |= SVF_BROADCAST;
//again, why broadcast?
// tent = G_TempEntity(tr.endpos, EV_MISSILE_MISS);
// tent->s.eventParm = DirToByte(tr.plane.normal);
// tent->s.eFlags |= EF_ALT_FIRING;
// VectorCopy( muzzle, tent->s.origin2 );
// now go along the trail and make sight events
VectorSubtract( tr.endpos, muzzle, dir );
// shotDist = VectorNormalize( dir );
//let's pack all this junk into a single tempent, and send it off.
tent = G_TempEntity(tr.endpos, EV_CONC_ALT_IMPACT);
tent->s.eventParm = DirToByte(tr.plane.normal);
tent->s.owner = ent->s.number;
VectorCopy(dir, tent->s.angles);
VectorCopy(muzzle, tent->s.origin2);
VectorCopy(forward, tent->s.angles2);
#if 0 //yuck
//FIXME: if shoot *really* close to someone, the alert could be way out of their FOV
for ( dist = 0; dist < shotDist; dist += 64 )
{
//FIXME: on a really long shot, this could make a LOT of alerts in one frame...
VectorMA( muzzle, dist, dir, spot );
AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
//FIXME: creates *way* too many effects, make it one effect somehow?
G_PlayEffectID( G_EffectIndex( "concussion/alt_ring" ), spot, actualAngles );
}
//FIXME: spawn a temp ent that continuously spawns sight alerts here? And 1 sound alert to draw their attention?
VectorMA( start, shotDist-4, forward, spot );
AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
G_PlayEffectID( G_EffectIndex( "concussion/altmuzzle_flash" ), muzzle, forward );
#endif
}
#pragma warning(default : 4701) //local variable may be used without having been initialized
static void WP_FireConcussion( gentity_t *ent )
{//a fast rocket-like projectile
vec3_t start;
int damage = CONC_DAMAGE;
float vel = CONC_VELOCITY;
gentity_t *missile;
//hold us still for a bit
//ent->client->ps.pm_time = 300;
//ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
//add viewkick
// if ( ent->s.number < MAX_CLIENTS//player only
// && !cg.renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise
// {//kick the view back
// cg.kick_angles[PITCH] = Q_flrand( -10, -15 );
// cg.kick_time = level.time;
// }
//mm..yeah..this needs some reworking for mp
VectorCopy( muzzle, start );
WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
missile = CreateMissile( start, forward, vel, 10000, ent, qfalse );
missile->classname = "conc_proj";
missile->s.weapon = WP_CONCUSSION;
missile->mass = 10;
// Make it easier to hit things
VectorSet( missile->r.maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
VectorScale( missile->r.maxs, -1, missile->r.mins );
missile->damage = damage;
missile->dflags = DAMAGE_EXTRA_KNOCKBACK;
missile->methodOfDeath = MOD_CONC;
missile->splashMethodOfDeath = MOD_CONC;
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
missile->splashDamage = CONC_SPLASH_DAMAGE;
missile->splashRadius = CONC_SPLASH_RADIUS;
// we don't want it to ever bounce
missile->bounceCount = 0;
}
//---------------------------------------------------------
// FireStunBaton
//---------------------------------------------------------
void WP_FireStunBaton( gentity_t *ent, qboolean alt_fire )
{
gentity_t *tr_ent;
trace_t tr;
vec3_t mins, maxs, end;
vec3_t muzzleStun;
if (!ent->client)
{
VectorCopy(ent->r.currentOrigin, muzzleStun);
muzzleStun[2] += 8;
}
else
{
VectorCopy(ent->client->ps.origin, muzzleStun);
muzzleStun[2] += ent->client->ps.viewheight-6;
}
VectorMA(muzzleStun, 20.0f, forward, muzzleStun);
VectorMA(muzzleStun, 4.0f, vright, muzzleStun);
VectorMA( muzzleStun, STUN_BATON_RANGE, forward, end );
VectorSet( maxs, 6, 6, 6 );
VectorScale( maxs, -1, mins );
trap_Trace ( &tr, muzzleStun, mins, maxs, end, ent->s.number, MASK_SHOT );
if ( tr.entityNum >= ENTITYNUM_WORLD )
{
return;
}
tr_ent = &g_entities[tr.entityNum];
if (tr_ent && tr_ent->takedamage && tr_ent->client)
{ //see if either party is involved in a duel
if (tr_ent->client->ps.duelInProgress &&
tr_ent->client->ps.duelIndex != ent->s.number)
{
return;
}
if (ent->client &&
ent->client->ps.duelInProgress &&
ent->client->ps.duelIndex != tr_ent->s.number)
{
return;
}
}
if ( tr_ent && tr_ent->takedamage )
{
G_PlayEffect( EFFECT_STUNHIT, tr.endpos, tr.plane.normal );
G_Sound( tr_ent, CHAN_WEAPON, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
G_Damage( tr_ent, ent, ent, forward, tr.endpos, STUN_BATON_DAMAGE, (DAMAGE_NO_KNOCKBACK|DAMAGE_HALF_ABSORB), MOD_STUN_BATON );
if (tr_ent->client)
{ //if it's a player then use the shock effect
if ( tr_ent->client->NPC_class == CLASS_VEHICLE )
{//not on vehicles
if ( !tr_ent->m_pVehicle
|| tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL
|| tr_ent->m_pVehicle->m_pVehicleInfo->type == VH_FLIER )
{//can zap animals
tr_ent->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
}
}
else
{
tr_ent->client->ps.electrifyTime = level.time + 700;
}
}
}
}
//---------------------------------------------------------
// FireMelee
//---------------------------------------------------------
void WP_FireMelee( gentity_t *ent, qboolean alt_fire )
{
gentity_t *tr_ent;
trace_t tr;
vec3_t mins, maxs, end;
vec3_t muzzlePunch;
if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2)
{ //right
if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM))
{
return;
}
}
else
{ //left
if (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))
{
return;
}
}
if (!ent->client)
{
VectorCopy(ent->r.currentOrigin, muzzlePunch);
muzzlePunch[2] += 8;
}
else
{
VectorCopy(ent->client->ps.origin, muzzlePunch);
muzzlePunch[2] += ent->client->ps.viewheight-6;
}
VectorMA(muzzlePunch, 20.0f, forward, muzzlePunch);
VectorMA(muzzlePunch, 4.0f, vright, muzzlePunch);
VectorMA( muzzlePunch, MELEE_RANGE, forward, end );
VectorSet( maxs, 6, 6, 6 );
VectorScale( maxs, -1, mins );
trap_Trace ( &tr, muzzlePunch, mins, maxs, end, ent->s.number, MASK_SHOT );
if (tr.entityNum != ENTITYNUM_NONE)
{ //hit something
tr_ent = &g_entities[tr.entityNum];
G_Sound( ent, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
if (tr_ent->takedamage && tr_ent->client)
{ //special duel checks
if (tr_ent->client->ps.duelInProgress &&
tr_ent->client->ps.duelIndex != ent->s.number)
{
return;
}
if (ent->client &&
ent->client->ps.duelInProgress &&
ent->client->ps.duelIndex != tr_ent->s.number)
{
return;
}
}
if ( tr_ent->takedamage )
{ //damage them, do more damage if we're in the second right hook
int dmg = MELEE_SWING1_DAMAGE;
if (ent->client && ent->client->ps.torsoAnim == BOTH_MELEE2)
{ //do a tad bit more damage on the second swing
dmg = MELEE_SWING2_DAMAGE;
}
if ( G_HeavyMelee( ent ) )
{ //2x damage for heavy melee class
dmg *= 2;
}
G_Damage( tr_ent, ent, ent, forward, tr.endpos, dmg, DAMAGE_NO_ARMOR, MOD_MELEE );
}
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/*
======================
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;
}
}
}
//======================================================================
/*
===============
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)
{
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
rwwFIXMEFIXME: Since ghoul2 models are on server and properly updated now,
it may be reasonable to base muzzle point off actual weapon bolt point.
The down side would be that it does not necessarily look alright from a
first person perspective.
===============
*/
void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint )
{
int weapontype;
vec3_t muzzleOffPoint;
weapontype = ent->s.weapon;
VectorCopy( ent->s.pos.trBase, muzzlePoint );
VectorCopy(WP_MuzzlePoint[weapontype], muzzleOffPoint);
if (weapontype > WP_NONE && weapontype < WP_NUM_WEAPONS)
{ // Use the table to generate the muzzlepoint;
{ // Crouching. Use the add-to-Z method to adjust vertically.
VectorMA(muzzlePoint, muzzleOffPoint[0], forward, muzzlePoint);
VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint);
muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2];
}
}
// 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 );
muzzlePoint[2] += ent->client->ps.viewheight;
VectorMA( muzzlePoint, 14, forward, muzzlePoint );
// snap to integer coordinates for more efficient network bandwidth usage
SnapVector( muzzlePoint );
}
extern void G_MissileImpact( gentity_t *ent, trace_t *trace );
void WP_TouchVehMissile( gentity_t *ent, gentity_t *other, trace_t *trace )
{
trace_t myTrace;
memcpy( (void *)&myTrace, (void *)trace, sizeof(myTrace) );
if ( other )
{
myTrace.entityNum = other->s.number;
}
G_MissileImpact( ent, &myTrace );
}
void WP_CalcVehMuzzle(gentity_t *ent, int muzzleNum)
{
Vehicle_t *pVeh = ent->m_pVehicle;
mdxaBone_t boltMatrix;
vec3_t vehAngles;
assert(pVeh);
if (pVeh->m_iMuzzleTime[muzzleNum] == level.time)
{ //already done for this frame, don't need to do it again
return;
}
//Uh... how about we set this, hunh...? :)
pVeh->m_iMuzzleTime[muzzleNum] = level.time;
VectorCopy( ent->client->ps.viewangles, vehAngles );
if ( pVeh->m_pVehicleInfo
&& (pVeh->m_pVehicleInfo->type == VH_ANIMAL
||pVeh->m_pVehicleInfo->type == VH_WALKER
||pVeh->m_pVehicleInfo->type == VH_SPEEDER) )
{
vehAngles[PITCH] = vehAngles[ROLL] = 0;
}
trap_G2API_GetBoltMatrix_NoRecNoRot(ent->ghoul2, 0, pVeh->m_iMuzzleTag[muzzleNum], &boltMatrix, vehAngles,
ent->client->ps.origin, level.time, NULL, ent->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pVeh->m_vMuzzlePos[muzzleNum]);
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, pVeh->m_vMuzzleDir[muzzleNum]);
}
void WP_VehWeapSetSolidToOwner( gentity_t *self )
{
self->r.svFlags |= SVF_OWNERNOTSHARED;
if ( self->genericValue1 )
{//expire after a time
if ( self->genericValue2 )
{//blow up when your lifetime is up
self->think = G_ExplodeMissile;//FIXME: custom func?
}
else
{//just remove yourself
self->think = G_FreeEntity;//FIXME: custom func?
}
self->nextthink = level.time + self->genericValue1;
}
}
#define VEH_HOMING_MISSILE_THINK_TIME 100
gentity_t *WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon, qboolean alt_fire, qboolean isTurretWeap )
{
gentity_t *missile = NULL;
//FIXME: add some randomness...? Inherent inaccuracy stat of weapon? Pilot skill?
if ( !vehWeapon )
{//invalid vehicle weapon
return NULL;
}
else if ( vehWeapon->bIsProjectile )
{//projectile entity
vec3_t mins, maxs;
VectorSet( maxs, vehWeapon->fWidth/2.0f,vehWeapon->fWidth/2.0f,vehWeapon->fHeight/2.0f );
VectorScale( maxs, -1, mins );
//make sure our start point isn't on the other side of a wall
WP_TraceSetStart( ent, start, mins, maxs );
//FIXME: CUSTOM MODEL?
//QUERY: alt_fire true or not? Does it matter?
missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse );
missile->classname = "vehicle_proj";
missile->s.genericenemyindex = ent->s.number+MAX_GENTITIES;
missile->damage = vehWeapon->iDamage;
missile->splashDamage = vehWeapon->iSplashDamage;
missile->splashRadius = vehWeapon->fSplashRadius;
//FIXME: externalize some of these properties?
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
missile->clipmask = MASK_SHOT;
//Maybe by checking flags...?
if ( vehWeapon->bSaberBlockable )
{
missile->clipmask |= CONTENTS_LIGHTSABER;
}
/*
if ( (vehWeapon->iFlags&VWF_KNOCKBACK) )
{
missile->dflags &= ~DAMAGE_DEATH_KNOCKBACK;
}
if ( (vehWeapon->iFlags&VWF_RADAR) )
{
missile->s.eFlags |= EF_RADAROBJECT;
}
*/
// Make it easier to hit things
VectorCopy( mins, missile->r.mins );
VectorCopy( maxs, missile->r.maxs );
//some slightly different stuff for things with bboxes
if ( vehWeapon->fWidth || vehWeapon->fHeight )
{//we assume it's a rocket-like thing
missile->s.weapon = WP_ROCKET_LAUNCHER;//does this really matter?
missile->methodOfDeath = MOD_VEHICLE;//MOD_ROCKET;
missile->splashMethodOfDeath = MOD_VEHICLE;//MOD_ROCKET;// ?SPLASH;
// we don't want it to ever bounce
missile->bounceCount = 0;
missile->mass = 10;
}
else
{//a blaster-laser-like thing
missile->s.weapon = WP_BLASTER;//does this really matter?
missile->methodOfDeath = MOD_VEHICLE; //count as a heavy weap
missile->splashMethodOfDeath = MOD_VEHICLE;// ?SPLASH;
// we don't want it to bounce forever
missile->bounceCount = 8;
}
if ( vehWeapon->bHasGravity )
{//TESTME: is this all we need to do?
missile->s.weapon = WP_THERMAL;//does this really matter?
missile->s.pos.trType = TR_GRAVITY;
}
if ( vehWeapon->bIonWeapon )
{//so it disables ship shields and sends them out of control
missile->s.weapon = WP_DEMP2;
}
if ( vehWeapon->iHealth )
{//the missile can take damage
/*
//don't do this - ships hit them first and have no trace.plane.normal to bounce off it at and end up in the middle of the asteroid...
missile->health = vehWeapon->iHealth;
missile->takedamage = qtrue;
missile->r.contents = MASK_SHOT;
missile->die = RocketDie;
*/
}
//pilot should own this projectile on server if we have a pilot
if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot)
{//owned by vehicle pilot
missile->r.ownerNum = ent->m_pVehicle->m_pPilot->s.number;
}
else
{//owned by vehicle?
missile->r.ownerNum = ent->s.number;
}
//set veh as cgame side owner for purpose of fx overrides
missile->s.owner = ent->s.number;
if ( alt_fire )
{//use the second weapon's iShotFX
missile->s.eFlags |= EF_ALT_FIRING;
}
if ( isTurretWeap )
{//look for the turret weapon info on cgame side, not vehicle weapon info
missile->s.weapon = WP_TURRET;
}
if ( vehWeapon->iLifeTime )
{//expire after a time
if ( vehWeapon->bExplodeOnExpire )
{//blow up when your lifetime is up
missile->think = G_ExplodeMissile;//FIXME: custom func?
}
else
{//just remove yourself
missile->think = G_FreeEntity;//FIXME: custom func?
}
missile->nextthink = level.time + vehWeapon->iLifeTime;
}
missile->s.otherEntityNum2 = (vehWeapon-&g_vehWeaponInfo[0]);
missile->s.eFlags |= EF_JETPACK_ACTIVE;
//homing
if ( vehWeapon->fHoming )
{//homing missile
if ( ent->client && ent->client->ps.rocketLockIndex != ENTITYNUM_NONE )
{
int dif = 0;
float rTime;
rTime = ent->client->ps.rocketLockTime;
if (rTime == -1)
{
rTime = ent->client->ps.rocketLastValidTime;
}
if ( !vehWeapon->iLockOnTime )
{//no minimum lock-on time
dif = 10;//guaranteed lock-on
}
else
{
float lockTimeInterval = vehWeapon->iLockOnTime/16.0f;
dif = ( level.time - rTime ) / lockTimeInterval;
}
if (dif < 0)
{
dif = 0;
}
//It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client
if ( dif >= 10 && rTime != -1 )
{
missile->enemy = &g_entities[ent->client->ps.rocketLockIndex];
if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
{ //if enemy became invalid, died, or is on the same team, then don't seek it
missile->spawnflags |= 1;//just to let it know it should be faster...
missile->speed = vehWeapon->fSpeed;
missile->angle = vehWeapon->fHoming;
missile->radius = vehWeapon->fHomingFOV;
//crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame...
if ( vehWeapon->iLifeTime )
{//expire after a time
missile->genericValue1 = level.time + vehWeapon->iLifeTime;
missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire);
}
//now go ahead and use the rocketThink func
missile->think = rocketThink;//FIXME: custom func?
missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME;
missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize
if ( missile->enemy->s.NPC_class == CLASS_VEHICLE )
{//let vehicle know we've locked on to them
missile->s.otherEntityNum = missile->enemy->s.number;
}
}
}
VectorCopy( dir, missile->movedir );
missile->random = 1.0f;//FIXME: externalize?
}
}
if ( !vehWeapon->fSpeed )
{//a mine or something?
if ( vehWeapon->iHealth )
{//the missile can take damage
missile->health = vehWeapon->iHealth;
missile->takedamage = qtrue;
missile->r.contents = MASK_SHOT;
missile->die = RocketDie;
}
//only do damage when someone touches us
missile->s.weapon = WP_THERMAL;//does this really matter?
G_SetOrigin( missile, start );
missile->touch = WP_TouchVehMissile;
missile->s.eFlags |= EF_RADAROBJECT;//FIXME: externalize
//crap, if we have a lifetime, need to store that somewhere else on ent and have rocketThink func check it every frame...
if ( vehWeapon->iLifeTime )
{//expire after a time
missile->genericValue1 = vehWeapon->iLifeTime;
missile->genericValue2 = (int)(vehWeapon->bExplodeOnExpire);
}
//now go ahead and use the setsolidtoowner func
missile->think = WP_VehWeapSetSolidToOwner;
missile->nextthink = level.time + 3000;
}
}
else
{//traceline
//FIXME: implement
}
return missile;
}
//custom routine to not waste tempents horribly -rww
void G_VehMuzzleFireFX( gentity_t *ent, gentity_t *broadcaster, int muzzlesFired )
{
Vehicle_t *pVeh = ent->m_pVehicle;
gentity_t *b;
if (!pVeh)
{
return;
}
if (!broadcaster)
{ //oh well. We will WASTE A TEMPENT.
b = G_TempEntity( ent->client->ps.origin, EV_VEH_FIRE );
}
else
{ //joy
b = broadcaster;
}
//this guy owns it
b->s.owner = ent->s.number;
//this is the bitfield of all muzzles fired this time
//NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits
b->s.trickedentindex = muzzlesFired;
if ( broadcaster )
{ //add the event
G_AddEvent( b, EV_VEH_FIRE, 0 );
}
}
void G_EstimateCamPos( vec3_t viewAngles, vec3_t cameraFocusLoc, float viewheight, float thirdPersonRange,
float thirdPersonHorzOffset, float vertOffset, float pitchOffset,
int ignoreEntNum, vec3_t camPos )
{
int MASK_CAMERACLIP = (MASK_SOLID|CONTENTS_PLAYERCLIP);
float CAMERA_SIZE = 4;
vec3_t cameramins;
vec3_t cameramaxs;
vec3_t cameraFocusAngles, camerafwd, cameraup;
vec3_t cameraIdealTarget, cameraCurTarget;
vec3_t cameraIdealLoc, cameraCurLoc;
vec3_t diff;
vec3_t camAngles;
vec3_t viewaxis[3];
trace_t trace;
VectorSet( cameramins, -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE );
VectorSet( cameramaxs, CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE );
VectorCopy( viewAngles, cameraFocusAngles );
cameraFocusAngles[PITCH] += pitchOffset;
if ( !bg_fighterAltControl.integer )
{//clamp view pitch
cameraFocusAngles[PITCH] = AngleNormalize180( cameraFocusAngles[PITCH] );
if (cameraFocusAngles[PITCH] > 80.0)
{
cameraFocusAngles[PITCH] = 80.0;
}
else if (cameraFocusAngles[PITCH] < -80.0)
{
cameraFocusAngles[PITCH] = -80.0;
}
}
AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup);
cameraFocusLoc[2] += viewheight;
VectorCopy( cameraFocusLoc, cameraIdealTarget );
cameraIdealTarget[2] += vertOffset;
//NOTE: on cgame, this uses the thirdpersontargetdamp value, we ignore that here
VectorCopy( cameraIdealTarget, cameraCurTarget );
trap_Trace( &trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, ignoreEntNum, MASK_CAMERACLIP );
if (trace.fraction < 1.0)
{
VectorCopy(trace.endpos, cameraCurTarget);
}
VectorMA(cameraIdealTarget, -(thirdPersonRange), camerafwd, cameraIdealLoc);
//NOTE: on cgame, this uses the thirdpersoncameradamp value, we ignore that here
VectorCopy( cameraIdealLoc, cameraCurLoc );
trap_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, ignoreEntNum, MASK_CAMERACLIP);
if (trace.fraction < 1.0)
{
VectorCopy( trace.endpos, cameraCurLoc );
}
VectorSubtract(cameraCurTarget, cameraCurLoc, diff);
{
float dist = VectorNormalize(diff);
//under normal circumstances, should never be 0.00000 and so on.
if ( !dist || (diff[0] == 0 || diff[1] == 0) )
{//must be hitting something, need some value to calc angles, so use cam forward
VectorCopy( camerafwd, diff );
}
}
vectoangles(diff, camAngles);
if ( thirdPersonHorzOffset != 0.0f )
{
AnglesToAxis( camAngles, viewaxis );
VectorMA( cameraCurLoc, thirdPersonHorzOffset, viewaxis[1], cameraCurLoc );
}
VectorCopy(cameraCurLoc, camPos);
}
void WP_GetVehicleCamPos( gentity_t *ent, gentity_t *pilot, vec3_t camPos )
{
float thirdPersonHorzOffset = ent->m_pVehicle->m_pVehicleInfo->cameraHorzOffset;
float thirdPersonRange = ent->m_pVehicle->m_pVehicleInfo->cameraRange;
float pitchOffset = ent->m_pVehicle->m_pVehicleInfo->cameraPitchOffset;
float vertOffset = ent->m_pVehicle->m_pVehicleInfo->cameraVertOffset;
if ( ent->client->ps.hackingTime )
{
thirdPersonHorzOffset += (((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * -80.0f;
thirdPersonRange += fabs(((float)ent->client->ps.hackingTime)/MAX_STRAFE_TIME) * 100.0f;
}
if ( ent->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset )
{
if ( pilot->client->ps.viewangles[PITCH] > 0 )
{
vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-10;
if ( vertOffset < -170 )
{
vertOffset = -170;
}
}
else if ( pilot->client->ps.viewangles[PITCH] < 0 )
{
vertOffset = 130+pilot->client->ps.viewangles[PITCH]*-5;
if ( vertOffset > 130 )
{
vertOffset = 130;
}
}
else
{
vertOffset = 30;
}
if ( pilot->client->ps.viewangles[PITCH] > 0 )
{
pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75;
}
else if ( pilot->client->ps.viewangles[PITCH] < 0 )
{
pitchOffset = pilot->client->ps.viewangles[PITCH]*-0.75;
}
else
{
pitchOffset = 0;
}
}
//Control Scheme 3 Method:
G_EstimateCamPos( ent->client->ps.viewangles, pilot->client->ps.origin, pilot->client->ps.viewheight, thirdPersonRange,
thirdPersonHorzOffset, vertOffset, pitchOffset,
pilot->s.number, camPos );
/*
//Control Scheme 2 Method:
G_EstimateCamPos( ent->m_pVehicle->m_vOrientation, ent->r.currentOrigin, pilot->client->ps.viewheight, thirdPersonRange,
thirdPersonHorzOffset, vertOffset, pitchOffset,
pilot->s.number, camPos );
*/
}
void WP_VehLeadCrosshairVeh( gentity_t *camTraceEnt, vec3_t newEnd, const vec3_t dir, const vec3_t shotStart, vec3_t shotDir )
{
if ( g_vehAutoAimLead.integer )
{
if ( camTraceEnt
&& camTraceEnt->client
&& camTraceEnt->client->NPC_class == CLASS_VEHICLE )
{//if the crosshair is on a vehicle, lead it
float dot, distAdjust = DotProduct( camTraceEnt->client->ps.velocity, dir );
vec3_t predPos, predShotDir;
if ( distAdjust > 500 || DistanceSquared( camTraceEnt->client->ps.origin, shotStart ) > 7000000 )
{//moving away from me at a decent speed and/or more than @2600 units away from me
VectorMA( newEnd, distAdjust, dir, predPos );
VectorSubtract( predPos, shotStart, predShotDir );
VectorNormalize( predShotDir );
dot = DotProduct( predShotDir, shotDir );
if ( dot >= 0.75f )
{//if the new aim vector is no more than 23 degrees off the original one, go ahead and adjust the aim
VectorCopy( predPos, newEnd );
}
}
}
}
VectorSubtract( newEnd, shotStart, shotDir );
VectorNormalize( shotDir );
}
#define MAX_XHAIR_DIST_ACCURACY 20000.0f
extern float g_cullDistance;
extern int BG_VehTraceFromCamPos( trace_t *camTrace, bgEntity_t *bgEnt, const vec3_t entOrg, const vec3_t shotStart, const vec3_t end, vec3_t newEnd, vec3_t shotDir, float bestDist );
qboolean WP_VehCheckTraceFromCamPos( gentity_t *ent, const vec3_t shotStart, vec3_t shotDir )
{
//FIXME: only if dynamicCrosshair and dynamicCrosshairPrecision is on!
if ( !ent
|| !ent->m_pVehicle
|| !ent->m_pVehicle->m_pVehicleInfo
|| !ent->m_pVehicle->m_pPilot//not being driven
|| !((gentity_t*)ent->m_pVehicle->m_pPilot)->client//not being driven by a client...?!!!
|| (ent->m_pVehicle->m_pPilot->s.number >= MAX_CLIENTS) )//being driven, but not by a real client, no need to worry about crosshair
{
return qfalse;
}
if ( (ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && g_cullDistance > MAX_XHAIR_DIST_ACCURACY )
|| ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
{
//FIRST: simulate the normal crosshair trace from the center of the veh straight forward
trace_t trace;
vec3_t dir, start, end;
if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
{//for some reason, the walker always draws the crosshair out from from the first muzzle point
AngleVectors( ent->client->ps.viewangles, dir, NULL, NULL );
VectorCopy( ent->r.currentOrigin, start );
start[2] += ent->m_pVehicle->m_pVehicleInfo->height-DEFAULT_MINS_2-48;
}
else
{
vec3_t ang;
if (ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER)
{
VectorSet(ang, 0.0f, ent->m_pVehicle->m_vOrientation[1], 0.0f);
}
else
{
VectorCopy(ent->m_pVehicle->m_vOrientation, ang);
}
AngleVectors( ang, dir, NULL, NULL );
VectorCopy( ent->r.currentOrigin, start );
}
VectorMA( start, g_cullDistance, dir, end );
trap_Trace( &trace, start, vec3_origin, vec3_origin, end,
ent->s.number, CONTENTS_SOLID|CONTENTS_BODY );
if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
{//just use the result of that one trace since walkers don't do the extra trace
VectorSubtract( trace.endpos, shotStart, shotDir );
VectorNormalize( shotDir );
return qtrue;
}
else
{//NOW do the trace from the camPos and compare with above trace
trace_t extraTrace;
vec3_t newEnd;
int camTraceEntNum = BG_VehTraceFromCamPos( &extraTrace, (bgEntity_t *)ent, ent->r.currentOrigin, shotStart, end, newEnd, shotDir, (trace.fraction*g_cullDistance) );
if ( camTraceEntNum )
{
WP_VehLeadCrosshairVeh( &g_entities[camTraceEntNum-1], newEnd, dir, shotStart, shotDir );
return qtrue;
}
}
}
return qfalse;
}
//---------------------------------------------------------
void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire )
//---------------------------------------------------------
{
Vehicle_t *pVeh = ent->m_pVehicle;
int muzzlesFired = 0;
gentity_t *missile = NULL;
vehWeaponInfo_t *vehWeapon = NULL;
qboolean clearRocketLockEntity = qfalse;
if ( !pVeh )
{
return;
}
if (pVeh->m_iRemovedSurfaces)
{ //can't fire when the thing is breaking apart
return;
}
if (pVeh->m_pVehicleInfo->type == VH_WALKER &&
ent->client->ps.electrifyTime > level.time)
{ //don't fire while being electrocuted
return;
}
// TODO?: If possible (probably not enough time), it would be nice if secondary fire was actually a mode switch/toggle
// so that, for instance, an x-wing can have 4-gun fire, or individual muzzle fire. If you wanted a different weapon, you
// would actually have to press the 2 key or something like that (I doubt I'd get a graphic for it anyways though). -AReis
// If this is not the alternate fire, fire a normal blaster shot...
if ( pVeh->m_pVehicleInfo &&
(pVeh->m_pVehicleInfo->type != VH_FIGHTER || (pVeh->m_ulFlags&VEH_WINGSOPEN)) ) // NOTE: Wings open also denotes that it has already launched.
{//fighters can only fire when wings are open
int weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE;
int delay = 1000;
qboolean aimCorrect = qfalse;
qboolean linkedFiring = qfalse;
if ( !alt_fire )
{
weaponNum = 0;
}
else
{
weaponNum = 1;
}
vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID;
if ( pVeh->weaponStatus[weaponNum].ammo <= 0 )
{//no ammo for this weapon
if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
{// let the client know he's out of ammo
int i;
//but only if one of the vehicle muzzles is actually ready to fire this weapon
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
{
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
{//this muzzle doesn't match the weapon we're trying to use
continue;
}
if ( pVeh->m_iMuzzleTag[i] != -1
&& pVeh->m_iMuzzleWait[i] < level.time )
{//this one would have fired, send the no ammo message
G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
break;
}
}
}
return;
}
delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay;
aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect;
if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2//always linked
|| ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable
&& pVeh->weaponStatus[weaponNum].linked ) )//linked
{//we're linking the primary or alternate weapons, so we'll do *all* the muzzles
linkedFiring = qtrue;
}
if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS )
{//invalid vehicle weapon
return;
}
else
{
int i, numMuzzles = 0, numMuzzlesReady = 0, cumulativeDelay = 0, cumulativeAmmo = 0;
qboolean sentAmmoWarning = qfalse;
vehWeapon = &g_vehWeaponInfo[vehWeaponIndex];
if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2 )
{//always linked weapons don't accumulate delay, just use specified delay
cumulativeDelay = delay;
}
//find out how many we've got for this weapon
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
{
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
{//this muzzle doesn't match the weapon we're trying to use
continue;
}
if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time )
{
numMuzzlesReady++;
}
if ( pVeh->m_pVehicleInfo->weapMuzzle[pVeh->weaponStatus[weaponNum].nextMuzzle] != vehWeaponIndex )
{//Our designated next muzzle for this weapon isn't valid for this weapon (happens when ships fire for the first time)
//set the next to this one
pVeh->weaponStatus[weaponNum].nextMuzzle = i;
}
if ( linkedFiring )
{
cumulativeAmmo += vehWeapon->iAmmoPerShot;
if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable != 2 )
{//always linked weapons don't accumulate delay, just use specified delay
cumulativeDelay += delay;
}
}
numMuzzles++;
}
if ( linkedFiring )
{//firing all muzzles at once
if ( numMuzzlesReady != numMuzzles )
{//can't fire all linked muzzles yet
return;
}
else
{//can fire all linked muzzles, check ammo
if ( pVeh->weaponStatus[weaponNum].ammo < cumulativeAmmo )
{//can't fire, not enough ammo
if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
{// let the client know he's out of ammo
G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
}
return;
}
}
}
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
{
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
{//this muzzle doesn't match the weapon we're trying to use
continue;
}
if ( !linkedFiring
&& i != pVeh->weaponStatus[weaponNum].nextMuzzle )
{//we're only firing one muzzle and this isn't it
continue;
}
// Fire this muzzle.
if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_iMuzzleWait[i] < level.time )
{
vec3_t start, dir;
if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot )
{//out of ammo!
if ( !sentAmmoWarning )
{
sentAmmoWarning = qtrue;
if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
{// let the client know he's out of ammo
G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
}
}
}
else
{//have enough ammo to shoot
//do the firing
WP_CalcVehMuzzle(ent, i);
VectorCopy( pVeh->m_vMuzzlePos[i], start );
VectorCopy( pVeh->m_vMuzzleDir[i], dir );
if ( WP_VehCheckTraceFromCamPos( ent, start, dir ) )
{//auto-aim at whatever crosshair would be over from camera's point of view (if closer)
}
else if ( aimCorrect )
{//auto-aim the missile at the crosshair if there's anything there
trace_t trace;
vec3_t end;
vec3_t ang;
vec3_t fixedDir;
if (pVeh->m_pVehicleInfo->type == VH_SPEEDER)
{
VectorSet(ang, 0.0f, pVeh->m_vOrientation[1], 0.0f);
}
else
{
VectorCopy(pVeh->m_vOrientation, ang);
}
AngleVectors( ang, fixedDir, NULL, NULL );
VectorMA( ent->r.currentOrigin, 32768, fixedDir, end );
//VectorMA( ent->r.currentOrigin, 8192, dir, end );
trap_Trace( &trace, ent->r.currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT );
if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid )
{
vec3_t newEnd;
VectorCopy( trace.endpos, newEnd );
WP_VehLeadCrosshairVeh( &g_entities[trace.entityNum], newEnd, fixedDir, start, dir );
}
}
//play the weapon's muzzle effect if we have one
//NOTE: just need MAX_VEHICLE_MUZZLES bits for this... should be cool since it's currently 12 and we're sending it in 16 bits
muzzlesFired |= (1<<i);
missile = WP_FireVehicleWeapon( ent, start, dir, vehWeapon, alt_fire, qfalse );
if ( vehWeapon->fHoming )
{//clear the rocket lock entity *after* all muzzles have fired
clearRocketLockEntity = qtrue;
}
}
if ( linkedFiring )
{//we're linking the weapon, so continue on and fire all appropriate muzzles
continue;
}
//else just firing one
//take the ammo, set the next muzzle and set the delay on it
if ( numMuzzles > 1 )
{//more than one, look for it
int nextMuzzle = pVeh->weaponStatus[weaponNum].nextMuzzle;
while ( 1 )
{
nextMuzzle++;
if ( nextMuzzle >= MAX_VEHICLE_MUZZLES )
{
nextMuzzle = 0;
}
if ( nextMuzzle == pVeh->weaponStatus[weaponNum].nextMuzzle )
{//WTF? Wrapped without finding another valid one!
break;
}
if ( pVeh->m_pVehicleInfo->weapMuzzle[nextMuzzle] == vehWeaponIndex )
{//this is the next muzzle for this weapon
pVeh->weaponStatus[weaponNum].nextMuzzle = nextMuzzle;
break;
}
}
}//else, just stay on the one we just fired
//set the delay on the next muzzle
pVeh->m_iMuzzleWait[pVeh->weaponStatus[weaponNum].nextMuzzle] = level.time + delay;
//take away the ammo
pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot;
//NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
{
((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
}
//done!
//we'll get in here again next frame and try the next muzzle...
//return;
goto tryFire;
}
}
//we went through all the muzzles, so apply the cumulative delay and ammo cost
if ( cumulativeAmmo )
{//taking ammo one shot at a time
//take the ammo
pVeh->weaponStatus[weaponNum].ammo -= cumulativeAmmo;
//NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
{
((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
}
}
if ( cumulativeDelay )
{//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
{
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
{//this muzzle doesn't match the weapon we're trying to use
continue;
}
//apply the cumulative delay
pVeh->m_iMuzzleWait[i] = level.time + cumulativeDelay;
}
}
}
}
tryFire:
if ( clearRocketLockEntity )
{//hmm, should probably clear that anytime any weapon fires?
ent->client->ps.rocketLockIndex = ENTITYNUM_NONE;
ent->client->ps.rocketLockTime = 0;
ent->client->ps.rocketTargetTime = 0;
}
if ( vehWeapon && muzzlesFired > 0 )
{
G_VehMuzzleFireFX(ent, missile, muzzlesFired );
}
}
/*
===============
FireWeapon
===============
*/
#include "../namespace_begin.h"
int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint);
#include "../namespace_end.h"
void FireWeapon( gentity_t *ent, qboolean altFire ) {
if (ent->client->ps.powerups[PW_QUAD] ) {
s_quadFactor = g_quadfactor.value;
} else {
s_quadFactor = 1;
}
// track shots taken for accuracy tracking. Grapple is not a weapon and gauntet is just not tracked
if( ent->s.weapon != WP_SABER && ent->s.weapon != WP_STUN_BATON && ent->s.weapon != WP_MELEE )
{
if( ent->s.weapon == WP_FLECHETTE ) {
ent->client->accuracy_shots += FLECHETTE_SHOTS;
} else {
ent->client->accuracy_shots++;
}
}
if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE )
{
FireVehicleWeapon( ent, altFire );
return;
}
else
{
// set aiming directions
if (ent->s.weapon == WP_EMPLACED_GUN &&
ent->client->ps.emplacedIndex)
{ //if using emplaced then base muzzle point off of gun position/angles
gentity_t *emp = &g_entities[ent->client->ps.emplacedIndex];
if (emp->inuse)
{
float yaw;
vec3_t viewAngCap;
int override;
VectorCopy(ent->client->ps.viewangles, viewAngCap);
if (viewAngCap[PITCH] > 40)
{
viewAngCap[PITCH] = 40;
}
override = BG_EmplacedView(ent->client->ps.viewangles, emp->s.angles, &yaw,
emp->s.origin2[0]);
if (override)
{
viewAngCap[YAW] = yaw;
}
AngleVectors( viewAngCap, forward, vright, up );
}
else
{
AngleVectors( ent->client->ps.viewangles, forward, vright, up );
}
}
else if (ent->s.number < MAX_CLIENTS &&
ent->client->ps.m_iVehicleNum && ent->s.weapon == WP_BLASTER)
{ //riding a vehicle...with blaster selected
vec3_t vehTurnAngles;
gentity_t *vehEnt = &g_entities[ent->client->ps.m_iVehicleNum];
if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle)
{
VectorCopy(vehEnt->m_pVehicle->m_vOrientation, vehTurnAngles);
vehTurnAngles[PITCH] = ent->client->ps.viewangles[PITCH];
}
else
{
VectorCopy(ent->client->ps.viewangles, vehTurnAngles);
}
if (ent->client->pers.cmd.rightmove > 0)
{ //shooting to right
vehTurnAngles[YAW] -= 90.0f;
}
else if (ent->client->pers.cmd.rightmove < 0)
{ //shooting to left
vehTurnAngles[YAW] += 90.0f;
}
AngleVectors( vehTurnAngles, forward, vright, up );
}
else
{
AngleVectors( ent->client->ps.viewangles, forward, vright, up );
}
CalcMuzzlePoint ( ent, forward, vright, up, muzzle );
// fire the specific weapon
switch( ent->s.weapon ) {
case WP_STUN_BATON:
WP_FireStunBaton( ent, altFire );
break;
case WP_MELEE:
WP_FireMelee(ent, altFire);
break;
case WP_SABER:
break;
case WP_BRYAR_PISTOL:
//if ( g_gametype.integer == GT_SIEGE )
if (1)
{//allow alt-fire
WP_FireBryarPistol( ent, altFire );
}
else
{
WP_FireBryarPistol( ent, qfalse );
}
break;
case WP_CONCUSSION:
if ( altFire )
{
WP_FireConcussionAlt( ent );
}
else
{
WP_FireConcussion( ent );
}
break;
case WP_BRYAR_OLD:
WP_FireBryarPistol( ent, altFire );
break;
case WP_BLASTER:
WP_FireBlaster( ent, altFire );
break;
case WP_DISRUPTOR:
WP_FireDisruptor( ent, altFire );
break;
case WP_BOWCASTER:
WP_FireBowcaster( ent, altFire );
break;
case WP_REPEATER:
WP_FireRepeater( ent, altFire );
break;
case WP_DEMP2:
WP_FireDEMP2( ent, altFire );
break;
case WP_FLECHETTE:
WP_FireFlechette( ent, altFire );
break;
case WP_ROCKET_LAUNCHER:
WP_FireRocket( ent, altFire );
break;
case WP_THERMAL:
WP_FireThermalDetonator( ent, altFire );
break;
case WP_TRIP_MINE:
WP_PlaceLaserTrap( ent, altFire );
break;
case WP_DET_PACK:
WP_DropDetPack( ent, altFire );
break;
case WP_EMPLACED_GUN:
if (ent->client && ent->client->ewebIndex)
{ //specially handled by the e-web itself
break;
}
WP_FireEmplaced( ent, altFire );
break;
default:
// assert(!"unknown weapon fire");
break;
}
}
G_LogWeaponFire(ent->s.number, ent->s.weapon);
}
//---------------------------------------------------------
static void WP_FireEmplaced( gentity_t *ent, qboolean altFire )
//---------------------------------------------------------
{
vec3_t dir, angs, gunpoint;
vec3_t right;
gentity_t *gun;
int side;
if (!ent->client)
{
return;
}
if (!ent->client->ps.emplacedIndex)
{ //shouldn't be using WP_EMPLACED_GUN if we aren't on an emplaced weapon
return;
}
gun = &g_entities[ent->client->ps.emplacedIndex];
if (!gun->inuse || gun->health <= 0)
{ //gun was removed or killed, although we should never hit this check because we should have been forced off it already
return;
}
VectorCopy(gun->s.origin, gunpoint);
gunpoint[2] += 46;
AngleVectors(ent->client->ps.viewangles, NULL, right, NULL);
if (gun->genericValue10)
{ //fire out of the right cannon side
VectorMA(gunpoint, 10.0f, right, gunpoint);
side = 0;
}
else
{ //the left
VectorMA(gunpoint, -10.0f, right, gunpoint);
side = 1;
}
gun->genericValue10 = side;
G_AddEvent(gun, EV_FIRE_WEAPON, side);
vectoangles( forward, angs );
AngleVectors( angs, dir, NULL, NULL );
WP_FireEmplacedMissile( gun, gunpoint, dir, altFire, ent );
}
#define EMPLACED_CANRESPAWN 1
//----------------------------------------------------------
/*QUAKED emplaced_gun (0 0 1) (-30 -20 8) (30 20 60) CANRESPAWN
count - if CANRESPAWN spawnflag, decides how long it is before gun respawns (in ms)
constraint - number of degrees gun is constrained from base angles on each side (default 60.0)
showhealth - set to 1 to show health bar on this entity when crosshair is over it
teamowner - crosshair shows green for this team, red for opposite team
0 - none
1 - red
2 - blue
alliedTeam - team that can use this
0 - any
1 - red
2 - blue
teamnodmg - team that turret does not take damage from or do damage to
0 - none
1 - red
2 - blue
*/
//----------------------------------------------------------
extern qboolean TryHeal(gentity_t *ent, gentity_t *target); //g_utils.c
void emplaced_gun_use( gentity_t *self, gentity_t *other, trace_t *trace )
{
vec3_t fwd1, fwd2;
float dot;
int oldWeapon;
gentity_t *activator = other;
float zoffset = 50;
vec3_t anglesToOwner;
vec3_t vLen;
float ownLen;
if ( self->health <= 0 )
{ //gun is destroyed
return;
}
if (self->activator)
{ //someone is already using me
return;
}
if (!activator->client)
{
return;
}
if (activator->client->ps.emplacedTime > level.time)
{ //last use attempt still too recent
return;
}
if (activator->client->ps.forceHandExtend != HANDEXTEND_NONE)
{ //don't use if busy doing something else
return;
}
if (activator->client->ps.origin[2] > self->s.origin[2]+zoffset-8)
{ //can't use it from the top
return;
}
if (activator->client->ps.pm_flags & PMF_DUCKED)
{ //must be standing
return;
}
if (activator->client->ps.isJediMaster)
{ //jm can't use weapons
return;
}
VectorSubtract(self->s.origin, activator->client->ps.origin, vLen);
ownLen = VectorLength(vLen);
if (ownLen > 64.0f)
{ //must be within 64 units of the gun to use at all
return;
}
// Let's get some direction vectors for the user
AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL );
// Get the guns direction vector
AngleVectors( self->pos1, fwd2, NULL, NULL );
dot = DotProduct( fwd1, fwd2 );
// Must be reasonably facing the way the gun points ( 110 degrees or so ), otherwise we don't allow to use it.
if ( dot < -0.2f )
{
goto tryHeal;
}
VectorSubtract(self->s.origin, activator->client->ps.origin, fwd1);
VectorNormalize(fwd1);
dot = DotProduct( fwd1, fwd2 );
//check the positioning in relation to the gun as well
if ( dot < 0.6f )
{
goto tryHeal;
}
self->genericValue1 = 1;
oldWeapon = activator->s.weapon;
// swap the users weapon with the emplaced gun
activator->client->ps.weapon = self->s.weapon;
activator->client->ps.weaponstate = WEAPON_READY;
activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN );
activator->client->ps.emplacedIndex = self->s.number;
self->s.emplacedOwner = activator->s.number;
self->s.activeForcePass = NUM_FORCE_POWERS+1;
// the gun will track which weapon we used to have
self->s.weapon = oldWeapon;
//user's new owner becomes the gun ent
activator->r.ownerNum = self->s.number;
self->activator = activator;
VectorSubtract(self->r.currentOrigin, activator->client->ps.origin, anglesToOwner);
vectoangles(anglesToOwner, anglesToOwner);
return;
tryHeal: //well, not in the right dir, try healing it instead...
TryHeal(activator, self);
}
void emplaced_gun_realuse( gentity_t *self, gentity_t *other, gentity_t *activator )
{
emplaced_gun_use(self, other, NULL);
}
//----------------------------------------------------------
void emplaced_gun_pain( gentity_t *self, gentity_t *attacker, int damage )
{
self->s.health = self->health;
if ( self->health <= 0 )
{
//death effect.. for now taken care of on cgame
}
else
{
//if we have a pain behavior set then use it I guess
G_ActivateBehavior( self, BSET_PAIN );
}
}
#define EMPLACED_GUN_HEALTH 800
//----------------------------------------------------------
void emplaced_gun_update(gentity_t *self)
{
vec3_t smokeOrg, puffAngle;
int oldWeap;
float ownLen = 0;
if (self->health < 1 && !self->genericValue5)
{ //we are dead, set our respawn delay if we have one
if (self->spawnflags & EMPLACED_CANRESPAWN)
{
self->genericValue5 = level.time + 4000 + self->count;
}
}
else if (self->health < 1 && self->genericValue5 < level.time)
{ //we are dead, see if it's time to respawn
self->s.time = 0;
self->genericValue4 = 0;
self->genericValue3 = 0;
self->health = EMPLACED_GUN_HEALTH*0.4;
self->s.health = self->health;
}
if (self->genericValue4 && self->genericValue4 < 2 && self->s.time < level.time)
{ //we have finished our warning (red flashing) effect, it's time to finish dying
vec3_t explOrg;
VectorSet( puffAngle, 0, 0, 1 );
VectorCopy(self->r.currentOrigin, explOrg);
explOrg[2] += 16;
//just use the detpack explosion effect
G_PlayEffect(EFFECT_EXPLOSION_DETPACK, explOrg, puffAngle);
self->genericValue3 = level.time + Q_irand(2500, 3500);
G_RadiusDamage(self->r.currentOrigin, self, self->splashDamage, self->splashRadius, self, NULL, MOD_UNKNOWN);
self->s.time = -1;
self->genericValue4 = 2;
}
if (self->genericValue3 > level.time)
{ //see if we are freshly dead and should be smoking
if (self->genericValue2 < level.time)
{ //is it time yet to spawn another smoke puff?
VectorSet( puffAngle, 0, 0, 1 );
VectorCopy(self->r.currentOrigin, smokeOrg);
smokeOrg[2] += 60;
G_PlayEffect(EFFECT_SMOKE, smokeOrg, puffAngle);
self->genericValue2 = level.time + Q_irand(250, 400);
}
}
if (self->activator && self->activator->client && self->activator->inuse)
{ //handle updating current user
vec3_t vLen;
VectorSubtract(self->s.origin, self->activator->client->ps.origin, vLen);
ownLen = VectorLength(vLen);
if (!(self->activator->client->pers.cmd.buttons & BUTTON_USE) && self->genericValue1)
{
self->genericValue1 = 0;
}
if ((self->activator->client->pers.cmd.buttons & BUTTON_USE) && !self->genericValue1)
{
self->activator->client->ps.emplacedIndex = 0;
self->activator->client->ps.saberHolstered = 0;
self->nextthink = level.time + 50;
return;
}
}
if ((self->activator && self->activator->client) &&
(!self->activator->inuse || self->activator->client->ps.emplacedIndex != self->s.number || self->genericValue4 || ownLen > 64))
{ //get the user off of me then
self->activator->client->ps.stats[STAT_WEAPONS] &= ~(1<<WP_EMPLACED_GUN);
oldWeap = self->activator->client->ps.weapon;
self->activator->client->ps.weapon = self->s.weapon;
self->s.weapon = oldWeap;
self->activator->r.ownerNum = ENTITYNUM_NONE;
self->activator->client->ps.emplacedTime = level.time + 1000;
self->activator->client->ps.emplacedIndex = 0;
self->activator->client->ps.saberHolstered = 0;
self->activator = NULL;
self->s.activeForcePass = 0;
}
else if (self->activator && self->activator->client)
{ //make sure the user is using the emplaced gun weapon
self->activator->client->ps.weapon = WP_EMPLACED_GUN;
self->activator->client->ps.weaponstate = WEAPON_READY;
}
self->nextthink = level.time + 50;
}
//----------------------------------------------------------
void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
{ //set us up to flash and then explode
if (self->genericValue4)
{
return;
}
self->genericValue4 = 1;
self->s.time = level.time + 3000;
self->genericValue5 = 0;
}
void SP_emplaced_gun( gentity_t *ent )
{
const char *name = "models/map_objects/mp/turret_chair.glm";
vec3_t down;
trace_t tr;
//make sure our assets are precached
RegisterItem( BG_FindItemForWeapon(WP_EMPLACED_GUN) );
ent->r.contents = CONTENTS_SOLID;
ent->s.solid = SOLID_BBOX;
ent->genericValue5 = 0;
VectorSet( ent->r.mins, -30, -20, 8 );
VectorSet( ent->r.maxs, 30, 20, 60 );
VectorCopy(ent->s.origin, down);
down[2] -= 1024;
trap_Trace(&tr, ent->s.origin, ent->r.mins, ent->r.maxs, down, ent->s.number, MASK_SOLID);
if (tr.fraction != 1 && !tr.allsolid && !tr.startsolid)
{
VectorCopy(tr.endpos, ent->s.origin);
}
ent->spawnflags |= 4; // deadsolid
ent->health = EMPLACED_GUN_HEALTH;
if (ent->spawnflags & EMPLACED_CANRESPAWN)
{ //make it somewhat easier to kill if it can respawn
ent->health *= 0.4;
}
ent->maxHealth = ent->health;
G_ScaleNetHealth(ent);
ent->genericValue4 = 0;
ent->takedamage = qtrue;
ent->pain = emplaced_gun_pain;
ent->die = emplaced_gun_die;
// being caught in this thing when it blows would be really bad.
ent->splashDamage = 80;
ent->splashRadius = 128;
// amount of ammo that this little poochie has
G_SpawnInt( "count", "600", &ent->count );
G_SpawnFloat( "constraint", "60", &ent->s.origin2[0] );
ent->s.modelindex = G_ModelIndex( (char *)name );
ent->s.modelGhoul2 = 1;
ent->s.g2radius = 110;
//so the cgame knows for sure that we're an emplaced weapon
ent->s.weapon = WP_EMPLACED_GUN;
G_SetOrigin( ent, ent->s.origin );
// store base angles for later
VectorCopy( ent->s.angles, ent->pos1 );
VectorCopy( ent->s.angles, ent->r.currentAngles );
VectorCopy( ent->s.angles, ent->s.apos.trBase );
ent->think = emplaced_gun_update;
ent->nextthink = level.time + 50;
ent->use = emplaced_gun_realuse;
ent->r.svFlags |= SVF_PLAYER_USABLE;
ent->s.pos.trType = TR_STATIONARY;
ent->s.owner = MAX_CLIENTS+1;
ent->s.shouldtarget = qtrue;
//ent->s.teamowner = 0;
trap_LinkEntity(ent);
}