mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-26 14:11:20 +00:00
5027 lines
137 KiB
C
5027 lines
137 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);
|
|
}
|