mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-25 13:41:34 +00:00
5357 lines
150 KiB
C++
5357 lines
150 KiB
C++
// g_weapon.c
|
|
// perform the server side effects of a weapon firing
|
|
|
|
// leave this line at the top for all g_xxxx.cpp files...
|
|
#include "g_headers.h"
|
|
|
|
|
|
|
|
#include "g_local.h"
|
|
#include "g_functions.h"
|
|
#include "anims.h"
|
|
#include "b_local.h"
|
|
#include "wp_saber.h"
|
|
#include "g_vehicles.h"
|
|
|
|
static vec3_t forward, vright, up;
|
|
static vec3_t muzzle;
|
|
|
|
void drop_charge(gentity_t *ent, vec3_t start, vec3_t dir);
|
|
void ViewHeightFix( const gentity_t * const ent );
|
|
qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker );
|
|
extern qboolean G_BoxInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs, const vec3_t boundsMins, const vec3_t boundsMaxs );
|
|
extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc );
|
|
extern qboolean PM_DroidMelee( int npc_class );
|
|
extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
|
|
extern qboolean G_HasKnockdownAnims( gentity_t *ent );
|
|
|
|
static gentity_t *ent_list[MAX_GENTITIES];
|
|
extern cvar_t *g_debugMelee;
|
|
|
|
// Bryar Pistol
|
|
//--------
|
|
#define BRYAR_PISTOL_VEL 1800
|
|
#define BRYAR_PISTOL_DAMAGE 14
|
|
#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
|
|
|
|
// E11 Blaster
|
|
//---------
|
|
#define BLASTER_MAIN_SPREAD 0.5f
|
|
#define BLASTER_ALT_SPREAD 1.5f
|
|
#define BLASTER_NPC_SPREAD 0.5f
|
|
#define BLASTER_VELOCITY 2300
|
|
#define BLASTER_NPC_VEL_CUT 0.5f
|
|
#define BLASTER_NPC_HARD_VEL_CUT 0.7f
|
|
#define BLASTER_DAMAGE 20
|
|
#define BLASTER_NPC_DAMAGE_EASY 6
|
|
#define BLASTER_NPC_DAMAGE_NORMAL 12 // 14
|
|
#define BLASTER_NPC_DAMAGE_HARD 16 // 18
|
|
|
|
// Tenloss Disruptor
|
|
//----------
|
|
#define DISRUPTOR_MAIN_DAMAGE 14
|
|
#define DISRUPTOR_NPC_MAIN_DAMAGE_EASY 5
|
|
#define DISRUPTOR_NPC_MAIN_DAMAGE_MEDIUM 10
|
|
#define DISRUPTOR_NPC_MAIN_DAMAGE_HARD 15
|
|
|
|
#define DISRUPTOR_ALT_DAMAGE 12
|
|
#define DISRUPTOR_NPC_ALT_DAMAGE_EASY 15
|
|
#define DISRUPTOR_NPC_ALT_DAMAGE_MEDIUM 25
|
|
#define DISRUPTOR_NPC_ALT_DAMAGE_HARD 30
|
|
#define DISRUPTOR_ALT_TRACES 3 // can go through a max of 3 entities
|
|
#define DISRUPTOR_CHARGE_UNIT 150.0f // distruptor charging gives us one more unit every 150ms--if you change this, you'll have to do the same in bg_pmove
|
|
|
|
// Wookie Bowcaster
|
|
//----------
|
|
#define BOWCASTER_DAMAGE 45
|
|
#define BOWCASTER_VELOCITY 1300
|
|
#define BOWCASTER_NPC_DAMAGE_EASY 12
|
|
#define BOWCASTER_NPC_DAMAGE_NORMAL 24
|
|
#define BOWCASTER_NPC_DAMAGE_HARD 36
|
|
#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_NPC_SPREAD 0.7f
|
|
#define REPEATER_DAMAGE 8
|
|
#define REPEATER_VELOCITY 1600
|
|
#define REPEATER_NPC_DAMAGE_EASY 2
|
|
#define REPEATER_NPC_DAMAGE_NORMAL 4
|
|
#define REPEATER_NPC_DAMAGE_HARD 6
|
|
|
|
#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_VELOCITY 1100
|
|
#define REPEATER_ALT_NPC_DAMAGE_EASY 15
|
|
#define REPEATER_ALT_NPC_DAMAGE_NORMAL 30
|
|
#define REPEATER_ALT_NPC_DAMAGE_HARD 45
|
|
|
|
// DEMP2
|
|
//----------
|
|
#define DEMP2_DAMAGE 15
|
|
#define DEMP2_VELOCITY 1800
|
|
#define DEMP2_NPC_DAMAGE_EASY 6
|
|
#define DEMP2_NPC_DAMAGE_NORMAL 12
|
|
#define DEMP2_NPC_DAMAGE_HARD 18
|
|
#define DEMP2_SIZE 2 // half of bbox size
|
|
|
|
#define DEMP2_ALT_DAMAGE 15
|
|
#define DEMP2_CHARGE_UNIT 500.0f // demp2 charging gives us one more unit every 500ms--if you change this, you'll have to do the same in bg_pmove
|
|
#define DEMP2_ALT_RANGE 4096
|
|
#define DEMP2_ALT_SPLASHRADIUS 256
|
|
|
|
// Golan Arms Flechette
|
|
//---------
|
|
#define FLECHETTE_SHOTS 6
|
|
#define FLECHETTE_SPREAD 4.0f
|
|
#define FLECHETTE_DAMAGE 15
|
|
#define FLECHETTE_VEL 3500
|
|
#define FLECHETTE_SIZE 1
|
|
|
|
#define FLECHETTE_ALT_DAMAGE 20
|
|
#define FLECHETTE_ALT_SPLASH_DAM 20
|
|
#define FLECHETTE_ALT_SPLASH_RAD 128
|
|
|
|
// NOT CURRENTLY USED
|
|
#define FLECHETTE_MINE_RADIUS_CHECK 200
|
|
#define FLECHETTE_MINE_VEL 1000
|
|
#define FLECHETTE_MINE_DAMAGE 100
|
|
#define FLECHETTE_MINE_SPLASH_DAMAGE 200
|
|
#define FLECHETTE_MINE_SPLASH_RADIUS 200
|
|
|
|
// Personal Rocket Launcher
|
|
//---------
|
|
#define ROCKET_VELOCITY 900
|
|
#define ROCKET_DAMAGE 100
|
|
#define ROCKET_SPLASH_DAMAGE 100
|
|
#define ROCKET_SPLASH_RADIUS 160
|
|
#define ROCKET_NPC_DAMAGE_EASY 20
|
|
#define ROCKET_NPC_DAMAGE_NORMAL 40
|
|
#define ROCKET_NPC_DAMAGE_HARD 60
|
|
#define ROCKET_SIZE 3
|
|
|
|
#define ROCKET_ALT_VELOCITY (ROCKET_VELOCITY*0.5)
|
|
#define ROCKET_ALT_THINK_TIME 100
|
|
|
|
// some naughty little things that are used cg side
|
|
int g_rocketLockEntNum = ENTITYNUM_NONE;
|
|
int g_rocketLockTime = 0;
|
|
int g_rocketSlackTime = 0;
|
|
|
|
// Concussion Rifle
|
|
//---------
|
|
//primary
|
|
#define CONC_VELOCITY 3000
|
|
#define CONC_DAMAGE 150
|
|
#define CONC_NPC_SPREAD 0.7f
|
|
#define CONC_NPC_DAMAGE_EASY 15
|
|
#define CONC_NPC_DAMAGE_NORMAL 30
|
|
#define CONC_NPC_DAMAGE_HARD 50
|
|
#define CONC_SPLASH_DAMAGE 50
|
|
#define CONC_SPLASH_RADIUS 300
|
|
//alt
|
|
#define CONC_ALT_DAMAGE 225//100
|
|
#define CONC_ALT_NPC_DAMAGE_EASY 10
|
|
#define CONC_ALT_NPC_DAMAGE_MEDIUM 20
|
|
#define CONC_ALT_NPC_DAMAGE_HARD 30
|
|
|
|
// Emplaced Gun
|
|
//--------------
|
|
#define EMPLACED_VEL 6000 // very fast
|
|
#define EMPLACED_DAMAGE 150 // and very damaging
|
|
#define EMPLACED_SIZE 5 // make it easier to hit things
|
|
|
|
// 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
|
|
|
|
// Stun Baton
|
|
//--------------
|
|
#define STUN_BATON_DAMAGE 22
|
|
#define STUN_BATON_ALT_DAMAGE 22
|
|
#define STUN_BATON_RANGE 25
|
|
|
|
// Laser Trip Mine
|
|
//--------------
|
|
#define LT_DAMAGE 150
|
|
#define LT_SPLASH_RAD 256.0f
|
|
#define LT_SPLASH_DAM 90
|
|
|
|
#define LT_VELOCITY 250.0f
|
|
#define LT_ALT_VELOCITY 1000.0f
|
|
|
|
#define PROX_MINE_RADIUS_CHECK 190
|
|
|
|
#define LT_SIZE 3.0f
|
|
#define LT_ALT_TIME 2000
|
|
#define LT_ACTIVATION_DELAY 1000
|
|
#define LT_DELAY_TIME 50
|
|
|
|
// Thermal Detonator
|
|
//--------------
|
|
#define TD_DAMAGE 100
|
|
#define TD_NPC_DAMAGE_CUT 0.6f // NPC thrown dets deliver only 60% of the damage that a player thrown one does
|
|
#define TD_SPLASH_RAD 128
|
|
#define TD_SPLASH_DAM 90
|
|
#define TD_VELOCITY 900
|
|
#define TD_MIN_CHARGE 0.15f
|
|
#define TD_TIME 4000
|
|
#define TD_THINK_TIME 300 // don't think too often?
|
|
#define TD_TEST_RAD (TD_SPLASH_RAD * 0.8f) // no sense in auto-blowing up if exactly on the radius edge--it would hardly do any damage
|
|
#define TD_ALT_TIME 3000
|
|
|
|
#define TD_ALT_DAMAGE 100
|
|
#define TD_ALT_SPLASH_RAD 128
|
|
#define TD_ALT_SPLASH_DAM 90
|
|
#define TD_ALT_VELOCITY 600
|
|
#define TD_ALT_MIN_CHARGE 0.15f
|
|
#define TD_ALT_TIME 3000
|
|
|
|
// Tusken Rifle Shot
|
|
//--------------
|
|
#define TUSKEN_RIFLE_VEL 3000 // fast
|
|
#define TUSKEN_RIFLE_DAMAGE_EASY 20 // damaging
|
|
#define TUSKEN_RIFLE_DAMAGE_MEDIUM 30 // very damaging
|
|
#define TUSKEN_RIFLE_DAMAGE_HARD 50 // extremely damaging
|
|
|
|
// Weapon Helper Functions
|
|
float weaponSpeed[WP_NUM_WEAPONS][2] =
|
|
{
|
|
0,0,//WP_NONE,
|
|
0,0,//WP_SABER, // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste.
|
|
BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL,//WP_BLASTER_PISTOL,
|
|
BLASTER_VELOCITY,BLASTER_VELOCITY,//WP_BLASTER,
|
|
Q3_INFINITE,Q3_INFINITE,//WP_DISRUPTOR,
|
|
BOWCASTER_VELOCITY,BOWCASTER_VELOCITY,//WP_BOWCASTER,
|
|
REPEATER_VELOCITY,REPEATER_ALT_VELOCITY,//WP_REPEATER,
|
|
DEMP2_VELOCITY,DEMP2_ALT_RANGE,//WP_DEMP2,
|
|
FLECHETTE_VEL,FLECHETTE_MINE_VEL,//WP_FLECHETTE,
|
|
ROCKET_VELOCITY,ROCKET_ALT_VELOCITY,//WP_ROCKET_LAUNCHER,
|
|
TD_VELOCITY,TD_ALT_VELOCITY,//WP_THERMAL,
|
|
0,0,//WP_TRIP_MINE,
|
|
0,0,//WP_DET_PACK,
|
|
CONC_VELOCITY,Q3_INFINITE,//WP_CONCUSSION,
|
|
0,0,//WP_MELEE, // Any ol' melee attack
|
|
0,0,//WP_STUN_BATON,
|
|
BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL,//WP_BRYAR_PISTOL,
|
|
EMPLACED_VEL,EMPLACED_VEL,//WP_EMPLACED_GUN,
|
|
BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL,//WP_BOT_LASER, // Probe droid - Laser blast
|
|
0,0,//WP_TURRET, // turret guns
|
|
ATST_MAIN_VEL,ATST_MAIN_VEL,//WP_ATST_MAIN,
|
|
ATST_SIDE_MAIN_VELOCITY,ATST_SIDE_ALT_NPC_VELOCITY,//WP_ATST_SIDE,
|
|
EMPLACED_VEL,EMPLACED_VEL,//WP_TIE_FIGHTER,
|
|
EMPLACED_VEL,REPEATER_ALT_VELOCITY,//WP_RAPID_FIRE_CONC,
|
|
0,0,//WP_JAWA,
|
|
TUSKEN_RIFLE_VEL,TUSKEN_RIFLE_VEL,//WP_TUSKEN_RIFLE,
|
|
0,0,//WP_TUSKEN_STAFF,
|
|
0,0,//WP_SCEPTER,
|
|
0,0,//WP_NOGHRI_STICK,
|
|
};
|
|
|
|
float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire )
|
|
{
|
|
if ( alt_fire )
|
|
{
|
|
return weaponSpeed[wp][1];
|
|
}
|
|
return weaponSpeed[wp][0];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void WP_TraceSetStart( const gentity_t *ent, vec3_t start, const vec3_t mins, const vec3_t maxs )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
//make sure our start point isn't on the other side of a wall
|
|
trace_t tr;
|
|
vec3_t entMins, newstart;
|
|
vec3_t entMaxs;
|
|
|
|
VectorSet( entMaxs, 5, 5, 5 );
|
|
VectorScale( entMaxs, -1, entMins );
|
|
|
|
if ( !ent->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy( ent->currentOrigin, newstart );
|
|
newstart[2] = start[2]; // force newstart to be on the same plane as the muzzle ( start )
|
|
|
|
gi.trace( &tr, newstart, entMins, entMaxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP );
|
|
|
|
if ( tr.startsolid || tr.allsolid )
|
|
{
|
|
// there is a problem here..
|
|
return;
|
|
}
|
|
|
|
if ( tr.fraction < 1.0f )
|
|
{
|
|
VectorCopy( tr.endpos, start );
|
|
}
|
|
}
|
|
|
|
extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
|
|
//-----------------------------------------------------------------------------
|
|
gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
gentity_t *missile;
|
|
|
|
missile = G_Spawn();
|
|
|
|
missile->nextthink = level.time + life;
|
|
missile->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
missile->s.eType = ET_MISSILE;
|
|
missile->owner = owner;
|
|
|
|
Vehicle_t* pVeh = G_IsRidingVehicle(owner);
|
|
|
|
missile->alt_fire = altFire;
|
|
|
|
missile->s.pos.trType = TR_LINEAR;
|
|
missile->s.pos.trTime = level.time;// - 10; // move a bit on the very first frame
|
|
VectorCopy( org, missile->s.pos.trBase );
|
|
VectorScale( dir, vel, missile->s.pos.trDelta );
|
|
if (pVeh)
|
|
{
|
|
missile->s.eFlags |= EF_USE_ANGLEDELTA;
|
|
vectoangles(missile->s.pos.trDelta, missile->s.angles);
|
|
VectorMA(missile->s.pos.trDelta, 2.0f, pVeh->m_pParentEntity->client->ps.velocity, missile->s.pos.trDelta);
|
|
}
|
|
|
|
VectorCopy( org, missile->currentOrigin);
|
|
gi.linkentity( missile );
|
|
|
|
return missile;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void WP_Stick( gentity_t *missile, trace_t *trace, float fudge_distance = 0.0f )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
vec3_t org, ang;
|
|
|
|
// not moving or rotating
|
|
missile->s.pos.trType = TR_STATIONARY;
|
|
VectorClear( missile->s.pos.trDelta );
|
|
VectorClear( missile->s.apos.trDelta );
|
|
|
|
// so we don't stick into the wall
|
|
VectorMA( trace->endpos, fudge_distance, trace->plane.normal, org );
|
|
G_SetOrigin( missile, org );
|
|
|
|
vectoangles( trace->plane.normal, ang );
|
|
G_SetAngles( missile, ang );
|
|
|
|
// I guess explode death wants me as the normal?
|
|
// VectorCopy( trace->plane.normal, missile->pos1 );
|
|
gi.linkentity( missile );
|
|
}
|
|
|
|
// This version shares is in the thinkFunc format
|
|
//-----------------------------------------------------------------------------
|
|
void WP_Explode( gentity_t *self )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
gentity_t *attacker = self;
|
|
vec3_t forward={0,0,1};
|
|
|
|
// stop chain reaction runaway loops
|
|
self->takedamage = qfalse;
|
|
|
|
self->s.loopSound = 0;
|
|
|
|
// VectorCopy( self->currentOrigin, self->s.pos.trBase );
|
|
if ( !self->client )
|
|
{
|
|
AngleVectors( self->s.angles, forward, NULL, NULL );
|
|
}
|
|
|
|
if ( self->fxID > 0 )
|
|
{
|
|
G_PlayEffect( self->fxID, self->currentOrigin, forward );
|
|
}
|
|
|
|
if ( self->owner )
|
|
{
|
|
attacker = self->owner;
|
|
}
|
|
else if ( self->activator )
|
|
{
|
|
attacker = self->activator;
|
|
}
|
|
|
|
if ( self->splashDamage > 0 && self->splashRadius > 0 )
|
|
{
|
|
G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, 0/*don't ignore attacker*/, MOD_EXPLOSIVE_SPLASH );
|
|
}
|
|
|
|
if ( self->target )
|
|
{
|
|
G_UseTargets( self, attacker );
|
|
}
|
|
|
|
G_SetOrigin( self, self->currentOrigin );
|
|
|
|
self->nextthink = level.time + 50;
|
|
self->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
}
|
|
|
|
// We need to have a dieFunc, otherwise G_Damage won't actually make us die. I could modify G_Damage, but that entails too many changes
|
|
//-----------------------------------------------------------------------------
|
|
void WP_ExplosiveDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
self->enemy = attacker;
|
|
|
|
if ( attacker && !attacker->s.number )
|
|
{
|
|
// less damage when shot by player
|
|
self->splashDamage /= 3;
|
|
self->splashRadius /= 3;
|
|
}
|
|
|
|
self->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead
|
|
|
|
WP_Explode( self );
|
|
}
|
|
|
|
|
|
/*
|
|
----------------------------------------------
|
|
PLAYER ITEMS
|
|
----------------------------------------------
|
|
*/
|
|
/*
|
|
#define SEEKER_RADIUS 500
|
|
|
|
gentity_t *SeekerAcquiresTarget ( gentity_t *ent, vec3_t pos )
|
|
{
|
|
vec3_t seekerPos;
|
|
float angle;
|
|
gentity_t *entityList[MAX_GENTITIES]; // targets within inital radius
|
|
gentity_t *visibleTargets[MAX_GENTITIES]; // final filtered target list
|
|
int numListedEntities;
|
|
int i, e;
|
|
gentity_t *target;
|
|
vec3_t mins, maxs;
|
|
|
|
angle = cg.time * 0.004f;
|
|
|
|
// must match cg_effects ( CG_Seeker ) & g_weapon ( SeekerAcquiresTarget ) & cg_weapons ( CG_FireSeeker )
|
|
seekerPos[0] = ent->currentOrigin[0] + 18 * cos(angle);
|
|
seekerPos[1] = ent->currentOrigin[1] + 18 * sin(angle);
|
|
seekerPos[2] = ent->currentOrigin[2] + ent->client->ps.viewheight + 8 + (3*cos(level.time*0.001f));
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = seekerPos[i] - SEEKER_RADIUS;
|
|
maxs[i] = seekerPos[i] + SEEKER_RADIUS;
|
|
}
|
|
|
|
// get potential targets within radius
|
|
numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
|
|
i = 0; // reset counter
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
target = entityList[e];
|
|
|
|
// seeker owner not a valid target
|
|
if ( target == ent )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// only players are valid targets
|
|
if ( !target->client )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// teammates not valid targets
|
|
if ( OnSameTeam( ent, target ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// don't shoot at dead things
|
|
if ( target->health <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( CanDamage( target, seekerPos ) ) // visible target, so add it to the list
|
|
{
|
|
visibleTargets[i++] = entityList[e];
|
|
}
|
|
}
|
|
|
|
if ( i )
|
|
{
|
|
// ok, now we know there are i visible targets. Pick one as the seeker's target
|
|
target = visibleTargets[Q_irand(0,i-1)];
|
|
VectorCopy( seekerPos, pos );
|
|
return target;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire );
|
|
void FireSeeker( gentity_t *owner, gentity_t *target, vec3_t origin, vec3_t dir )
|
|
{
|
|
VectorSubtract( target->currentOrigin, origin, dir );
|
|
VectorNormalize( dir );
|
|
|
|
// for now I'm just using the scavenger bullet.
|
|
WP_FireBlasterMissile( owner, origin, dir, qfalse );
|
|
}
|
|
*/
|
|
|
|
|
|
#ifdef _XBOX // Auto-aim
|
|
|
|
static float VectorDistanceSquared(vec3_t p1, vec3_t p2)
|
|
{
|
|
vec3_t dir;
|
|
VectorSubtract(p2, p1, dir);
|
|
return VectorLengthSquared(dir);
|
|
}
|
|
|
|
int WP_FindClosestBodyPart(gentity_t *ent, gentity_t *other, vec3_t point, vec3_t out, int c = 0)
|
|
{
|
|
int shortestLen = 509999;
|
|
char where = -1;
|
|
int len;
|
|
renderInfo_t *ri = NULL;
|
|
|
|
ri = &ent->client->renderInfo;
|
|
|
|
if (ent->client)
|
|
{
|
|
if (c > 0)
|
|
{
|
|
where = c - 1; // Fail safe, set to torso
|
|
}
|
|
else
|
|
{
|
|
len = VectorDistanceSquared(point, ri->eyePoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 0;
|
|
}
|
|
|
|
len = VectorDistanceSquared(point, ri->headPoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 1;
|
|
}
|
|
|
|
len = VectorDistanceSquared(point, ri->handRPoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 2;
|
|
}
|
|
|
|
len = VectorDistanceSquared(point, ri->handLPoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 3;
|
|
}
|
|
|
|
len = VectorDistanceSquared(point, ri->crotchPoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 4;
|
|
}
|
|
|
|
len = VectorDistanceSquared(point, ri->footRPoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 5;
|
|
}
|
|
|
|
len = VectorDistanceSquared(point, ri->footLPoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 6;
|
|
}
|
|
|
|
len = VectorDistanceSquared(point, ri->torsoPoint);
|
|
if (len < shortestLen) {
|
|
shortestLen = len; where = 7;
|
|
}
|
|
}
|
|
|
|
if (where < 2 && c == 0)
|
|
{
|
|
if (random() < .75f) // 25% chance to actualy hit the head or eye
|
|
where = 7;
|
|
}
|
|
|
|
switch (where)
|
|
{
|
|
case 0:
|
|
VectorCopy(ri->eyePoint, out);
|
|
break;
|
|
case 1:
|
|
VectorCopy(ri->headPoint, out);
|
|
break;
|
|
case 2:
|
|
VectorCopy(ri->handRPoint, out);
|
|
break;
|
|
case 3:
|
|
VectorCopy(ri->handLPoint, out);
|
|
break;
|
|
case 4:
|
|
VectorCopy(ri->crotchPoint, out);
|
|
break;
|
|
case 5:
|
|
VectorCopy(ri->footRPoint, out);
|
|
break;
|
|
case 6:
|
|
VectorCopy(ri->footLPoint, out);
|
|
break;
|
|
case 7:
|
|
VectorCopy(ri->torsoPoint, out);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(ent->s.pos.trBase, out);
|
|
// Really bad hack
|
|
if (strcmp(ent->classname, "misc_turret") == 0)
|
|
{
|
|
out[2] = point[2];
|
|
}
|
|
|
|
}
|
|
|
|
if (ent && ent->client && ent->client->NPC_class == CLASS_MINEMONSTER)
|
|
{
|
|
out[2] -= 24; // not a clue???
|
|
return shortestLen; // mine critters are too small to randomize
|
|
}
|
|
|
|
if (ent->NPC_type && !Q_stricmp(ent->NPC_type, "atst"))
|
|
{
|
|
// Dont randomize those atst's they have some pretty small legs
|
|
return shortestLen;
|
|
}
|
|
|
|
if (c == 0)
|
|
{
|
|
// Add a bit of chance to the actual location
|
|
float r = random() * 8.0f - 4.0f;
|
|
float r2 = random() * 8.0f - 4.0f;
|
|
float r3 = random() * 10.0f - 5.0f;
|
|
|
|
out[0] += r;
|
|
out[1] += r2;
|
|
out[2] += r3;
|
|
}
|
|
|
|
return shortestLen;
|
|
}
|
|
#endif // Auto-aim
|
|
|
|
//extern cvar_t *cv_autoAim;
|
|
#ifdef _XBOX // Auto-aim
|
|
static bool cv_autoAim = qtrue;
|
|
#endif // Auto-aim
|
|
|
|
bool WP_MissileTargetHint(gentity_t* shooter, vec3_t start, vec3_t out)
|
|
{
|
|
#ifdef _XBOX
|
|
extern short cg_crossHairStatus;
|
|
extern int g_crosshairEntNum;
|
|
// int allow = 0;
|
|
// allow = Cvar_VariableIntegerValue("cv_autoAim");
|
|
|
|
// if ((!cg.snap) || !allow ) return false;
|
|
if ((!cg.snap) || !cv_autoAim ) return false;
|
|
if (shooter->s.clientNum != 0) return false; // assuming shooter must be client, using 0 for cg_entities[0] a few lines down if you change this
|
|
// if (cg_crossHairStatus != 1 || cg_crosshairEntNum < 0 || cg_crosshairEntNum >= ENTITYNUM_WORLD) return false;
|
|
if (cg_crossHairStatus != 1 || g_crosshairEntNum < 0 || g_crosshairEntNum >= ENTITYNUM_WORLD) return false;
|
|
|
|
gentity_t* traceEnt = &g_entities[g_crosshairEntNum];
|
|
|
|
vec3_t d_f, d_rt, d_up;
|
|
vec3_t end;
|
|
trace_t trace;
|
|
|
|
// Calculate the end point
|
|
AngleVectors( cg_entities[0].lerpAngles, d_f, d_rt, d_up );
|
|
VectorMA( start, 8192, d_f, end );//4028 is max for mind trick
|
|
|
|
// This will get a detailed trace
|
|
gi.trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_COLLIDE, 10);
|
|
// If the trace came up with a different entity then our crosshair, then you are not actualy over the enemy
|
|
if (trace.entityNum != g_crosshairEntNum)
|
|
{
|
|
// Must trace again to find out where the crosshair will end up
|
|
gi.trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_NOCOLLIDE, 10 );
|
|
|
|
// Find the closest body part to the trace
|
|
WP_FindClosestBodyPart(traceEnt, shooter, trace.endpos, out);
|
|
|
|
// Compute the direction vector between the shooter and the guy being shot
|
|
VectorSubtract(out, start, out);
|
|
VectorNormalize(out);
|
|
|
|
for (int i = 1; i < 8; i++) /// do this 7 times to make sure we get it
|
|
{
|
|
/// Where will this direction end up?
|
|
VectorMA( start, 8192, out, end );//4028 is max for mind trick
|
|
|
|
// Try it one more time, ??? are we trying to shoot through solid space??
|
|
gi.trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_COLLIDE, 10);
|
|
if (trace.entityNum != g_crosshairEntNum)
|
|
{
|
|
// Find the closest body part to the trace
|
|
WP_FindClosestBodyPart(traceEnt, shooter, trace.endpos, out, i);
|
|
|
|
// Computer the direction vector between the shooter and the guy being shot
|
|
VectorSubtract(out, start, out);
|
|
VectorNormalize(out);
|
|
}
|
|
else
|
|
{
|
|
break; /// a hit wahoo
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
#else // Auto-aim
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
----------------------------------------------
|
|
PLAYER WEAPONS
|
|
----------------------------------------------
|
|
*/
|
|
|
|
//---------------
|
|
// Bryar Pistol
|
|
//---------------
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireBryarPistol( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
int damage = BRYAR_PISTOL_DAMAGE;
|
|
|
|
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
|
|
|
|
if ( !(ent->client->ps.forcePowersActive&(1<<FP_SEE))
|
|
|| ent->client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 )
|
|
{//force sight 2+ gives perfect aim
|
|
//FIXME: maybe force sight level 3 autoaims some?
|
|
if ( ent->NPC && ent->NPC->currentAim < 5 )
|
|
{
|
|
vec3_t angs;
|
|
|
|
vectoangles( forward, angs );
|
|
|
|
if ( ent->client->NPC_class == CLASS_IMPWORKER )
|
|
{//*sigh*, hack to make impworkers less accurate without affecteing imperial officer accuracy
|
|
angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
}
|
|
else
|
|
{
|
|
angs[PITCH] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) );
|
|
angs[YAW] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) );
|
|
}
|
|
|
|
AngleVectors( angs, forward, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
|
|
gentity_t *missile = CreateMissile( start, forward, BRYAR_PISTOL_VEL, 10000, ent, alt_fire );
|
|
|
|
missile->classname = "bryar_proj";
|
|
if ( ent->s.weapon == WP_BLASTER_PISTOL
|
|
|| ent->s.weapon == WP_JAWA )
|
|
{//*SIGH*... I hate our weapon system...
|
|
missile->s.weapon = ent->s.weapon;
|
|
}
|
|
else
|
|
{
|
|
missile->s.weapon = WP_BRYAR_PISTOL;
|
|
}
|
|
|
|
if ( alt_fire )
|
|
{
|
|
int count = ( level.time - ent->client->ps.weaponChargeTime ) / BRYAR_CHARGE_UNIT;
|
|
|
|
if ( count < 1 )
|
|
{
|
|
count = 1;
|
|
}
|
|
else if ( count > 5 )
|
|
{
|
|
count = 5;
|
|
}
|
|
|
|
damage *= count;
|
|
missile->count = count; // this will get used in the projectile rendering code to make a beefier effect
|
|
}
|
|
|
|
// 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
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// damage *= 2;
|
|
// }
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
|
|
if ( alt_fire )
|
|
{
|
|
missile->methodOfDeath = MOD_BRYAR_ALT;
|
|
}
|
|
else
|
|
{
|
|
missile->methodOfDeath = MOD_BRYAR;
|
|
}
|
|
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
// we don't want it to bounce forever
|
|
missile->bounceCount = 8;
|
|
|
|
if ( ent->weaponModel[1] > 0 )
|
|
{//dual pistols, toggle the muzzle point back and forth between the two pistols each time he fires
|
|
ent->count = (ent->count)?0:1;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------
|
|
// Blaster
|
|
//---------------
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire )
|
|
//---------------------------------------------------------
|
|
{
|
|
int velocity = BLASTER_VELOCITY;
|
|
int damage = BLASTER_DAMAGE;
|
|
|
|
if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE )
|
|
{
|
|
damage *= 3;
|
|
velocity = ATST_MAIN_VEL + ent->client->ps.speed;
|
|
}
|
|
else
|
|
{
|
|
// If an enemy is shooting at us, lower the velocity so you have a chance to evade
|
|
if ( ent->client && ent->client->ps.clientNum != 0 && ent->client->NPC_class != CLASS_BOBAFETT )
|
|
{
|
|
if ( g_spskill->integer < 2 )
|
|
{
|
|
velocity *= BLASTER_NPC_VEL_CUT;
|
|
}
|
|
else
|
|
{
|
|
velocity *= BLASTER_NPC_HARD_VEL_CUT;
|
|
}
|
|
}
|
|
}
|
|
|
|
WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
|
|
|
|
WP_MissileTargetHint(ent, start, dir);
|
|
|
|
gentity_t *missile = CreateMissile( start, dir, velocity, 10000, ent, altFire );
|
|
|
|
missile->classname = "blaster_proj";
|
|
missile->s.weapon = WP_BLASTER;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 && ent->client->NPC_class != CLASS_BOBAFETT )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = BLASTER_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = BLASTER_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = BLASTER_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
// if ( ent->client )
|
|
// {
|
|
// if ( ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time )
|
|
// {
|
|
// // in overcharge mode, so doing double damage
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// damage *= 2;
|
|
// }
|
|
// }
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
if ( altFire )
|
|
{
|
|
missile->methodOfDeath = MOD_BLASTER_ALT;
|
|
}
|
|
else
|
|
{
|
|
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 = G_EffectIndex( "turret/turb_shot" );
|
|
//use a custom impact effect
|
|
//missile->s.emplacedOwner = G_EffectIndex( "turret/turb_impact" );
|
|
|
|
missile->classname = "turbo_proj";
|
|
missile->s.weapon = WP_TIE_FIGHTER;
|
|
|
|
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_EMPLACED; //MOD_TURBLAST; //count as a heavy weap
|
|
missile->splashMethodOfDeath = MOD_EMPLACED; //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->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
missile->nextthink = level.time + 10000;//at 20000 speed, that should be more than enough
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireBlaster( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t dir, angs;
|
|
|
|
vectoangles( forward, angs );
|
|
|
|
if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE )
|
|
{//no inherent aim screw up
|
|
}
|
|
else if ( !(ent->client->ps.forcePowersActive&(1<<FP_SEE))
|
|
|| ent->client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 )
|
|
{//force sight 2+ gives perfect aim
|
|
//FIXME: maybe force sight level 3 autoaims some?
|
|
if ( alt_fire )
|
|
{
|
|
// add some slop to the alt-fire direction
|
|
angs[PITCH] += crandom() * BLASTER_ALT_SPREAD;
|
|
angs[YAW] += crandom() * BLASTER_ALT_SPREAD;
|
|
}
|
|
else
|
|
{
|
|
// Troopers use their aim values as well as the gun's inherent inaccuracy
|
|
// so check for all classes of stormtroopers and anyone else that has aim error
|
|
if ( ent->client && ent->NPC &&
|
|
( ent->client->NPC_class == CLASS_STORMTROOPER ||
|
|
ent->client->NPC_class == CLASS_SWAMPTROOPER ) )
|
|
{
|
|
angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
}
|
|
else
|
|
{
|
|
// add some slop to the main-fire direction
|
|
angs[PITCH] += crandom() * BLASTER_MAIN_SPREAD;
|
|
angs[YAW] += crandom() * BLASTER_MAIN_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, alt_fire );
|
|
}
|
|
|
|
|
|
//---------------------
|
|
// Tenloss Disruptor
|
|
//---------------------
|
|
int G_GetHitLocFromTrace( trace_t *trace, int mod )
|
|
{
|
|
int hitLoc = HL_NONE;
|
|
for (int i=0; i < MAX_G2_COLLISIONS; i++)
|
|
{
|
|
if ( trace->G2CollisionMap[i].mEntityNum == -1 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
CCollisionRecord &coll = trace->G2CollisionMap[i];
|
|
if ( (coll.mFlags & G2_FRONTFACE) )
|
|
{
|
|
G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &hitLoc, coll.mCollisionPosition, NULL, NULL, mod );
|
|
//we only want the first "entrance wound", so break
|
|
break;
|
|
}
|
|
}
|
|
return hitLoc;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_DisruptorMainFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int damage = DISRUPTOR_MAIN_DAMAGE;
|
|
qboolean render_impact = qtrue;
|
|
vec3_t start, end, spot;
|
|
trace_t tr;
|
|
gentity_t *traceEnt = NULL, *tent;
|
|
float dist, shotDist, shotRange = 8192;
|
|
|
|
if ( ent->NPC )
|
|
{
|
|
switch ( g_spskill->integer )
|
|
{
|
|
case 0:
|
|
damage = DISRUPTOR_NPC_MAIN_DAMAGE_EASY;
|
|
break;
|
|
case 1:
|
|
damage = DISRUPTOR_NPC_MAIN_DAMAGE_MEDIUM;
|
|
break;
|
|
case 2:
|
|
default:
|
|
damage = DISRUPTOR_NPC_MAIN_DAMAGE_HARD;
|
|
break;
|
|
}
|
|
}
|
|
|
|
VectorCopy( muzzle, start );
|
|
WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
|
|
|
|
// 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;
|
|
// }
|
|
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
VectorMA( start, shotRange, forward, end );
|
|
|
|
int ignore = ent->s.number;
|
|
int traces = 0;
|
|
while ( traces < 10 )
|
|
{//need to loop this in case we hit a Jedi who dodges the shot
|
|
gi.trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, G2_RETURNONHIT, 0 );
|
|
|
|
traceEnt = &g_entities[tr.entityNum];
|
|
if ( traceEnt
|
|
&& ( traceEnt->s.weapon == WP_SABER || (traceEnt->client && (traceEnt->client->NPC_class == CLASS_BOBAFETT||traceEnt->client->NPC_class == CLASS_REBORN) ) ) )
|
|
{//FIXME: need a more reliable way to know we hit a jedi?
|
|
if ( Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE ) )
|
|
{//act like we didn't even hit him
|
|
VectorCopy( tr.endpos, start );
|
|
ignore = tr.entityNum;
|
|
traces++;
|
|
continue;
|
|
}
|
|
}
|
|
//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 );
|
|
tent->svFlags |= SVF_BROADCAST;
|
|
VectorCopy( muzzle, tent->s.origin2 );
|
|
|
|
if ( render_impact )
|
|
{
|
|
if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
|
|
{
|
|
// Create a simple impact type mark that doesn't last long in the world
|
|
G_PlayEffect( G_EffectIndex( "disruptor/flesh_impact" ), tr.endpos, tr.plane.normal );
|
|
|
|
if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
|
|
{
|
|
ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
|
|
}
|
|
|
|
int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR );
|
|
if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH )
|
|
{//hehe
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, 3, DAMAGE_DEATH_KNOCKBACK, MOD_DISRUPTOR, hitLoc );
|
|
}
|
|
else
|
|
{
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_DEATH_KNOCKBACK, MOD_DISRUPTOR, hitLoc );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_PlayEffect( G_EffectIndex( "disruptor/wall_impact" ), tr.endpos, tr.plane.normal );
|
|
}
|
|
}
|
|
|
|
shotDist = shotRange * tr.fraction;
|
|
|
|
for ( dist = 0; dist < shotDist; dist += 64 )
|
|
{
|
|
//FIXME: on a really long shot, this could make a LOT of alerts in one frame...
|
|
VectorMA( start, dist, forward, spot );
|
|
AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
|
|
}
|
|
VectorMA( start, shotDist-4, forward, spot );
|
|
AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void WP_DisruptorAltFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int damage = DISRUPTOR_ALT_DAMAGE, skip, traces = DISRUPTOR_ALT_TRACES;
|
|
qboolean render_impact = qtrue;
|
|
vec3_t start, end;
|
|
vec3_t muzzle2, spot, dir;
|
|
trace_t tr;
|
|
gentity_t *traceEnt, *tent;
|
|
float dist, shotDist, shotRange = 8192;
|
|
qboolean hitDodged = qfalse, fullCharge = qfalse;
|
|
|
|
VectorCopy( muzzle, muzzle2 ); // making a backup copy
|
|
|
|
// The trace start will originate at the eye so we can ensure that it hits the crosshair.
|
|
if ( ent->NPC )
|
|
{
|
|
switch ( g_spskill->integer )
|
|
{
|
|
case 0:
|
|
damage = DISRUPTOR_NPC_ALT_DAMAGE_EASY;
|
|
break;
|
|
case 1:
|
|
damage = DISRUPTOR_NPC_ALT_DAMAGE_MEDIUM;
|
|
break;
|
|
case 2:
|
|
default:
|
|
damage = DISRUPTOR_NPC_ALT_DAMAGE_HARD;
|
|
break;
|
|
}
|
|
VectorCopy( muzzle, start );
|
|
|
|
fullCharge = qtrue;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( ent->client->renderInfo.eyePoint, start );
|
|
AngleVectors( ent->client->renderInfo.eyeAngles, forward, NULL, NULL );
|
|
|
|
// don't let NPC's do charging
|
|
int count = ( level.time - ent->client->ps.weaponChargeTime - 50 ) / DISRUPTOR_CHARGE_UNIT;
|
|
|
|
if ( count < 1 )
|
|
{
|
|
count = 1;
|
|
}
|
|
else if ( count >= 10 )
|
|
{
|
|
count = 10;
|
|
fullCharge = qtrue;
|
|
}
|
|
|
|
// more powerful charges go through more things
|
|
if ( count < 3 )
|
|
{
|
|
traces = 1;
|
|
}
|
|
else if ( count < 6 )
|
|
{
|
|
traces = 2;
|
|
}
|
|
//else do full traces
|
|
|
|
damage = damage * count + DISRUPTOR_MAIN_DAMAGE * 0.5f; // give a boost to low charge shots
|
|
}
|
|
|
|
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;
|
|
// }
|
|
|
|
for ( int 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 ( 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
|
|
gi.Printf( "BAD! Disruptor 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_DISRUPTOR_SNIPER_SHOT );
|
|
//tent->svFlags |= SVF_BROADCAST;
|
|
//tent->alt_fire = fullCharge; // mark us so we can alter the effect
|
|
|
|
//VectorCopy( muzzle2, tent->s.origin2 );
|
|
|
|
if ( tr.fraction >= 1.0f )
|
|
{
|
|
// draw the beam but don't do anything else
|
|
break;
|
|
}
|
|
|
|
traceEnt = &g_entities[tr.entityNum];
|
|
|
|
if ( traceEnt //&& traceEnt->NPC
|
|
&& ( traceEnt->s.weapon == WP_SABER || (traceEnt->client && (traceEnt->client->NPC_class == CLASS_BOBAFETT||traceEnt->client->NPC_class == CLASS_REBORN) ) ) )
|
|
{//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 )
|
|
{
|
|
// Create a simple impact type mark that doesn't last long in the world
|
|
G_PlayEffect( G_EffectIndex( "disruptor/alt_hit" ), tr.endpos, tr.plane.normal );
|
|
|
|
if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
|
|
{//NOTE: hitting multiple ents can still get you over 100% accuracy
|
|
ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
|
|
}
|
|
|
|
int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR );
|
|
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, fullCharge ? MOD_SNIPER : MOD_DISRUPTOR, hitLoc );
|
|
break;
|
|
}
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, fullCharge ? MOD_SNIPER : MOD_DISRUPTOR, hitLoc );
|
|
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_DISRUPTOR_SNIPER_MISS );
|
|
tent->svFlags |= SVF_BROADCAST;
|
|
VectorCopy( tr.plane.normal, tent->pos1 );
|
|
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 solid beam all the way to the end...
|
|
tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT );
|
|
tent->svFlags |= SVF_BROADCAST;
|
|
tent->alt_fire = fullCharge; // mark us so we can alter the effect
|
|
VectorCopy( muzzle, tent->s.origin2 );
|
|
|
|
// now go along the trail and make sight events
|
|
VectorSubtract( tr.endpos, muzzle, dir );
|
|
|
|
shotDist = VectorNormalize( dir );
|
|
|
|
//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: 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 );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireDisruptor( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
if ( alt_fire )
|
|
{
|
|
WP_DisruptorAltFire( ent );
|
|
}
|
|
else
|
|
{
|
|
WP_DisruptorMainFire( ent );
|
|
}
|
|
|
|
G_PlayEffect( G_EffectIndex( "disruptor/line_cap" ), muzzle, forward );
|
|
}
|
|
|
|
|
|
//-------------------
|
|
// Wookiee Bowcaster
|
|
//-------------------
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_BowcasterMainFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int damage = BOWCASTER_DAMAGE, count;
|
|
float vel;
|
|
vec3_t angs, dir, start;
|
|
gentity_t *missile;
|
|
|
|
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
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = BOWCASTER_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = BOWCASTER_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = BOWCASTER_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
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--;
|
|
}
|
|
|
|
// 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;
|
|
// }
|
|
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
// create a range of different velocities
|
|
vel = BOWCASTER_VELOCITY * ( crandom() * BOWCASTER_VEL_RANGE + 1.0f );
|
|
|
|
vectoangles( forward, angs );
|
|
|
|
if ( !(ent->client->ps.forcePowersActive&(1<<FP_SEE))
|
|
|| ent->client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 )
|
|
{//force sight 2+ gives perfect aim
|
|
//FIXME: maybe force sight level 3 autoaims some?
|
|
// add some slop to the fire direction
|
|
angs[PITCH] += crandom() * BOWCASTER_ALT_SPREAD * 0.2f;
|
|
angs[YAW] += ((i+0.5f) * BOWCASTER_ALT_SPREAD - count * 0.5f * BOWCASTER_ALT_SPREAD );
|
|
if ( ent->NPC )
|
|
{
|
|
angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) );
|
|
angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) );
|
|
}
|
|
}
|
|
|
|
AngleVectors( angs, dir, NULL, NULL );
|
|
|
|
missile = CreateMissile( start, dir, vel, 10000, ent );
|
|
|
|
missile->classname = "bowcaster_proj";
|
|
missile->s.weapon = WP_BOWCASTER;
|
|
|
|
VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time )
|
|
// {
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// }
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_BOWCASTER;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
missile->splashDamage = BOWCASTER_SPLASH_DAMAGE;
|
|
missile->splashRadius = BOWCASTER_SPLASH_RADIUS;
|
|
|
|
// we don't want it to bounce
|
|
missile->bounceCount = 0;
|
|
ent->client->sess.missionStats.shotsFired++;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_BowcasterAltFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
int damage = BOWCASTER_DAMAGE;
|
|
|
|
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
|
|
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
|
|
gentity_t *missile = CreateMissile( start, forward, BOWCASTER_VELOCITY, 10000, ent, qtrue );
|
|
|
|
missile->classname = "bowcaster_alt_proj";
|
|
missile->s.weapon = WP_BOWCASTER;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = BOWCASTER_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = BOWCASTER_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = BOWCASTER_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
// 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
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// damage *= 2;
|
|
// }
|
|
|
|
missile->s.eFlags |= EF_BOUNCE;
|
|
missile->bounceCount = 3;
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_BOWCASTER_ALT;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
missile->splashDamage = BOWCASTER_SPLASH_DAMAGE;
|
|
missile->splashRadius = BOWCASTER_SPLASH_RADIUS;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireBowcaster( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
if ( alt_fire )
|
|
{
|
|
WP_BowcasterAltFire( ent );
|
|
}
|
|
else
|
|
{
|
|
WP_BowcasterMainFire( ent );
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------
|
|
// Heavy Repeater
|
|
//-------------------
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_RepeaterMainFire( gentity_t *ent, vec3_t dir )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
int damage = REPEATER_DAMAGE;
|
|
|
|
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
|
|
|
|
WP_MissileTargetHint(ent, start, dir);
|
|
|
|
gentity_t *missile = CreateMissile( start, dir, REPEATER_VELOCITY, 10000, ent );
|
|
|
|
missile->classname = "repeater_proj";
|
|
missile->s.weapon = WP_REPEATER;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = REPEATER_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = REPEATER_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = REPEATER_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// damage *= 2;
|
|
// }
|
|
|
|
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 )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
int damage = REPEATER_ALT_DAMAGE;
|
|
gentity_t *missile = NULL;
|
|
|
|
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
|
|
|
|
if ( ent->client && ent->client->NPC_class == CLASS_GALAKMECH )
|
|
{
|
|
missile = CreateMissile( start, ent->client->hiddenDir, ent->client->hiddenDist, 10000, ent, qtrue );
|
|
}
|
|
else
|
|
{
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
missile = CreateMissile( start, forward, REPEATER_ALT_VELOCITY, 10000, ent, qtrue );
|
|
}
|
|
|
|
missile->classname = "repeater_alt_proj";
|
|
missile->s.weapon = WP_REPEATER;
|
|
missile->mass = 10;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = REPEATER_ALT_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = REPEATER_ALT_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = REPEATER_ALT_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
VectorSet( missile->maxs, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
missile->s.pos.trType = TR_GRAVITY;
|
|
missile->s.pos.trDelta[2] += 40.0f; //give a slight boost in the upward direction
|
|
|
|
// 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
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// damage *= 2;
|
|
// }
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_REPEATER_ALT;
|
|
missile->splashMethodOfDeath = MOD_REPEATER_ALT;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
missile->splashDamage = REPEATER_ALT_SPLASH_DAMAGE;
|
|
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 alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t dir, angs;
|
|
|
|
vectoangles( forward, angs );
|
|
|
|
if ( alt_fire )
|
|
{
|
|
WP_RepeaterAltFire( ent );
|
|
}
|
|
else
|
|
{
|
|
if ( !(ent->client->ps.forcePowersActive&(1<<FP_SEE))
|
|
|| ent->client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 )
|
|
{//force sight 2+ gives perfect aim
|
|
//FIXME: maybe force sight level 3 autoaims some?
|
|
// Troopers use their aim values as well as the gun's inherent inaccuracy
|
|
// so check for all classes of stormtroopers and anyone else that has aim error
|
|
if ( ent->client && ent->NPC &&
|
|
( ent->client->NPC_class == CLASS_STORMTROOPER ||
|
|
ent->client->NPC_class == CLASS_SWAMPTROOPER ||
|
|
ent->client->NPC_class == CLASS_SHADOWTROOPER ) )
|
|
{
|
|
angs[PITCH] += ( crandom() * (REPEATER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) );
|
|
angs[YAW] += ( crandom() * (REPEATER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) );
|
|
}
|
|
else
|
|
{
|
|
// add some slop to the alt-fire direction
|
|
angs[PITCH] += crandom() * REPEATER_SPREAD;
|
|
angs[YAW] += crandom() * REPEATER_SPREAD;
|
|
}
|
|
}
|
|
|
|
AngleVectors( angs, dir, NULL, NULL );
|
|
|
|
// FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot!
|
|
WP_RepeaterMainFire( ent, dir );
|
|
}
|
|
}
|
|
|
|
//-------------------
|
|
// DEMP2
|
|
//-------------------
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_DEMP2_MainFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
int damage = DEMP2_DAMAGE;
|
|
|
|
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
|
|
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
|
|
gentity_t *missile = CreateMissile( start, forward, DEMP2_VELOCITY, 10000, ent );
|
|
|
|
missile->classname = "demp2_proj";
|
|
missile->s.weapon = WP_DEMP2;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = DEMP2_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = DEMP2_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = DEMP2_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
VectorSet( missile->maxs, DEMP2_SIZE, DEMP2_SIZE, DEMP2_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
// 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
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// damage *= 2;
|
|
// }
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_DEMP2;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
// we don't want it to ever bounce
|
|
missile->bounceCount = 0;
|
|
}
|
|
|
|
// NOTE: this is 100% for the demp2 alt-fire effect, so changes to the visual effect will affect game side demp2 code
|
|
//--------------------------------------------------
|
|
void DEMP2_AltRadiusDamage( gentity_t *ent )
|
|
{
|
|
float frac = ( level.time - ent->fx_time ) / 1300.0f; // synchronize with demp2 effect
|
|
float dist, radius;
|
|
gentity_t *gent;
|
|
gentity_t *entityList[MAX_GENTITIES];
|
|
int numListedEntities, i, e;
|
|
vec3_t mins, maxs;
|
|
vec3_t v, dir;
|
|
|
|
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.
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = ent->currentOrigin[i] - radius;
|
|
maxs[i] = ent->currentOrigin[i] + radius;
|
|
}
|
|
|
|
numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
gent = entityList[ e ];
|
|
|
|
if ( !gent->takedamage || !gent->contents )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// find the distance from the edge of the bounding box
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( ent->currentOrigin[i] < gent->absmin[i] )
|
|
{
|
|
v[i] = gent->absmin[i] - ent->currentOrigin[i];
|
|
}
|
|
else if ( ent->currentOrigin[i] > gent->absmax[i] )
|
|
{
|
|
v[i] = ent->currentOrigin[i] - gent->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 < ent->radius )
|
|
{
|
|
// shockwave has already hit this thing...
|
|
continue;
|
|
}
|
|
|
|
VectorCopy( gent->currentOrigin, v );
|
|
VectorSubtract( v, ent->currentOrigin, dir);
|
|
|
|
// push the center of mass higher than the origin so players get knocked into the air more
|
|
dir[2] += 12;
|
|
|
|
G_Damage( gent, ent, ent->owner, dir, ent->currentOrigin, DEMP2_ALT_DAMAGE, DAMAGE_DEATH_KNOCKBACK, ent->splashMethodOfDeath );
|
|
if ( gent->takedamage && gent->client )
|
|
{
|
|
gent->s.powerups |= ( 1 << PW_SHOCKED );
|
|
gent->client->ps.powerups[PW_SHOCKED] = level.time + 2000;
|
|
Saboteur_Decloak( gent, 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->radius = radius;
|
|
|
|
if ( frac < 1.0f )
|
|
{
|
|
// shock is still happening so continue letting it expand
|
|
ent->nextthink = level.time + 50;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
void DEMP2_AltDetonate( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
G_SetOrigin( ent, ent->currentOrigin );
|
|
|
|
// start the effects, unfortunately, I wanted to do some custom things that I couldn't easily do with the fx system, so part of it uses an event and localEntities
|
|
G_PlayEffect( "demp2/altDetonate", ent->currentOrigin, ent->pos1 );
|
|
G_AddEvent( ent, EV_DEMP2_ALT_IMPACT, ent->count * 2 );
|
|
|
|
ent->fx_time = level.time;
|
|
ent->radius = 0;
|
|
ent->nextthink = level.time + 50;
|
|
ent->e_ThinkFunc = thinkF_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;
|
|
vec3_t start;
|
|
trace_t tr;
|
|
|
|
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
|
|
|
|
count = ( level.time - ent->client->ps.weaponChargeTime ) / DEMP2_CHARGE_UNIT;
|
|
|
|
if ( count < 1 )
|
|
{
|
|
count = 1;
|
|
}
|
|
else if ( count > 3 )
|
|
{
|
|
count = 3;
|
|
}
|
|
|
|
damage *= ( 1 + ( count * ( count - 1 )));// yields damage of 12,36,84...gives a higher bonus for longer charge
|
|
|
|
// the shot can travel a whopping 4096 units in 1 second. Note that the shot will auto-detonate at 4096 units...we'll see if this looks cool or not
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
gentity_t *missile = CreateMissile( start, forward, DEMP2_ALT_RANGE, 1000, ent, qtrue );
|
|
|
|
// letting it know what the charge size is.
|
|
missile->count = count;
|
|
|
|
// missile->speed = missile->nextthink;
|
|
VectorCopy( tr.plane.normal, missile->pos1 );
|
|
|
|
missile->classname = "demp2_alt_proj";
|
|
missile->s.weapon = WP_DEMP2;
|
|
|
|
missile->e_ThinkFunc = thinkF_DEMP2_AltDetonate;
|
|
|
|
missile->splashDamage = missile->damage = damage;
|
|
missile->splashMethodOfDeath = missile->methodOfDeath = MOD_DEMP2_ALT;
|
|
missile->splashRadius = DEMP2_ALT_SPLASHRADIUS;
|
|
|
|
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 alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
if ( alt_fire )
|
|
{
|
|
WP_DEMP2_AltFire( ent );
|
|
}
|
|
else
|
|
{
|
|
WP_DEMP2_MainFire( ent );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------
|
|
// Golan Arms Flechette
|
|
//-----------------------
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FlechetteMainFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t fwd, angs, start;
|
|
gentity_t *missile;
|
|
float damage = FLECHETTE_DAMAGE, vel = FLECHETTE_VEL;
|
|
|
|
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
|
|
|
|
// If we aren't the player, we will cut the velocity and damage of the shots
|
|
if ( ent->s.number )
|
|
{
|
|
damage *= 0.75f;
|
|
vel *= 0.5f;
|
|
}
|
|
|
|
// 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;
|
|
// }
|
|
|
|
for ( int i = 0; i < FLECHETTE_SHOTS; i++ )
|
|
{
|
|
vectoangles( forward, angs );
|
|
|
|
if ( i == 0 && ent->s.number == 0 )
|
|
{
|
|
// do nothing on the first shot for the player, this one will hit the crosshairs
|
|
}
|
|
else
|
|
{
|
|
angs[PITCH] += crandom() * FLECHETTE_SPREAD;
|
|
angs[YAW] += crandom() * FLECHETTE_SPREAD;
|
|
}
|
|
|
|
AngleVectors( angs, fwd, NULL, NULL );
|
|
|
|
WP_MissileTargetHint(ent, start, fwd);
|
|
|
|
missile = CreateMissile( start, fwd, vel, 10000, ent );
|
|
|
|
missile->classname = "flech_proj";
|
|
missile->s.weapon = WP_FLECHETTE;
|
|
|
|
VectorSet( missile->maxs, FLECHETTE_SIZE, FLECHETTE_SIZE, FLECHETTE_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
missile->damage = damage;
|
|
|
|
// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time )
|
|
// {
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// }
|
|
|
|
missile->dflags = (DAMAGE_DEATH_KNOCKBACK|DAMAGE_EXTRA_KNOCKBACK);
|
|
|
|
missile->methodOfDeath = MOD_FLECHETTE;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
// we don't want it to bounce forever
|
|
missile->bounceCount = Q_irand(1,2);
|
|
|
|
missile->s.eFlags |= EF_BOUNCE_SHRAPNEL;
|
|
ent->client->sess.missionStats.shotsFired++;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void prox_mine_think( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int count;
|
|
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->currentOrigin, FLECHETTE_MINE_RADIUS_CHECK, ent, qtrue, ent_list );
|
|
|
|
for ( int 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 )
|
|
{
|
|
// G_Sound( ent, G_SoundIndex( "sound/weapons/flechette/warning.wav" ));
|
|
ent->e_ThinkFunc = thinkF_WP_Explode;
|
|
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;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void prox_mine_stick( gentity_t *self, gentity_t *other, trace_t *trace )
|
|
//---------------------------------------------------------
|
|
{
|
|
// turn us into a generic entity so we aren't running missile code
|
|
self->s.eType = ET_GENERAL;
|
|
|
|
self->s.modelindex = G_ModelIndex("models/weapons2/golan_arms/prox_mine.md3");
|
|
self->e_TouchFunc = touchF_NULL;
|
|
|
|
self->contents = CONTENTS_SOLID;
|
|
self->takedamage = qtrue;
|
|
self->health = 5;
|
|
self->e_DieFunc = dieF_WP_ExplosiveDie;
|
|
|
|
VectorSet( self->maxs, 5, 5, 5 );
|
|
VectorScale( self->maxs, -1, self->mins );
|
|
|
|
self->activator = self->owner;
|
|
self->owner = NULL;
|
|
|
|
WP_Stick( self, trace );
|
|
|
|
self->e_ThinkFunc = thinkF_prox_mine_think;
|
|
self->nextthink = level.time + 450;
|
|
|
|
// sticks for twenty seconds, then auto blows.
|
|
self->delay = level.time + 20000;
|
|
|
|
gi.linkentity( self );
|
|
}
|
|
/* Old Flechette alt-fire code....
|
|
//---------------------------------------------------------
|
|
static void WP_FlechetteProxMine( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
gentity_t *missile = CreateMissile( muzzle, forward, FLECHETTE_MINE_VEL, 10000, ent, qtrue );
|
|
|
|
missile->fxID = G_EffectIndex( "flechette/explosion" );
|
|
|
|
missile->classname = "proxMine";
|
|
missile->s.weapon = WP_FLECHETTE;
|
|
|
|
missile->s.pos.trType = TR_GRAVITY;
|
|
|
|
missile->s.eFlags |= EF_MISSILE_STICK;
|
|
missile->e_TouchFunc = touchF_prox_mine_stick;
|
|
|
|
missile->damage = FLECHETTE_MINE_DAMAGE;
|
|
missile->methodOfDeath = MOD_EXPLOSIVE;
|
|
|
|
missile->splashDamage = FLECHETTE_MINE_SPLASH_DAMAGE;
|
|
missile->splashRadius = FLECHETTE_MINE_SPLASH_RADIUS;
|
|
missile->splashMethodOfDeath = MOD_EXPLOSIVE_SPLASH;
|
|
|
|
missile->clipmask = MASK_SHOT;
|
|
|
|
// we don't want it to bounce forever
|
|
missile->bounceCount = 0;
|
|
}
|
|
*/
|
|
//----------------------------------------------
|
|
void WP_flechette_alt_blow( gentity_t *ent )
|
|
//----------------------------------------------
|
|
{
|
|
EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin ); // Not sure if this is even necessary, but correct origins are cool?
|
|
|
|
G_RadiusDamage( ent->currentOrigin, ent->owner, ent->splashDamage, ent->splashRadius, NULL, MOD_EXPLOSIVE_SPLASH );
|
|
G_PlayEffect( "flechette/alt_blow", ent->currentOrigin );
|
|
|
|
G_FreeEntity( ent );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
static void WP_CreateFlechetteBouncyThing( vec3_t start, vec3_t fwd, gentity_t *self )
|
|
//------------------------------------------------------------------------------
|
|
{
|
|
gentity_t *missile = CreateMissile( start, fwd, 950 + random() * 700, 1500 + random() * 2000, self, qtrue );
|
|
|
|
missile->e_ThinkFunc = thinkF_WP_flechette_alt_blow;
|
|
|
|
missile->s.weapon = WP_FLECHETTE;
|
|
missile->classname = "flech_alt";
|
|
missile->mass = 4;
|
|
|
|
// How 'bout we give this thing a size...
|
|
VectorSet( missile->mins, -3.0f, -3.0f, -3.0f );
|
|
VectorSet( missile->maxs, 3.0f, 3.0f, 3.0f );
|
|
missile->clipmask = MASK_SHOT;
|
|
missile->clipmask &= ~CONTENTS_CORPSE;
|
|
|
|
// normal ones bounce, alt ones explode on impact
|
|
missile->s.pos.trType = TR_GRAVITY;
|
|
|
|
missile->s.eFlags |= EF_BOUNCE_HALF;
|
|
|
|
missile->damage = FLECHETTE_ALT_DAMAGE;
|
|
missile->dflags = 0;
|
|
missile->splashDamage = FLECHETTE_ALT_SPLASH_DAM;
|
|
missile->splashRadius = FLECHETTE_ALT_SPLASH_RAD;
|
|
|
|
missile->svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
|
|
missile->methodOfDeath = MOD_FLECHETTE_ALT;
|
|
missile->splashMethodOfDeath = MOD_FLECHETTE_ALT;
|
|
|
|
VectorCopy( start, missile->pos2 );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FlechetteAltFire( gentity_t *self )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t dir, fwd, start, angs;
|
|
|
|
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 ( int 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 );
|
|
self->client->sess.missionStats.shotsFired++;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireFlechette( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
if ( alt_fire )
|
|
{
|
|
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;
|
|
|
|
if ( ent->disconnectDebounceTime && ent->disconnectDebounceTime < level.time )
|
|
{//time's up, we're done, remove us
|
|
if ( ent->lockCount )
|
|
{//explode when die
|
|
WP_ExplosiveDie( ent, ent->owner, ent->owner, 0, MOD_UNKNOWN, 0, HL_NONE );
|
|
}
|
|
else
|
|
{//just remove when die
|
|
G_FreeEntity( ent );
|
|
}
|
|
return;
|
|
}
|
|
if ( ent->enemy && ent->enemy->inuse )
|
|
{
|
|
float vel = (ent->spawnflags&1)?ent->speed:ROCKET_VELOCITY;
|
|
float newDirMult = ent->angle?ent->angle*2.0f:1.0f;
|
|
float oldDirMult = ent->angle?(1.0f-ent->angle)*2.0f:1.0f;
|
|
|
|
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+ent->speed > vel )
|
|
{
|
|
vel = ent->enemy->client->ps.speed+ent->speed;
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorCopy( ent->enemy->currentOrigin, org );
|
|
org[2] += (ent->enemy->mins[2] + ent->enemy->maxs[2]) * 0.5f;
|
|
|
|
if ( ent->enemy->client )
|
|
{
|
|
switch( ent->enemy->client->NPC_class )
|
|
{
|
|
case CLASS_ATST:
|
|
org[2] += 80;
|
|
break;
|
|
case CLASS_MARK1:
|
|
org[2] += 40;
|
|
break;
|
|
case CLASS_PROBE:
|
|
org[2] += 60;
|
|
break;
|
|
}
|
|
if ( !TIMER_Done( ent->enemy, "flee" ) )
|
|
{
|
|
TIMER_Set( ent->enemy, "rocketChasing", 500 );
|
|
}
|
|
}
|
|
|
|
VectorSubtract( org, ent->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 );
|
|
|
|
// 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.3f*newDirMult, right, newdir );
|
|
}
|
|
else
|
|
{
|
|
// Turn 45 degrees left.
|
|
VectorMA(ent->movedir, -0.3f*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;
|
|
|
|
// slowing down coupled with fairly tight turns can lead us to orbit an enemy..looks bad so don't do it!
|
|
// 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 ( int 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
|
|
float dis = Distance( ent->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->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;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireRocket( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
int damage = ROCKET_DAMAGE;
|
|
float vel = ROCKET_VELOCITY;
|
|
|
|
if ( alt_fire )
|
|
{
|
|
vel *= 0.5f;
|
|
}
|
|
|
|
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
|
|
|
|
gentity_t *missile = CreateMissile( start, forward, vel, 10000, ent, alt_fire );
|
|
|
|
missile->classname = "rocket_proj";
|
|
missile->s.weapon = WP_ROCKET_LAUNCHER;
|
|
missile->mass = 10;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = ROCKET_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = ROCKET_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = ROCKET_NPC_DAMAGE_HARD;
|
|
}
|
|
if (ent->client && ent->client->NPC_class==CLASS_BOBAFETT)
|
|
{
|
|
damage = damage/2;
|
|
}
|
|
}
|
|
|
|
if ( alt_fire )
|
|
{
|
|
int lockEntNum, lockTime;
|
|
if ( ent->NPC && ent->enemy )
|
|
{
|
|
lockEntNum = ent->enemy->s.number;
|
|
lockTime = Q_irand( 600, 1200 );
|
|
}
|
|
else
|
|
{
|
|
lockEntNum = g_rocketLockEntNum;
|
|
lockTime = g_rocketLockTime;
|
|
}
|
|
// we'll consider attempting to lock this little poochie onto some baddie.
|
|
if ( (lockEntNum > 0||ent->NPC&&lockEntNum>=0) && lockEntNum < ENTITYNUM_WORLD && lockTime > 0 )
|
|
{
|
|
// take our current lock time and divide that by 8 wedge slices to get the current lock amount
|
|
int dif = ( level.time - lockTime ) / ( 1200.0f / 8.0f );
|
|
|
|
if ( dif < 0 )
|
|
{
|
|
dif = 0;
|
|
}
|
|
else if ( dif > 8 )
|
|
{
|
|
dif = 8;
|
|
}
|
|
|
|
// if we are fully locked, always take on the enemy.
|
|
// Also give a slight advantage to higher, but not quite full charges.
|
|
// Finally, just give any amount of charge a very slight random chance of locking.
|
|
if ( dif == 8 || random() * dif > 2 || random() > 0.97f )
|
|
{
|
|
missile->enemy = &g_entities[lockEntNum];
|
|
|
|
if ( missile->enemy
|
|
&& missile->enemy->inuse )//&& DistanceSquared( missile->currentOrigin, missile->enemy->currentOrigin ) < 262144 && InFOV( missile->currentOrigin, missile->enemy->currentOrigin, missile->enemy->client->ps.viewangles, 45, 45 ) )
|
|
{
|
|
if ( missile->enemy->client
|
|
&& (missile->enemy->client->ps.forcePowersKnown&(1<<FP_PUSH))
|
|
&& missile->enemy->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_0 )
|
|
{//have force push, don't flee from homing rockets
|
|
}
|
|
else
|
|
{
|
|
vec3_t dir, dir2;
|
|
|
|
AngleVectors( missile->enemy->currentAngles, dir, NULL, NULL );
|
|
AngleVectors( ent->client->renderInfo.eyeAngles, dir2, NULL, NULL );
|
|
|
|
if ( DotProduct( dir, dir2 ) < 0.0f )
|
|
{
|
|
G_StartFlee( missile->enemy, ent, missile->enemy->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 );
|
|
if ( !TIMER_Done( missile->enemy, "flee" ) )
|
|
{
|
|
TIMER_Set( missile->enemy, "rocketChasing", 500 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorCopy( forward, missile->movedir );
|
|
|
|
missile->e_ThinkFunc = thinkF_rocketThink;
|
|
missile->random = 1.0f;
|
|
missile->nextthink = level.time + ROCKET_ALT_THINK_TIME;
|
|
}
|
|
|
|
// Make it easier to hit things
|
|
VectorSet( missile->maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
|
|
if ( alt_fire )
|
|
{
|
|
missile->methodOfDeath = MOD_ROCKET_ALT;
|
|
missile->splashMethodOfDeath = MOD_ROCKET_ALT;// ?SPLASH;
|
|
}
|
|
else
|
|
{
|
|
missile->methodOfDeath = MOD_ROCKET;
|
|
missile->splashMethodOfDeath = MOD_ROCKET;// ?SPLASH;
|
|
}
|
|
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
missile->splashDamage = ROCKET_SPLASH_DAMAGE;
|
|
missile->splashRadius = ROCKET_SPLASH_RADIUS;
|
|
|
|
// we don't want it to ever bounce
|
|
missile->bounceCount = 0;
|
|
}
|
|
|
|
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, spot, dir;
|
|
trace_t tr;
|
|
gentity_t *traceEnt, *tent;
|
|
float dist, shotDist, shotRange = 8192;
|
|
qboolean hitDodged = qfalse;
|
|
|
|
if (ent->s.number >= MAX_CLIENTS)
|
|
{
|
|
vec3_t angles;
|
|
vectoangles(forward, angles);
|
|
angles[PITCH] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
angles[YAW] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
AngleVectors(angles, forward, vright, up);
|
|
}
|
|
|
|
//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
|
|
|
|
// The trace start will originate at the eye so we can ensure that it hits the crosshair.
|
|
if ( ent->NPC )
|
|
{
|
|
switch ( g_spskill->integer )
|
|
{
|
|
case 0:
|
|
damage = CONC_ALT_NPC_DAMAGE_EASY;
|
|
break;
|
|
case 1:
|
|
damage = CONC_ALT_NPC_DAMAGE_MEDIUM;
|
|
break;
|
|
case 2:
|
|
default:
|
|
damage = CONC_ALT_NPC_DAMAGE_HARD;
|
|
break;
|
|
}
|
|
}
|
|
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
|
|
vec3_t shot_mins, shot_maxs;
|
|
VectorSet( shot_mins, -1, -1, -1 );
|
|
VectorSet( shot_maxs, 1, 1, 1 );
|
|
|
|
for ( int 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 );
|
|
gi.trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 );
|
|
|
|
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
|
|
gi.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;
|
|
}
|
|
|
|
traceEnt = &g_entities[tr.entityNum];
|
|
|
|
if ( traceEnt //&& traceEnt->NPC
|
|
&& ( traceEnt->s.weapon == WP_SABER || (traceEnt->client && (traceEnt->client->NPC_class == CLASS_BOBAFETT||traceEnt->client->NPC_class == CLASS_REBORN) ) ) )
|
|
{//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 )
|
|
{
|
|
// Create a simple impact type mark that doesn't last long in the world
|
|
G_PlayEffect( G_EffectIndex( "concussion/alt_hit" ), tr.endpos, tr.plane.normal );
|
|
|
|
if ( traceEnt->client && LogAccuracyHit( traceEnt, ent ))
|
|
{//NOTE: hitting multiple ents can still get you over 100% accuracy
|
|
ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
|
|
}
|
|
|
|
int hitLoc = G_GetHitLocFromTrace( &tr, MOD_CONC_ALT );
|
|
qboolean 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, hitLoc );
|
|
break;
|
|
}
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT, hitLoc );
|
|
|
|
//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 ( traceEnt->NPC || Q_irand(0,g_spskill->integer+1) )
|
|
{
|
|
if ( !noKnockBack )
|
|
{//knock-backable
|
|
G_Throw( traceEnt, pushDir, 200 );
|
|
if ( traceEnt->client->NPC_class == CLASS_ROCKETTROOPER )
|
|
{
|
|
traceEnt->client->ps.pm_time = Q_irand( 1500, 3000 );
|
|
}
|
|
}
|
|
if ( traceEnt->health > 0 )
|
|
{//alive
|
|
if ( G_HasKnockdownAnims( traceEnt ) )
|
|
{//knock-downable
|
|
G_Knockdown( traceEnt, ent, pushDir, 400, qtrue );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_CONC_ALT_MISS );
|
|
tent->svFlags |= SVF_BROADCAST;
|
|
VectorCopy( tr.plane.normal, tent->pos1 );
|
|
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;
|
|
VectorCopy( muzzle, tent->s.origin2 );
|
|
|
|
// now go along the trail and make sight events
|
|
VectorSubtract( tr.endpos, muzzle, dir );
|
|
|
|
shotDist = VectorNormalize( dir );
|
|
|
|
//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_PlayEffect( G_EffectIndex( "concussion/alt_ring" ), spot, forward );
|
|
}
|
|
//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_PlayEffect( G_EffectIndex( "concussion/altmuzzle_flash" ), muzzle, forward );
|
|
}
|
|
|
|
static void WP_FireConcussion( gentity_t *ent )
|
|
{//a fast rocket-like projectile
|
|
vec3_t start;
|
|
int damage = CONC_DAMAGE;
|
|
float vel = CONC_VELOCITY;
|
|
|
|
if (ent->s.number >= MAX_CLIENTS)
|
|
{
|
|
vec3_t angles;
|
|
vectoangles(forward, angles);
|
|
angles[PITCH] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
angles[YAW] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
AngleVectors(angles, forward, vright, up);
|
|
}
|
|
|
|
//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;
|
|
}
|
|
|
|
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
|
|
|
|
gentity_t *missile = CreateMissile( start, forward, vel, 10000, ent, qfalse );
|
|
|
|
missile->classname = "conc_proj";
|
|
missile->s.weapon = WP_CONCUSSION;
|
|
missile->mass = 10;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = CONC_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = CONC_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = CONC_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
// Make it easier to hit things
|
|
VectorSet( missile->maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->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;
|
|
}
|
|
|
|
//-----------------------
|
|
// Det Pack
|
|
//-----------------------
|
|
|
|
//---------------------------------------------------------
|
|
void charge_stick( gentity_t *self, gentity_t *other, trace_t *trace )
|
|
//---------------------------------------------------------
|
|
{
|
|
self->s.eType = ET_GENERAL;
|
|
|
|
// make us so we can take damage
|
|
self->clipmask = MASK_SHOT;
|
|
self->contents = CONTENTS_SHOTCLIP;
|
|
self->takedamage = qtrue;
|
|
self->health = 25;
|
|
|
|
self->e_DieFunc = dieF_WP_ExplosiveDie;
|
|
|
|
VectorSet( self->maxs, 10, 10, 10 );
|
|
VectorScale( self->maxs, -1, self->mins );
|
|
|
|
self->activator = self->owner;
|
|
self->owner = NULL;
|
|
|
|
self->e_TouchFunc = touchF_NULL;
|
|
self->e_ThinkFunc = thinkF_NULL;
|
|
self->nextthink = -1;
|
|
|
|
WP_Stick( self, trace, 1.0f );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_DropDetPack( gentity_t *self, vec3_t start, vec3_t dir )
|
|
//---------------------------------------------------------
|
|
{
|
|
// Chucking a new one
|
|
AngleVectors( self->client->ps.viewangles, forward, vright, up );
|
|
CalcMuzzlePoint( self, forward, vright, up, muzzle, 0 );
|
|
VectorNormalize( forward );
|
|
VectorMA( muzzle, -4, forward, muzzle );
|
|
|
|
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
|
|
|
|
gentity_t *missile = CreateMissile( start, forward, 300, 10000, self, qfalse );
|
|
|
|
missile->fxID = G_EffectIndex( "detpack/explosion" ); // if we set an explosion effect, explode death can use that instead
|
|
|
|
missile->classname = "detpack";
|
|
missile->s.weapon = WP_DET_PACK;
|
|
|
|
missile->s.pos.trType = TR_GRAVITY;
|
|
|
|
missile->s.eFlags |= EF_MISSILE_STICK;
|
|
missile->e_TouchFunc = touchF_charge_stick;
|
|
|
|
missile->damage = FLECHETTE_MINE_DAMAGE;
|
|
missile->methodOfDeath = MOD_DETPACK;
|
|
|
|
missile->splashDamage = FLECHETTE_MINE_SPLASH_DAMAGE;
|
|
missile->splashRadius = FLECHETTE_MINE_SPLASH_RADIUS;
|
|
missile->splashMethodOfDeath = MOD_DETPACK;// ?SPLASH;
|
|
|
|
missile->clipmask = (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_SHOTCLIP);//MASK_SHOT;
|
|
|
|
// we don't want it to ever bounce
|
|
missile->bounceCount = 0;
|
|
|
|
missile->s.radius = 30;
|
|
VectorSet( missile->s.modelScale, 1.0f, 1.0f, 1.0f );
|
|
gi.G2API_InitGhoul2Model( missile->ghoul2, weaponData[WP_DET_PACK].missileMdl, G_ModelIndex( weaponData[WP_DET_PACK].missileMdl ));
|
|
|
|
AddSoundEvent( NULL, missile->currentOrigin, 128, AEL_MINOR, qtrue );
|
|
AddSightEvent( NULL, missile->currentOrigin, 128, AEL_SUSPICIOUS, 10 );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireDetPack( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
if ( !ent || !ent->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( alt_fire )
|
|
{
|
|
if ( ent->client->ps.eFlags & EF_PLANTED_CHARGE )
|
|
{
|
|
gentity_t *found = NULL;
|
|
|
|
// loop through all ents and blow the crap out of them!
|
|
while (( found = G_Find( found, FOFS( classname ), "detpack" )) != NULL )
|
|
{
|
|
if ( found->activator == ent )
|
|
{
|
|
VectorCopy( found->currentOrigin, found->s.origin );
|
|
found->e_ThinkFunc = thinkF_WP_Explode;
|
|
found->nextthink = level.time + 100 + random() * 100;
|
|
G_Sound( found, G_SoundIndex( "sound/weapons/detpack/warning.wav" ));
|
|
|
|
// would be nice if this actually worked?
|
|
AddSoundEvent( NULL, found->currentOrigin, found->splashRadius*2, AEL_DANGER, qfalse, qtrue );//FIXME: are we on ground or not?
|
|
AddSightEvent( NULL, found->currentOrigin, found->splashRadius*2, AEL_DISCOVERED, 100 );
|
|
}
|
|
}
|
|
|
|
ent->client->ps.eFlags &= ~EF_PLANTED_CHARGE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WP_DropDetPack( ent, muzzle, forward );
|
|
|
|
ent->client->ps.eFlags |= EF_PLANTED_CHARGE;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------
|
|
// Laser Trip Mine
|
|
//-----------------------
|
|
|
|
#define PROXIMITY_STYLE 1
|
|
#define TRIPWIRE_STYLE 2
|
|
|
|
//---------------------------------------------------------
|
|
void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace )
|
|
//---------------------------------------------------------
|
|
{
|
|
ent->s.eType = ET_GENERAL;
|
|
|
|
// a tripwire so add draw line flag
|
|
VectorCopy( trace->plane.normal, ent->movedir );
|
|
|
|
// make it shootable
|
|
VectorSet( ent->mins, -4, -4, -4 );
|
|
VectorSet( ent->maxs, 4, 4, 4 );
|
|
|
|
ent->clipmask = MASK_SHOT;
|
|
ent->contents = CONTENTS_SHOTCLIP;
|
|
ent->takedamage = qtrue;
|
|
ent->health = 15;
|
|
|
|
ent->e_DieFunc = dieF_WP_ExplosiveDie;
|
|
ent->e_TouchFunc = touchF_NULL;
|
|
|
|
// so we can trip it too
|
|
ent->activator = ent->owner;
|
|
ent->owner = NULL;
|
|
|
|
WP_Stick( ent, trace );
|
|
|
|
if ( ent->count == TRIPWIRE_STYLE )
|
|
{
|
|
vec3_t mins = {-4,-4,-4}, maxs = {4,4,4};//FIXME: global these
|
|
trace_t tr;
|
|
VectorMA( ent->currentOrigin, 32, ent->movedir, ent->s.origin2 );
|
|
gi.trace( &tr, ent->s.origin2, mins, maxs, ent->currentOrigin, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 0 );
|
|
VectorCopy( tr.endpos, ent->s.origin2 );
|
|
|
|
ent->e_ThinkFunc = thinkF_laserTrapThink;
|
|
}
|
|
else
|
|
{
|
|
ent->e_ThinkFunc = thinkF_WP_prox_mine_think;
|
|
}
|
|
|
|
ent->nextthink = level.time + LT_ACTIVATION_DELAY;
|
|
}
|
|
|
|
// copied from flechette prox above...which will not be used if this gets approved
|
|
//---------------------------------------------------------
|
|
void WP_prox_mine_think( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int count;
|
|
qboolean blow = qfalse;
|
|
|
|
// first time through?
|
|
if ( ent->count )
|
|
{
|
|
// play activated warning
|
|
ent->count = 0;
|
|
ent->s.eFlags |= EF_PROX_TRIP;
|
|
G_Sound( ent, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ));
|
|
}
|
|
|
|
// if it isn't time to auto-explode, do a small proximity check
|
|
if ( ent->delay > level.time )
|
|
{
|
|
count = G_RadiusList( ent->currentOrigin, PROX_MINE_RADIUS_CHECK, ent, qtrue, ent_list );
|
|
|
|
for ( int 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 )
|
|
{
|
|
// G_Sound( ent, G_SoundIndex( "sound/weapons/flechette/warning.wav" ));
|
|
ent->e_ThinkFunc = thinkF_WP_Explode;
|
|
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;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void laserTrapThink( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
gentity_t *traceEnt;
|
|
vec3_t end, mins = {-4,-4,-4}, maxs = {4,4,4};
|
|
trace_t tr;
|
|
|
|
// turn on the beam effect
|
|
if ( !(ent->s.eFlags & EF_FIRING ))
|
|
{
|
|
// arm me
|
|
G_Sound( ent, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" ));
|
|
ent->s.loopSound = G_SoundIndex( "sound/weapons/laser_trap/hum_loop.wav" );
|
|
ent->s.eFlags |= EF_FIRING;
|
|
}
|
|
|
|
ent->e_ThinkFunc = thinkF_laserTrapThink;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
|
|
// Find the main impact point
|
|
VectorMA( ent->s.pos.trBase, 2048, ent->movedir, end );
|
|
gi.trace( &tr, ent->s.origin2, mins, maxs, end, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 0 );
|
|
|
|
traceEnt = &g_entities[ tr.entityNum ];
|
|
|
|
// Adjust this so that the effect has a relatively fresh endpoint
|
|
VectorCopy( tr.endpos, ent->pos4 );
|
|
|
|
if ( traceEnt->client || tr.startsolid )
|
|
{
|
|
// go boom
|
|
WP_Explode( ent );
|
|
ent->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
// FIXME: they need to avoid the beam!
|
|
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER );
|
|
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 50 );
|
|
*/
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner )
|
|
//---------------------------------------------------------
|
|
{
|
|
if ( !VALIDSTRING( laserTrap->classname ))
|
|
{
|
|
// since we may be coming from a map placed trip mine, we don't want to override that class name....
|
|
// That would be bad because the player drop code tries to limit number of placed items...so it would have removed map placed ones as well.
|
|
laserTrap->classname = "tripmine";
|
|
}
|
|
|
|
laserTrap->splashDamage = LT_SPLASH_DAM;
|
|
laserTrap->splashRadius = LT_SPLASH_RAD;
|
|
laserTrap->damage = LT_DAMAGE;
|
|
laserTrap->methodOfDeath = MOD_LASERTRIP;
|
|
laserTrap->splashMethodOfDeath = MOD_LASERTRIP;//? SPLASH;
|
|
|
|
laserTrap->s.eType = ET_MISSILE;
|
|
laserTrap->svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
laserTrap->s.weapon = WP_TRIP_MINE;
|
|
|
|
laserTrap->owner = owner;
|
|
// VectorSet( laserTrap->mins, -LT_SIZE, -LT_SIZE, -LT_SIZE );
|
|
// VectorSet( laserTrap->maxs, LT_SIZE, LT_SIZE, LT_SIZE );
|
|
laserTrap->clipmask = (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_SHOTCLIP);//MASK_SHOT;
|
|
|
|
laserTrap->s.pos.trTime = level.time; // move a bit on the very first frame
|
|
VectorCopy( start, laserTrap->s.pos.trBase );
|
|
VectorCopy( start, laserTrap->currentOrigin);
|
|
|
|
VectorCopy( start, laserTrap->pos2 ); // ?? wtf ?
|
|
|
|
laserTrap->fxID = G_EffectIndex( "tripMine/explosion" );
|
|
|
|
laserTrap->e_TouchFunc = touchF_touchLaserTrap;
|
|
|
|
laserTrap->s.radius = 60;
|
|
VectorSet( laserTrap->s.modelScale, 1.0f, 1.0f, 1.0f );
|
|
gi.G2API_InitGhoul2Model( laserTrap->ghoul2, weaponData[WP_TRIP_MINE].missileMdl, G_ModelIndex( weaponData[WP_TRIP_MINE].missileMdl ));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_RemoveOldTraps( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
gentity_t *found = NULL;
|
|
int trapcount = 0, i;
|
|
int foundLaserTraps[MAX_GENTITIES] = {ENTITYNUM_NONE};
|
|
int trapcount_org, lowestTimeStamp;
|
|
int removeMe;
|
|
|
|
// see how many there are now
|
|
while (( found = G_Find( found, FOFS(classname), "tripmine" )) != NULL )
|
|
{
|
|
if ( found->activator != ent ) // activator is really the owner?
|
|
{
|
|
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 ( 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void WP_PlaceLaserTrap( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
gentity_t *laserTrap;
|
|
|
|
// limit to 10 placed at any one time
|
|
WP_RemoveOldTraps( ent );
|
|
|
|
//FIXME: surface must be within 64
|
|
laserTrap = G_Spawn();
|
|
|
|
if ( laserTrap )
|
|
{
|
|
// now make the new one
|
|
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
|
|
|
|
CreateLaserTrap( laserTrap, start, ent );
|
|
|
|
// set player-created-specific fields
|
|
laserTrap->setTime = level.time;//remember when we placed it
|
|
|
|
laserTrap->s.eFlags |= EF_MISSILE_STICK;
|
|
laserTrap->s.pos.trType = TR_GRAVITY;
|
|
VectorScale( forward, LT_VELOCITY, laserTrap->s.pos.trDelta );
|
|
|
|
if ( alt_fire )
|
|
{
|
|
laserTrap->count = PROXIMITY_STYLE;
|
|
laserTrap->delay = level.time + 40000; // will auto-blow in 40 seconds.
|
|
laserTrap->methodOfDeath = MOD_LASERTRIP_ALT;
|
|
laserTrap->splashMethodOfDeath = MOD_LASERTRIP_ALT;//? SPLASH;
|
|
}
|
|
else
|
|
{
|
|
laserTrap->count = TRIPWIRE_STYLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------
|
|
// Thermal Detonator
|
|
//---------------------
|
|
|
|
//---------------------------------------------------------
|
|
void thermalDetonatorExplode( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) )
|
|
{
|
|
ent->takedamage = qfalse; // don't allow double deaths!
|
|
|
|
G_Damage( ent->activator, ent, ent->owner, vec3_origin, ent->currentOrigin, TD_ALT_DAMAGE, 0, MOD_EXPLOSIVE );
|
|
G_PlayEffect( "thermal/explosion", ent->currentOrigin );
|
|
G_PlayEffect( "thermal/shockwave", ent->currentOrigin );
|
|
|
|
G_FreeEntity( ent );
|
|
}
|
|
else if ( !ent->count )
|
|
{
|
|
G_Sound( ent, G_SoundIndex( "sound/weapons/thermal/warning.wav" ) );
|
|
ent->count = 1;
|
|
ent->nextthink = level.time + 800;
|
|
ent->svFlags |= SVF_BROADCAST;//so everyone hears/sees the explosion?
|
|
}
|
|
else
|
|
{
|
|
vec3_t pos;
|
|
|
|
VectorSet( pos, ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2] + 8 );
|
|
|
|
ent->takedamage = qfalse; // don't allow double deaths!
|
|
|
|
G_RadiusDamage( ent->currentOrigin, ent->owner, TD_SPLASH_DAM, TD_SPLASH_RAD, NULL, MOD_EXPLOSIVE_SPLASH );
|
|
|
|
G_PlayEffect( "thermal/explosion", ent->currentOrigin );
|
|
G_PlayEffect( "thermal/shockwave", ent->currentOrigin );
|
|
|
|
G_FreeEntity( ent );
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------------------
|
|
void thermal_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
|
|
//-------------------------------------------------------------------------------------------------------------
|
|
{
|
|
thermalDetonatorExplode( self );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
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 = 0, float maxSpeed = 0, float idealSpeed = 0, qboolean mustHit = qfalse )
|
|
//---------------------------------------------------------
|
|
{
|
|
float targetDist, shotSpeed, speedInc = 100, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed,
|
|
vec3_t targetDir, shotVel, failCase = {0, 0, 0};
|
|
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 );
|
|
}
|
|
EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
|
|
gi.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;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void WP_ThermalThink( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int count;
|
|
qboolean blow = qfalse;
|
|
|
|
// Thermal detonators for the player do occasional radius checks and blow up if there are entities in the blast radius
|
|
// This is done so that the main fire is actually useful as an attack. We explode anyway after delay expires.
|
|
|
|
if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) )
|
|
{//blow once creature is underground (done with anim)
|
|
//FIXME: chance of being spit out? Especially if lots of delay left...
|
|
ent->e_TouchFunc = NULL;//don't impact on anything
|
|
if ( !ent->activator
|
|
|| !ent->activator->client
|
|
|| !ent->activator->client->ps.legsAnimTimer )
|
|
{//either something happened to the sand creature or it's done with it's attack anim
|
|
//blow!
|
|
ent->e_ThinkFunc = thinkF_thermalDetonatorExplode;
|
|
ent->nextthink = level.time + Q_irand( 50, 2000 );
|
|
}
|
|
else
|
|
{//keep checking
|
|
ent->nextthink = level.time + TD_THINK_TIME;
|
|
}
|
|
return;
|
|
}
|
|
else if ( ent->delay > level.time )
|
|
{
|
|
// Finally, we force it to bounce at least once before doing the special checks, otherwise it's just too easy for the player?
|
|
if ( ent->has_bounced )
|
|
{
|
|
count = G_RadiusList( ent->currentOrigin, TD_TEST_RAD, ent, qtrue, ent_list );
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( ent_list[i]->s.number == 0 )
|
|
{
|
|
// avoid deliberately blowing up next to the player, no matter how close any enemy is..
|
|
// ...if the delay time expires though, there is no saving the player...muwhaaa haa ha
|
|
blow = qfalse;
|
|
break;
|
|
}
|
|
else if ( ent_list[i]->client
|
|
&& ent_list[i]->client->NPC_class != CLASS_SAND_CREATURE//ignore sand creatures
|
|
&& ent_list[i]->health > 0 )
|
|
{
|
|
//FIXME! sometimes the ent_list order changes, so we should make sure that the player isn't anywhere in this list
|
|
blow = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// our death time has arrived, even if nothing is near us
|
|
blow = qtrue;
|
|
}
|
|
|
|
if ( blow )
|
|
{
|
|
ent->e_ThinkFunc = thinkF_thermalDetonatorExplode;
|
|
ent->nextthink = level.time + 50;
|
|
}
|
|
else
|
|
{
|
|
// we probably don't need to do this thinking logic very often...maybe this is fast enough?
|
|
ent->nextthink = level.time + TD_THINK_TIME;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
gentity_t *WP_FireThermalDetonator( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
gentity_t *bolt;
|
|
vec3_t dir, start;
|
|
float damageScale = 1.0f;
|
|
|
|
VectorCopy( forward, dir );
|
|
VectorCopy( muzzle, start );
|
|
|
|
bolt = G_Spawn();
|
|
|
|
bolt->classname = "thermal_detonator";
|
|
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
// If not the player, cut the damage a bit so we don't get pounded on so much
|
|
damageScale = TD_NPC_DAMAGE_CUT;
|
|
}
|
|
|
|
if ( !alt_fire && ent->s.number == 0 )
|
|
{
|
|
// Main fires for the players do a little bit of extra thinking
|
|
bolt->e_ThinkFunc = thinkF_WP_ThermalThink;
|
|
bolt->nextthink = level.time + TD_THINK_TIME;
|
|
bolt->delay = level.time + TD_TIME; // How long 'til she blows
|
|
}
|
|
else
|
|
{
|
|
bolt->e_ThinkFunc = thinkF_thermalDetonatorExplode;
|
|
bolt->nextthink = level.time + TD_TIME; // How long 'til she blows
|
|
}
|
|
|
|
bolt->mass = 10;
|
|
|
|
// How 'bout we give this thing a size...
|
|
VectorSet( bolt->mins, -4.0f, -4.0f, -4.0f );
|
|
VectorSet( bolt->maxs, 4.0f, 4.0f, 4.0f );
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->clipmask &= ~CONTENTS_CORPSE;
|
|
bolt->contents = CONTENTS_SHOTCLIP;
|
|
bolt->takedamage = qtrue;
|
|
bolt->health = 15;
|
|
bolt->e_DieFunc = dieF_thermal_die;
|
|
|
|
WP_TraceSetStart( ent, start, bolt->mins, bolt->maxs );//make sure our start point isn't on the other side of a wall
|
|
|
|
float chargeAmount = 1.0f; // default of full charge
|
|
|
|
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;
|
|
}
|
|
|
|
float thrownSpeed = TD_VELOCITY;
|
|
const qboolean thisIsAShooter = !Q_stricmp( "misc_weapon_shooter", ent->classname);
|
|
|
|
if (thisIsAShooter)
|
|
{
|
|
if (ent->delay != 0)
|
|
{
|
|
thrownSpeed = ent->delay;
|
|
}
|
|
}
|
|
|
|
// normal ones bounce, alt ones explode on impact
|
|
bolt->s.pos.trType = TR_GRAVITY;
|
|
bolt->owner = ent;
|
|
VectorScale( dir, thrownSpeed * chargeAmount, bolt->s.pos.trDelta );
|
|
|
|
if ( ent->health > 0 )
|
|
{
|
|
bolt->s.pos.trDelta[2] += 120;
|
|
|
|
if ( (ent->NPC || (ent->s.number && thisIsAShooter) )
|
|
&& ent->enemy )
|
|
{//NPC or misc_weapon_shooter
|
|
//FIXME: we're assuming he's actually facing this direction...
|
|
vec3_t target;
|
|
|
|
VectorCopy( ent->enemy->currentOrigin, target );
|
|
if ( target[2] <= start[2] )
|
|
{
|
|
vec3_t vec;
|
|
VectorSubtract( target, start, vec );
|
|
VectorNormalize( vec );
|
|
VectorMA( target, Q_flrand( 0, -32 ), vec, target );//throw a little short
|
|
}
|
|
|
|
target[0] += Q_flrand( -5, 5 )+(crandom()*(6-ent->NPC->currentAim)*2);
|
|
target[1] += Q_flrand( -5, 5 )+(crandom()*(6-ent->NPC->currentAim)*2);
|
|
target[2] += Q_flrand( -5, 5 )+(crandom()*(6-ent->NPC->currentAim)*2);
|
|
|
|
WP_LobFire( ent, start, target, bolt->mins, bolt->maxs, bolt->clipmask, bolt->s.pos.trDelta, qtrue, ent->s.number, ent->enemy->s.number );
|
|
}
|
|
else if ( thisIsAShooter && ent->target && !VectorCompare( ent->pos1, vec3_origin ) )
|
|
{//misc_weapon_shooter firing at a position
|
|
WP_LobFire( ent, start, ent->pos1, bolt->mins, bolt->maxs, bolt->clipmask, bolt->s.pos.trDelta, qtrue, ent->s.number, ent->enemy->s.number );
|
|
}
|
|
}
|
|
|
|
if ( alt_fire )
|
|
{
|
|
bolt->alt_fire = qtrue;
|
|
}
|
|
else
|
|
{
|
|
bolt->s.eFlags |= EF_BOUNCE_HALF;
|
|
}
|
|
|
|
bolt->s.loopSound = G_SoundIndex( "sound/weapons/thermal/thermloop.wav" );
|
|
|
|
bolt->damage = TD_DAMAGE * damageScale;
|
|
bolt->dflags = 0;
|
|
bolt->splashDamage = TD_SPLASH_DAM * damageScale;
|
|
bolt->splashRadius = TD_SPLASH_RAD;
|
|
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_THERMAL;
|
|
|
|
if ( alt_fire )
|
|
{
|
|
bolt->methodOfDeath = MOD_THERMAL_ALT;
|
|
bolt->splashMethodOfDeath = MOD_THERMAL_ALT;//? SPLASH;
|
|
}
|
|
else
|
|
{
|
|
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->currentOrigin);
|
|
|
|
VectorCopy( start, bolt->pos2 );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
gentity_t *WP_DropThermal( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
AngleVectors( ent->client->ps.viewangles, forward, vright, up );
|
|
CalcEntitySpot( ent, SPOT_WEAPON, muzzle );
|
|
return (WP_FireThermalDetonator( ent, qfalse ));
|
|
}
|
|
|
|
|
|
// Bot Laser
|
|
//---------------------------------------------------------
|
|
void WP_BotLaser( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
gentity_t *missile = CreateMissile( muzzle, forward, BRYAR_PISTOL_VEL, 10000, ent );
|
|
|
|
missile->classname = "bryar_proj";
|
|
missile->s.weapon = WP_BRYAR_PISTOL;
|
|
|
|
missile->damage = BRYAR_PISTOL_DAMAGE;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_ENERGY;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
}
|
|
|
|
|
|
// Emplaced Gun
|
|
//---------------------------------------------------------
|
|
void WP_EmplacedFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
float damage = EMPLACED_DAMAGE * ( ent->NPC ? 0.1f : 1.0f );
|
|
float vel = EMPLACED_VEL * ( ent->NPC ? 0.4f : 1.0f );
|
|
|
|
WP_MissileTargetHint(ent, muzzle, forward);
|
|
|
|
gentity_t *missile = CreateMissile( muzzle, forward, vel, 10000, ent );
|
|
|
|
missile->classname = "emplaced_proj";
|
|
missile->s.weapon = WP_EMPLACED_GUN;
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS;
|
|
missile->methodOfDeath = MOD_EMPLACED;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
// do some weird switchery on who the real owner is, we do this so the projectiles don't hit the gun object
|
|
if ( ent && ent->client && !(ent->client->ps.eFlags&EF_LOCKED_TO_WEAPON) )
|
|
{
|
|
missile->owner = ent;
|
|
}
|
|
else
|
|
{
|
|
missile->owner = ent->owner;
|
|
}
|
|
|
|
if ( missile->owner->e_UseFunc == useF_eweb_use )
|
|
{
|
|
missile->alt_fire = qtrue;
|
|
}
|
|
|
|
VectorSet( missile->maxs, EMPLACED_SIZE, EMPLACED_SIZE, EMPLACED_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
// alternate muzzles
|
|
ent->fxID = !ent->fxID;
|
|
}
|
|
|
|
// ATST Main
|
|
//---------------------------------------------------------
|
|
void WP_ATSTMainFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
float vel = ATST_MAIN_VEL;
|
|
|
|
// if ( ent->client && (ent->client->ps.eFlags & EF_IN_ATST ))
|
|
// {
|
|
// vel = 4500.0f;
|
|
// }
|
|
|
|
if ( !ent->s.number )
|
|
{
|
|
// player shoots faster
|
|
vel *= 1.6f;
|
|
}
|
|
|
|
WP_MissileTargetHint(ent, muzzle, forward);
|
|
|
|
gentity_t *missile = CreateMissile( muzzle, forward, vel, 10000, ent );
|
|
|
|
missile->classname = "atst_main_proj";
|
|
missile->s.weapon = WP_ATST_MAIN;
|
|
|
|
missile->damage = ATST_MAIN_DAMAGE;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS;
|
|
missile->methodOfDeath = MOD_ENERGY;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
missile->owner = ent;
|
|
|
|
VectorSet( missile->maxs, ATST_MAIN_SIZE, ATST_MAIN_SIZE, ATST_MAIN_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
}
|
|
|
|
// ATST Alt Side
|
|
//---------------------------------------------------------
|
|
void WP_ATSTSideAltFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int damage = ATST_SIDE_ALT_DAMAGE;
|
|
float vel = ATST_SIDE_ALT_NPC_VELOCITY;
|
|
|
|
if ( ent->client && (ent->client->ps.eFlags & EF_IN_ATST ))
|
|
{
|
|
vel = ATST_SIDE_ALT_VELOCITY;
|
|
}
|
|
|
|
gentity_t *missile = CreateMissile( muzzle, forward, vel, 10000, ent, qtrue );
|
|
|
|
missile->classname = "atst_rocket";
|
|
missile->s.weapon = WP_ATST_SIDE;
|
|
|
|
missile->mass = 10;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = ATST_SIDE_ROCKET_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = ATST_SIDE_ROCKET_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = ATST_SIDE_ROCKET_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
VectorCopy( forward, missile->movedir );
|
|
|
|
// Make it easier to hit things
|
|
VectorSet( missile->maxs, ATST_SIDE_ALT_ROCKET_SIZE, ATST_SIDE_ALT_ROCKET_SIZE, ATST_SIDE_ALT_ROCKET_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS;
|
|
missile->methodOfDeath = MOD_EXPLOSIVE;
|
|
missile->splashMethodOfDeath = MOD_EXPLOSIVE_SPLASH;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
// Scale damage down a bit if it is coming from an NPC
|
|
missile->splashDamage = ATST_SIDE_ALT_SPLASH_DAMAGE * ( ent->s.number == 0 ? 1.0f : ATST_SIDE_ALT_ROCKET_SPLASH_SCALE );
|
|
missile->splashRadius = ATST_SIDE_ALT_SPLASH_RADIUS;
|
|
|
|
// we don't want it to ever bounce
|
|
missile->bounceCount = 0;
|
|
}
|
|
|
|
// ATST Side
|
|
//---------------------------------------------------------
|
|
void WP_ATSTSideFire( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
int damage = ATST_SIDE_MAIN_DAMAGE;
|
|
|
|
gentity_t *missile = CreateMissile( muzzle, forward, ATST_SIDE_MAIN_VELOCITY, 10000, ent, qfalse );
|
|
|
|
missile->classname = "atst_side_proj";
|
|
missile->s.weapon = WP_ATST_SIDE;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
damage = ATST_SIDE_MAIN_NPC_DAMAGE_EASY;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
damage = ATST_SIDE_MAIN_NPC_DAMAGE_NORMAL;
|
|
}
|
|
else
|
|
{
|
|
damage = ATST_SIDE_MAIN_NPC_DAMAGE_HARD;
|
|
}
|
|
}
|
|
|
|
VectorSet( missile->maxs, ATST_SIDE_MAIN_SIZE, ATST_SIDE_MAIN_SIZE, ATST_SIDE_MAIN_SIZE );
|
|
VectorScale( missile->maxs, -1, missile->mins );
|
|
|
|
missile->damage = damage;
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS;
|
|
missile->methodOfDeath = MOD_ENERGY;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
missile->splashDamage = ATST_SIDE_MAIN_SPLASH_DAMAGE * ( ent->s.number == 0 ? 1.0f : 0.6f );
|
|
missile->splashRadius = ATST_SIDE_MAIN_SPLASH_RADIUS;
|
|
|
|
// we don't want it to bounce
|
|
missile->bounceCount = 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void WP_FireStunBaton( gentity_t *ent, qboolean alt_fire )
|
|
{
|
|
gentity_t *tr_ent;
|
|
trace_t tr;
|
|
vec3_t mins, maxs, end, start;
|
|
|
|
G_Sound( ent, G_SoundIndex( "sound/weapons/baton/fire" ));
|
|
#ifdef _IMMERSION
|
|
G_Force( ent, G_ForceIndex( "fffx/weapons/baton/fire", FF_CHANNEL_WEAPON ) );
|
|
#endif // _IMMERSION
|
|
|
|
VectorCopy( muzzle, start );
|
|
WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
|
|
|
|
VectorMA( start, STUN_BATON_RANGE, forward, end );
|
|
|
|
VectorSet( maxs, 5, 5, 5 );
|
|
VectorScale( maxs, -1, mins );
|
|
|
|
gi.trace ( &tr, start, mins, maxs, end, ent->s.number, CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_SHOTCLIP );
|
|
|
|
if ( tr.entityNum >= ENTITYNUM_WORLD || tr.entityNum < 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
tr_ent = &g_entities[tr.entityNum];
|
|
|
|
if ( tr_ent && tr_ent->takedamage && tr_ent->client )
|
|
{
|
|
G_PlayEffect( "stunBaton/flesh_impact", tr.endpos, tr.plane.normal );
|
|
|
|
// TEMP!
|
|
// G_Sound( tr_ent, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
|
|
tr_ent->client->ps.powerups[PW_SHOCKED] = level.time + 1500;
|
|
|
|
G_Damage( tr_ent, ent, ent, forward, tr.endpos, STUN_BATON_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
|
|
}
|
|
else if ( tr_ent->svFlags & SVF_GLASS_BRUSH || ( tr_ent->svFlags & SVF_BBRUSH && tr_ent->material == 12 )) // material grate...we are breaking a grate!
|
|
{
|
|
G_Damage( tr_ent, ent, ent, forward, tr.endpos, 999, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); // smash that puppy
|
|
}
|
|
}
|
|
|
|
void WP_Melee( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
gentity_t *tr_ent;
|
|
trace_t tr;
|
|
vec3_t mins, maxs, end;
|
|
int damage = ent->s.number ? (g_spskill->integer*2)+1 : 3;
|
|
float range = ent->s.number ? 64 : 32;
|
|
|
|
VectorMA( muzzle, range, forward, end );
|
|
|
|
VectorSet( maxs, 6, 6, 6 );
|
|
VectorScale( maxs, -1, mins );
|
|
|
|
gi.trace ( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT );
|
|
|
|
if ( tr.entityNum >= ENTITYNUM_WORLD )
|
|
{
|
|
return;
|
|
}
|
|
|
|
tr_ent = &g_entities[tr.entityNum];
|
|
|
|
if ( ent->client && !PM_DroidMelee( ent->client->NPC_class ) )
|
|
{
|
|
if ( ent->s.number || ent->alt_fire )
|
|
{
|
|
damage *= Q_irand( 2, 3 );
|
|
}
|
|
else
|
|
{
|
|
damage *= Q_irand( 1, 2 );
|
|
}
|
|
}
|
|
|
|
if ( tr_ent && tr_ent->takedamage )
|
|
{
|
|
int dflags = DAMAGE_NO_KNOCKBACK;
|
|
G_PlayEffect( G_EffectIndex( "melee/punch_impact" ), tr.endpos, forward );
|
|
//G_Sound( tr_ent, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) );
|
|
if ( ent->NPC && (ent->NPC->aiFlags&NPCAI_HEAVY_MELEE) )
|
|
{ //4x damage for heavy melee class
|
|
damage *= 4;
|
|
dflags &= ~DAMAGE_NO_KNOCKBACK;
|
|
dflags |= DAMAGE_DISMEMBER;
|
|
}
|
|
|
|
G_Damage( tr_ent, ent, ent, forward, tr.endpos, damage, dflags, MOD_MELEE );
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireTuskenRifle( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t start;
|
|
|
|
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
|
|
|
|
if ( !(ent->client->ps.forcePowersActive&(1<<FP_SEE))
|
|
|| ent->client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 )
|
|
{//force sight 2+ gives perfect aim
|
|
//FIXME: maybe force sight level 3 autoaims some?
|
|
if ( ent->NPC && ent->NPC->currentAim < 5 )
|
|
{
|
|
vec3_t angs;
|
|
|
|
vectoangles( forward, angs );
|
|
|
|
if ( ent->client->NPC_class == CLASS_IMPWORKER )
|
|
{//*sigh*, hack to make impworkers less accurate without affecteing imperial officer accuracy
|
|
angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
}
|
|
else
|
|
{
|
|
angs[PITCH] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) );
|
|
angs[YAW] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) );
|
|
}
|
|
|
|
AngleVectors( angs, forward, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
|
|
gentity_t *missile = CreateMissile( start, forward, TUSKEN_RIFLE_VEL, 10000, ent, qfalse );
|
|
|
|
missile->classname = "trifle_proj";
|
|
missile->s.weapon = WP_TUSKEN_RIFLE;
|
|
|
|
if ( ent->s.number < MAX_CLIENTS || g_spskill->integer >= 2 )
|
|
{
|
|
missile->damage = TUSKEN_RIFLE_DAMAGE_HARD;
|
|
}
|
|
else if ( g_spskill->integer > 0 )
|
|
{
|
|
missile->damage = TUSKEN_RIFLE_DAMAGE_MEDIUM;
|
|
}
|
|
else
|
|
{
|
|
missile->damage = TUSKEN_RIFLE_DAMAGE_EASY;
|
|
}
|
|
missile->dflags = DAMAGE_DEATH_KNOCKBACK;
|
|
|
|
missile->methodOfDeath = MOD_BRYAR;//???
|
|
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
|
|
// we don't want it to bounce forever
|
|
missile->bounceCount = 8;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void WP_FireNoghriStick( gentity_t *ent )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t dir, angs;
|
|
|
|
vectoangles( forward, angs );
|
|
|
|
if ( !(ent->client->ps.forcePowersActive&(1<<FP_SEE))
|
|
|| ent->client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 )
|
|
{//force sight 2+ gives perfect aim
|
|
//FIXME: maybe force sight level 3 autoaims some?
|
|
// add some slop to the main-fire direction
|
|
angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f
|
|
}
|
|
|
|
AngleVectors( angs, dir, NULL, NULL );
|
|
|
|
// FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot!
|
|
int velocity = 1200;
|
|
|
|
WP_TraceSetStart( ent, muzzle, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall
|
|
|
|
WP_MissileTargetHint(ent, muzzle, dir);
|
|
|
|
gentity_t *missile = CreateMissile( muzzle, dir, velocity, 10000, ent, qfalse );
|
|
|
|
missile->classname = "noghri_proj";
|
|
missile->s.weapon = WP_NOGHRI_STICK;
|
|
|
|
// Do the damages
|
|
if ( ent->s.number != 0 )
|
|
{
|
|
if ( g_spskill->integer == 0 )
|
|
{
|
|
missile->damage = 1;
|
|
}
|
|
else if ( g_spskill->integer == 1 )
|
|
{
|
|
missile->damage = 5;
|
|
}
|
|
else
|
|
{
|
|
missile->damage = 10;
|
|
}
|
|
}
|
|
|
|
// if ( ent->client )
|
|
// {
|
|
// if ( ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time )
|
|
// {
|
|
// // in overcharge mode, so doing double damage
|
|
// missile->flags |= FL_OVERCHARGED;
|
|
// damage *= 2;
|
|
// }
|
|
// }
|
|
|
|
missile->dflags = DAMAGE_NO_KNOCKBACK;
|
|
missile->methodOfDeath = MOD_BLASTER;
|
|
missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
|
|
missile->splashDamage = 0;
|
|
missile->splashRadius = 100;
|
|
missile->splashMethodOfDeath = MOD_GAS;
|
|
|
|
//Hmm: maybe spew gas on impact?
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void AddLeanOfs(const gentity_t *const ent, vec3_t point)
|
|
//---------------------------------------------------------
|
|
{
|
|
if(ent->client)
|
|
{
|
|
if(ent->client->ps.leanofs)
|
|
{
|
|
vec3_t right;
|
|
//add leaning offset
|
|
AngleVectors(ent->client->ps.viewangles, NULL, right, NULL);
|
|
VectorMA(point, (float)ent->client->ps.leanofs, right, point);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void SubtractLeanOfs(const gentity_t *const ent, vec3_t point)
|
|
//---------------------------------------------------------
|
|
{
|
|
if(ent->client)
|
|
{
|
|
if(ent->client->ps.leanofs)
|
|
{
|
|
vec3_t right;
|
|
//add leaning offset
|
|
AngleVectors( ent->client->ps.viewangles, NULL, right, NULL );
|
|
VectorMA( point, ent->client->ps.leanofs*-1, right, point );
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void ViewHeightFix(const gentity_t *const ent)
|
|
//---------------------------------------------------------
|
|
{
|
|
//FIXME: this is hacky and doesn't need to be here. Was only put here to make up
|
|
//for the times a crouch anim would be used but not actually crouching.
|
|
//When we start calcing eyepos (SPOT_HEAD) from the tag_eyes, we won't need
|
|
//this (or viewheight at all?)
|
|
if ( !ent )
|
|
return;
|
|
|
|
if ( !ent->client || !ent->NPC )
|
|
return;
|
|
|
|
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 )
|
|
return;//dead
|
|
|
|
if ( ent->client->ps.legsAnim == BOTH_CROUCH1IDLE || ent->client->ps.legsAnim == BOTH_CROUCH1 || ent->client->ps.legsAnim == BOTH_CROUCH1WALK )
|
|
{
|
|
if ( ent->client->ps.viewheight!=ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET )
|
|
ent->client->ps.viewheight = ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET;
|
|
}
|
|
else
|
|
{
|
|
if ( ent->client->ps.viewheight!=ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET )
|
|
ent->client->ps.viewheight = ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET;
|
|
}
|
|
}
|
|
|
|
qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod )
|
|
{
|
|
if ( mod != MOD_UNKNOWN )
|
|
{
|
|
switch( mod )
|
|
{
|
|
//standard weapons
|
|
case MOD_BRYAR:
|
|
case MOD_BRYAR_ALT:
|
|
case MOD_BLASTER:
|
|
case MOD_BLASTER_ALT:
|
|
case MOD_DISRUPTOR:
|
|
case MOD_SNIPER:
|
|
case MOD_BOWCASTER:
|
|
case MOD_BOWCASTER_ALT:
|
|
case MOD_ROCKET:
|
|
case MOD_ROCKET_ALT:
|
|
case MOD_CONC:
|
|
case MOD_CONC_ALT:
|
|
return qtrue;
|
|
break;
|
|
//non-alt standard
|
|
case MOD_REPEATER:
|
|
case MOD_DEMP2:
|
|
case MOD_FLECHETTE:
|
|
return qtrue;
|
|
break;
|
|
//emplaced gun
|
|
case MOD_EMPLACED:
|
|
return qtrue;
|
|
break;
|
|
//atst
|
|
case MOD_ENERGY:
|
|
case MOD_EXPLOSIVE:
|
|
if ( weapon == WP_ATST_MAIN || weapon == WP_ATST_SIDE )
|
|
{
|
|
return qtrue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if ( weapon != WP_NONE )
|
|
{
|
|
switch( weapon )
|
|
{
|
|
case WP_BRYAR_PISTOL:
|
|
case WP_BLASTER_PISTOL:
|
|
case WP_BLASTER:
|
|
case WP_DISRUPTOR:
|
|
case WP_BOWCASTER:
|
|
case WP_ROCKET_LAUNCHER:
|
|
case WP_CONCUSSION:
|
|
return qtrue;
|
|
break;
|
|
//non-alt standard
|
|
case WP_REPEATER:
|
|
case WP_DEMP2:
|
|
case WP_FLECHETTE:
|
|
if ( !alt_fire )
|
|
{
|
|
return qtrue;
|
|
}
|
|
break;
|
|
//emplaced gun
|
|
case WP_EMPLACED_GUN:
|
|
return qtrue;
|
|
break;
|
|
//atst
|
|
case WP_ATST_MAIN:
|
|
case WP_ATST_SIDE:
|
|
return qtrue;
|
|
break;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
LogAccuracyHit
|
|
===============
|
|
*/
|
|
qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
|
|
if( !target->takedamage ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if ( target == attacker ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if( !target->client ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if( !attacker->client ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if ( OnSameTeam( target, attacker ) ) {
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void CalcMuzzlePoint( gentity_t *const ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint, float lead_in )
|
|
//---------------------------------------------------------
|
|
{
|
|
vec3_t org;
|
|
mdxaBone_t boltMatrix;
|
|
|
|
if( !lead_in ) //&& ent->s.number != 0
|
|
{//Not players or melee
|
|
if( ent->client )
|
|
{
|
|
if ( ent->client->renderInfo.mPCalcTime >= level.time - FRAMETIME*2 )
|
|
{//Our muzz point was calced no more than 2 frames ago
|
|
VectorCopy(ent->client->renderInfo.muzzlePoint, muzzlePoint);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorCopy( ent->currentOrigin, muzzlePoint );
|
|
|
|
switch( ent->s.weapon )
|
|
{
|
|
case WP_BRYAR_PISTOL:
|
|
case WP_BLASTER_PISTOL:
|
|
ViewHeightFix(ent);
|
|
muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
|
|
muzzlePoint[2] -= 16;
|
|
VectorMA( muzzlePoint, 28, forward, muzzlePoint );
|
|
VectorMA( muzzlePoint, 6, vright, muzzlePoint );
|
|
break;
|
|
|
|
case WP_ROCKET_LAUNCHER:
|
|
case WP_CONCUSSION:
|
|
case WP_THERMAL:
|
|
ViewHeightFix(ent);
|
|
muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
|
|
muzzlePoint[2] -= 2;
|
|
break;
|
|
|
|
case WP_BLASTER:
|
|
ViewHeightFix(ent);
|
|
muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
|
|
muzzlePoint[2] -= 1;
|
|
if ( ent->s.number == 0 )
|
|
VectorMA( muzzlePoint, 12, forward, muzzlePoint ); // player, don't set this any lower otherwise the projectile will impact immediately when your back is to a wall
|
|
else
|
|
VectorMA( muzzlePoint, 2, forward, muzzlePoint ); // NPC, don't set too far forward otherwise the projectile can go through doors
|
|
|
|
VectorMA( muzzlePoint, 1, vright, muzzlePoint );
|
|
break;
|
|
|
|
case WP_SABER:
|
|
if(ent->NPC!=NULL &&
|
|
(ent->client->ps.torsoAnim == TORSO_WEAPONREADY2 ||
|
|
ent->client->ps.torsoAnim == BOTH_ATTACK2))//Sniper pose
|
|
{
|
|
ViewHeightFix(ent);
|
|
muzzle[2] += ent->client->ps.viewheight;//By eyes
|
|
}
|
|
else
|
|
{
|
|
muzzlePoint[2] += 16;
|
|
}
|
|
VectorMA( muzzlePoint, 8, forward, muzzlePoint );
|
|
VectorMA( muzzlePoint, 16, vright, muzzlePoint );
|
|
break;
|
|
|
|
case WP_BOT_LASER:
|
|
muzzlePoint[2] -= 16; //
|
|
break;
|
|
case WP_ATST_MAIN:
|
|
|
|
if (ent->count > 0)
|
|
{
|
|
ent->count = 0;
|
|
gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
|
|
ent->handLBolt,
|
|
&boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
|
|
NULL, ent->s.modelScale );
|
|
}
|
|
else
|
|
{
|
|
ent->count = 1;
|
|
gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
|
|
ent->handRBolt,
|
|
&boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
|
|
NULL, ent->s.modelScale );
|
|
}
|
|
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
|
|
|
|
VectorCopy(org,muzzlePoint);
|
|
|
|
break;
|
|
}
|
|
|
|
AddLeanOfs(ent, muzzlePoint);
|
|
}
|
|
|
|
// Muzzle point table...
|
|
vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS] =
|
|
{// Fwd, right, up.
|
|
{0, 0, 0 }, // WP_NONE,
|
|
{8 , 16, 0 }, // WP_SABER,
|
|
{12, 6, -6 }, // WP_BLASTER_PISTOL,
|
|
{12, 6, -6 }, // WP_BLASTER,
|
|
{12, 6, -6 }, // WP_DISRUPTOR,
|
|
{12, 2, -6 }, // WP_BOWCASTER,
|
|
{12, 4.5, -6 }, // WP_REPEATER,
|
|
{12, 6, -6 }, // WP_DEMP2,
|
|
{12, 6, -6 }, // WP_FLECHETTE,
|
|
{12, 8, -4 }, // WP_ROCKET_LAUNCHER,
|
|
{12, 0, -4 }, // WP_THERMAL,
|
|
{12, 0, -10 }, // WP_TRIP_MINE,
|
|
{12, 0, -4 }, // WP_DET_PACK,
|
|
{12, 8, -4 }, // WP_CONCUSSION,
|
|
{0 , 8, 0 }, // WP_MELEE,
|
|
{0, 0, 0 }, // WP_ATST_MAIN,
|
|
{0, 0, 0 }, // WP_ATST_SIDE,
|
|
{0 , 8, 0 }, // WP_STUN_BATON,
|
|
{12, 6, -6 }, // WP_BRYAR_PISTOL,
|
|
};
|
|
|
|
void WP_RocketLock( gentity_t *ent, float lockDist )
|
|
{
|
|
// Not really a charge weapon, but we still want to delay fire until the button comes up so that we can
|
|
// implement our alt-fire locking stuff
|
|
vec3_t ang;
|
|
trace_t tr;
|
|
|
|
vec3_t muzzleOffPoint, muzzlePoint, forward, right, up;
|
|
|
|
AngleVectors( ent->client->ps.viewangles, forward, right, up );
|
|
|
|
AngleVectors(ent->client->ps.viewangles, ang, NULL, NULL);
|
|
|
|
VectorCopy( ent->client->ps.origin, muzzlePoint );
|
|
VectorCopy(WP_MuzzlePoint[WP_ROCKET_LAUNCHER], muzzleOffPoint);
|
|
|
|
VectorMA(muzzlePoint, muzzleOffPoint[0], forward, muzzlePoint);
|
|
VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint);
|
|
muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2];
|
|
|
|
ang[0] = muzzlePoint[0] + ang[0]*lockDist;
|
|
ang[1] = muzzlePoint[1] + ang[1]*lockDist;
|
|
ang[2] = muzzlePoint[2] + ang[2]*lockDist;
|
|
|
|
gi.trace(&tr, muzzlePoint, NULL, NULL, ang, ent->client->ps.clientNum, MASK_PLAYERSOLID);
|
|
|
|
if (tr.fraction != 1 && tr.entityNum < ENTITYNUM_NONE && tr.entityNum != ent->client->ps.clientNum)
|
|
{
|
|
gentity_t *bgEnt = &g_entities[tr.entityNum];
|
|
if ( bgEnt && (bgEnt->s.powerups&PW_CLOAKED) )
|
|
{
|
|
ent->client->rocketLockIndex = ENTITYNUM_NONE;
|
|
ent->client->rocketLockTime = 0;
|
|
}
|
|
else if (bgEnt && bgEnt->s.eType == ET_PLAYER )
|
|
{
|
|
if (ent->client->rocketLockIndex == ENTITYNUM_NONE)
|
|
{
|
|
ent->client->rocketLockIndex = tr.entityNum;
|
|
ent->client->rocketLockTime = level.time;
|
|
}
|
|
else if (ent->client->rocketLockIndex != tr.entityNum && ent->client->rocketTargetTime < level.time)
|
|
{
|
|
ent->client->rocketLockIndex = tr.entityNum;
|
|
ent->client->rocketLockTime = level.time;
|
|
}
|
|
else if (ent->client->rocketLockIndex == tr.entityNum)
|
|
{
|
|
if (ent->client->rocketLockTime == -1)
|
|
{
|
|
ent->client->rocketLockTime = ent->client->rocketLastValidTime;
|
|
}
|
|
}
|
|
|
|
if (ent->client->rocketLockIndex == tr.entityNum)
|
|
{
|
|
ent->client->rocketTargetTime = level.time + 500;
|
|
}
|
|
}
|
|
}
|
|
else if (ent->client->rocketTargetTime < level.time)
|
|
{
|
|
ent->client->rocketLockIndex = ENTITYNUM_NONE;
|
|
ent->client->rocketLockTime = 0;
|
|
}
|
|
else
|
|
{
|
|
if (ent->client->rocketLockTime != -1)
|
|
{
|
|
ent->client->rocketLastValidTime = ent->client->rocketLockTime;
|
|
}
|
|
ent->client->rocketLockTime = -1;
|
|
}
|
|
}
|
|
|
|
#define VEH_HOMING_MISSILE_THINK_TIME 100
|
|
void WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon )
|
|
{
|
|
if ( !vehWeapon )
|
|
{//invalid vehicle weapon
|
|
return;
|
|
}
|
|
else if ( vehWeapon->bIsProjectile )
|
|
{//projectile entity
|
|
gentity_t *missile;
|
|
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 );
|
|
|
|
//QUERY: alt_fire true or not? Does it matter?
|
|
missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse );
|
|
if ( vehWeapon->bHasGravity )
|
|
{//TESTME: is this all we need to do?
|
|
missile->s.pos.trType = TR_GRAVITY;
|
|
}
|
|
|
|
missile->classname = "vehicle_proj";
|
|
|
|
missile->damage = vehWeapon->iDamage;
|
|
missile->splashDamage = vehWeapon->iSplashDamage;
|
|
missile->splashRadius = vehWeapon->fSplashRadius;
|
|
|
|
// HUGE HORRIBLE HACK
|
|
if (ent->owner && ent->owner->s.number==0)
|
|
{
|
|
missile->damage *= 20.0f;
|
|
missile->splashDamage *= 20.0f;
|
|
missile->splashRadius *= 20.0f;
|
|
}
|
|
|
|
//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_DISTORTION_TRAIL) )
|
|
{
|
|
missile->s.eFlags |= EF_DISTORTION_TRAIL;
|
|
}
|
|
if ( (vehWeapon->iFlags&VWF_RADAR) )
|
|
{
|
|
missile->s.eFlags |= EF_RADAROBJECT;
|
|
}
|
|
*/
|
|
missile->s.weapon = WP_BLASTER;//does this really matter?
|
|
|
|
// Make it easier to hit things
|
|
VectorCopy( mins, missile->mins );
|
|
VectorCopy( maxs, missile->maxs );
|
|
//some slightly different stuff for things with bboxes
|
|
if ( vehWeapon->fWidth || vehWeapon->fHeight )
|
|
{//we assume it's a rocket-like thing
|
|
missile->methodOfDeath = MOD_ROCKET;
|
|
missile->splashMethodOfDeath = 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_EMPLACED;//MOD_TURBLAST; //count as a heavy weap
|
|
missile->splashMethodOfDeath = MOD_EMPLACED;//MOD_TURBLAST;// ?SPLASH;
|
|
// we don't want it to bounce forever
|
|
missile->bounceCount = 8;
|
|
}
|
|
|
|
if ( vehWeapon->iHealth )
|
|
{//the missile can take damage
|
|
missile->health = vehWeapon->iHealth;
|
|
missile->takedamage = qtrue;
|
|
missile->contents = MASK_SHOT;
|
|
missile->e_DieFunc = dieF_WP_ExplosiveDie;//dieF_RocketDie;
|
|
}
|
|
|
|
//set veh as cgame side owner for purpose of fx overrides
|
|
if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot)
|
|
{
|
|
missile->owner = ent->m_pVehicle->m_pPilot;
|
|
}
|
|
else
|
|
{
|
|
missile->owner = ent;
|
|
}
|
|
missile->s.otherEntityNum = ent->s.number;
|
|
|
|
if ( vehWeapon->iLifeTime )
|
|
{//expire after a time
|
|
if ( vehWeapon->bExplodeOnExpire )
|
|
{//blow up when your lifetime is up
|
|
missile->e_ThinkFunc = thinkF_WP_Explode;//FIXME: custom func?
|
|
}
|
|
else
|
|
{//just remove yourself
|
|
missile->e_ThinkFunc = thinkF_G_FreeEntity;//FIXME: custom func?
|
|
}
|
|
missile->nextthink = level.time + vehWeapon->iLifeTime;
|
|
}
|
|
if ( vehWeapon->fHoming )
|
|
{//homing missile
|
|
//crap, we need to set up the homing stuff like it is in MP...
|
|
WP_RocketLock( ent, 16384 );
|
|
if ( ent->client && ent->client->rocketLockIndex != ENTITYNUM_NONE )
|
|
{
|
|
int dif = 0;
|
|
float rTime;
|
|
rTime = ent->client->rocketLockTime;
|
|
|
|
if (rTime == -1)
|
|
{
|
|
rTime = ent->client->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->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... FIXME: EXTERNALIZE
|
|
missile->speed = vehWeapon->fSpeed;
|
|
missile->angle = vehWeapon->fHoming;
|
|
if ( vehWeapon->iLifeTime )
|
|
{//expire after a time
|
|
missile->disconnectDebounceTime = level.time + vehWeapon->iLifeTime;
|
|
missile->lockCount = (int)(vehWeapon->bExplodeOnExpire);
|
|
}
|
|
missile->e_ThinkFunc = thinkF_rocketThink;
|
|
missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME;
|
|
//FIXME: implement radar in SP?
|
|
//missile->s.eFlags |= EF_RADAROBJECT;
|
|
}
|
|
}
|
|
|
|
ent->client->rocketLockIndex = ENTITYNUM_NONE;
|
|
ent->client->rocketLockTime = 0;
|
|
ent->client->rocketTargetTime = 0;
|
|
|
|
VectorCopy( dir, missile->movedir );
|
|
missile->random = 1.0f;//FIXME: externalize?
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{//traceline
|
|
//FIXME: implement
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
Vehicle_t *pVeh = ent->m_pVehicle;
|
|
|
|
if ( !pVeh )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pVeh->m_iRemovedSurfaces)
|
|
{ //can't fire when the thing is breaking apart
|
|
return;
|
|
}
|
|
|
|
|
|
if (ent->owner && ent->owner->client && ent->owner->client->ps.weapon!=WP_NONE)
|
|
{
|
|
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 ( true )//(pVeh->m_ulFlags & VEH_FLYING) || (pVeh->m_ulFlags & VEH_WINGSOPEN) ) // NOTE: Wings open also denotes that it has already launched.
|
|
{
|
|
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;
|
|
delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay;
|
|
aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect;
|
|
if ( 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
|
|
{
|
|
vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[vehWeaponIndex];
|
|
int i, cumulativeDelay = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
// Fire this muzzle.
|
|
if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
|
|
{
|
|
vec3_t start, dir;
|
|
// Prepare weapon delay.
|
|
if ( linkedFiring )
|
|
{//linked firing, add the delay up for each muzzle, then apply to all of them at the end
|
|
cumulativeDelay += delay;
|
|
}
|
|
else
|
|
{//normal delay - NOTE: always-linked muzzles use normal delay, not cumulative
|
|
pVeh->m_Muzzles[i].m_iMuzzleWait = level.time + delay;
|
|
}
|
|
|
|
if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot )
|
|
{//out of ammo!
|
|
}
|
|
else
|
|
{//have enough ammo to shoot
|
|
//take the ammo
|
|
pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot;
|
|
//do the firing
|
|
//FIXME: do we still need to calc the muzzle here in SP?
|
|
//WP_CalcVehMuzzle(ent, i);
|
|
VectorCopy( pVeh->m_Muzzles[i].m_vMuzzlePos, start );
|
|
VectorCopy( pVeh->m_Muzzles[i].m_vMuzzleDir, dir );
|
|
if ( aimCorrect )
|
|
{//auto-aim the missile at the crosshair
|
|
trace_t trace;
|
|
vec3_t end;
|
|
AngleVectors( pVeh->m_vOrientation, dir, NULL, NULL );
|
|
//VectorMA( ent->currentOrigin, 32768, dir, end );
|
|
VectorMA( ent->currentOrigin, 8192, dir, end );
|
|
gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT );
|
|
//if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid )
|
|
//bah, always point at end of trace
|
|
{
|
|
VectorSubtract( trace.endpos, start, dir );
|
|
VectorNormalize( dir );
|
|
}
|
|
|
|
// Mounted lazer cannon auto aiming at enemy
|
|
//-------------------------------------------
|
|
if (ent->enemy)
|
|
{
|
|
Vehicle_t* enemyVeh = G_IsRidingVehicle(ent->enemy);
|
|
|
|
// Don't Auto Aim At A Person Who Is Slide Breaking
|
|
if (!enemyVeh || !(enemyVeh->m_ulFlags&VEH_SLIDEBREAKING))
|
|
{
|
|
vec3_t dir2;
|
|
VectorSubtract( ent->enemy->currentOrigin, start, dir2 );
|
|
VectorNormalize( dir2 );
|
|
if (DotProduct(dir, dir2)>0.95f)
|
|
{
|
|
VectorCopy(dir2, dir);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//play the weapon's muzzle effect if we have one
|
|
if ( vehWeapon->iMuzzleFX )
|
|
{
|
|
G_PlayEffect( vehWeapon->iMuzzleFX, pVeh->m_Muzzles[i].m_vMuzzlePos, pVeh->m_Muzzles[i].m_vMuzzleDir );
|
|
}
|
|
WP_FireVehicleWeapon( ent, start, dir, vehWeapon );
|
|
}
|
|
|
|
if ( pVeh->weaponStatus[weaponNum].linked )//NOTE: we don't check linkedFiring here because that does *not* get set if the cannons are *always* linked
|
|
{//we're linking the weapon, so continue on and fire all appropriate muzzles
|
|
continue;
|
|
}
|
|
//ok, just break, we'll get in here again next frame and try the next muzzle...
|
|
break;
|
|
}
|
|
}
|
|
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_Muzzles[i].m_iMuzzleWait = level.time + cumulativeDelay;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WP_FireScepter( gentity_t *ent, qboolean alt_fire )
|
|
{//just a straight beam
|
|
int damage = 1;
|
|
vec3_t start, end;
|
|
trace_t tr;
|
|
gentity_t *traceEnt = NULL, *tent;
|
|
float shotRange = 8192;
|
|
qboolean render_impact = qtrue;
|
|
|
|
VectorCopy( muzzle, start );
|
|
WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
|
|
|
|
WP_MissileTargetHint(ent, start, forward);
|
|
VectorMA( start, shotRange, forward, end );
|
|
|
|
gi.trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
|
|
traceEnt = &g_entities[tr.entityNum];
|
|
|
|
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 );
|
|
tent->svFlags |= SVF_BROADCAST;
|
|
VectorCopy( muzzle, tent->s.origin2 );
|
|
|
|
if ( render_impact )
|
|
{
|
|
if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
|
|
{
|
|
// Create a simple impact type mark that doesn't last long in the world
|
|
G_PlayEffect( G_EffectIndex( "disruptor/flesh_impact" ), tr.endpos, tr.plane.normal );
|
|
|
|
int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR );
|
|
G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_EXTRA_KNOCKBACK, MOD_DISRUPTOR, hitLoc );
|
|
}
|
|
else
|
|
{
|
|
G_PlayEffect( G_EffectIndex( "disruptor/wall_impact" ), tr.endpos, tr.plane.normal );
|
|
}
|
|
}
|
|
|
|
/*
|
|
shotDist = shotRange * tr.fraction;
|
|
|
|
for ( dist = 0; dist < shotDist; dist += 64 )
|
|
{
|
|
//FIXME: on a really long shot, this could make a LOT of alerts in one frame...
|
|
VectorMA( start, dist, forward, spot );
|
|
AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
|
|
}
|
|
VectorMA( start, shotDist-4, forward, spot );
|
|
AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
|
|
*/
|
|
}
|
|
|
|
extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
|
|
//---------------------------------------------------------
|
|
void FireWeapon( gentity_t *ent, qboolean alt_fire )
|
|
//---------------------------------------------------------
|
|
{
|
|
float alert = 256;
|
|
Vehicle_t *pVeh = NULL;
|
|
|
|
// track shots taken for accuracy tracking.
|
|
ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++;
|
|
|
|
// If this is a vehicle, fire it's weapon and we're done.
|
|
if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE )
|
|
{
|
|
FireVehicleWeapon( ent, alt_fire );
|
|
return;
|
|
}
|
|
|
|
// set aiming directions
|
|
if ( ent->s.weapon == WP_DISRUPTOR && alt_fire )
|
|
{
|
|
if ( ent->NPC )
|
|
{
|
|
//snipers must use the angles they actually did their shot trace with
|
|
AngleVectors( ent->lastAngles, forward, vright, up );
|
|
}
|
|
}
|
|
else if ( ent->s.weapon == WP_ATST_SIDE || ent->s.weapon == WP_ATST_MAIN )
|
|
{
|
|
vec3_t delta1, enemy_org1, muzzle1;
|
|
vec3_t angleToEnemy1;
|
|
|
|
VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle1 );
|
|
|
|
if ( !ent->s.number )
|
|
{//player driving an AT-ST
|
|
//SIGH... because we can't anticipate alt-fire, must calc muzzle here and now
|
|
mdxaBone_t boltMatrix;
|
|
int bolt;
|
|
|
|
if ( ent->client->ps.weapon == WP_ATST_MAIN )
|
|
{//FIXME: alt_fire should fire both barrels, but slower?
|
|
if ( ent->alt_fire )
|
|
{
|
|
bolt = ent->handRBolt;
|
|
}
|
|
else
|
|
{
|
|
bolt = ent->handLBolt;
|
|
}
|
|
}
|
|
else
|
|
{// ATST SIDE weapons
|
|
if ( ent->alt_fire )
|
|
{
|
|
if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_light_blaster_cann" ) )
|
|
{//don't have it!
|
|
return;
|
|
}
|
|
bolt = ent->genericBolt2;
|
|
}
|
|
else
|
|
{
|
|
if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_concussion_charger" ) )
|
|
{//don't have it!
|
|
return;
|
|
}
|
|
bolt = ent->genericBolt1;
|
|
}
|
|
}
|
|
|
|
vec3_t yawOnlyAngles = {0, ent->currentAngles[YAW], 0};
|
|
if ( ent->currentAngles[YAW] != ent->client->ps.legsYaw )
|
|
{
|
|
yawOnlyAngles[YAW] = ent->client->ps.legsYaw;
|
|
}
|
|
gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, bolt, &boltMatrix, yawOnlyAngles, ent->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale );
|
|
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->renderInfo.muzzlePoint );
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent->client->renderInfo.muzzleDir );
|
|
ent->client->renderInfo.mPCalcTime = level.time;
|
|
|
|
AngleVectors( ent->client->ps.viewangles, forward, vright, up );
|
|
//CalcMuzzlePoint( ent, forward, vright, up, muzzle, 0 );
|
|
}
|
|
else if ( !ent->enemy )
|
|
{//an NPC with no enemy to auto-aim at
|
|
VectorCopy( ent->client->renderInfo.muzzleDir, forward );
|
|
}
|
|
else
|
|
{//NPC, auto-aim at enemy
|
|
CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
|
|
|
|
VectorSubtract (enemy_org1, muzzle1, delta1);
|
|
|
|
vectoangles ( delta1, angleToEnemy1 );
|
|
AngleVectors (angleToEnemy1, forward, vright, up);
|
|
}
|
|
}
|
|
else if ( ent->s.weapon == WP_BOT_LASER && ent->enemy )
|
|
{
|
|
vec3_t delta1, enemy_org1, muzzle1;
|
|
vec3_t angleToEnemy1;
|
|
|
|
CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
|
|
CalcEntitySpot( ent, SPOT_WEAPON, muzzle1 );
|
|
|
|
VectorSubtract (enemy_org1, muzzle1, delta1);
|
|
|
|
vectoangles ( delta1, angleToEnemy1 );
|
|
AngleVectors (angleToEnemy1, forward, vright, up);
|
|
}
|
|
else
|
|
{
|
|
if ( (pVeh = G_IsRidingVehicle( ent )) != NULL) //riding a vehicle
|
|
{//use our muzzleDir, can't use viewangles or vehicle m_vOrientation because we may be animated to shoot left or right...
|
|
if ((ent->s.eFlags&EF_NODRAW))//we're inside it
|
|
{
|
|
vec3_t aimAngles;
|
|
VectorCopy( ent->client->renderInfo.muzzleDir, forward );
|
|
vectoangles( forward, aimAngles );
|
|
//we're only keeping the yaw
|
|
aimAngles[PITCH] = ent->client->ps.viewangles[PITCH];
|
|
aimAngles[ROLL] = 0;
|
|
AngleVectors( aimAngles, forward, vright, up );
|
|
}
|
|
else
|
|
{
|
|
vec3_t actorRight;
|
|
vec3_t actorFwd;
|
|
|
|
VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle );
|
|
AngleVectors(ent->currentAngles, actorFwd, actorRight, 0);
|
|
|
|
// Aiming Left
|
|
//-------------
|
|
if (ent->client->ps.torsoAnim==BOTH_VT_ATL_G || ent->client->ps.torsoAnim==BOTH_VS_ATL_G)
|
|
{
|
|
VectorScale(actorRight, -1.0f, forward);
|
|
}
|
|
|
|
// Aiming Right
|
|
//--------------
|
|
else if (ent->client->ps.torsoAnim==BOTH_VT_ATR_G || ent->client->ps.torsoAnim==BOTH_VS_ATR_G)
|
|
{
|
|
VectorCopy(actorRight, forward);
|
|
}
|
|
|
|
// Aiming Forward
|
|
//----------------
|
|
else
|
|
{
|
|
VectorCopy(actorFwd, forward);
|
|
}
|
|
|
|
// If We Have An Enemy, Fudge The Aim To Hit The Enemy
|
|
if (ent->enemy)
|
|
{
|
|
vec3_t toEnemy;
|
|
VectorSubtract(ent->enemy->currentOrigin, ent->currentOrigin, toEnemy);
|
|
VectorNormalize(toEnemy);
|
|
if (DotProduct(toEnemy, forward)>0.75f &&
|
|
((ent->s.number==0 && !Q_irand(0,2)) || // the player has a 1 in 3 chance
|
|
(ent->s.number!=0 && !Q_irand(0,5)))) // other guys have a 1 in 6 chance
|
|
{
|
|
VectorCopy(toEnemy, forward);
|
|
}
|
|
else
|
|
{
|
|
forward[0] += Q_flrand(-0.1f, 0.1f);
|
|
forward[1] += Q_flrand(-0.1f, 0.1f);
|
|
forward[2] += Q_flrand(-0.1f, 0.1f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AngleVectors( ent->client->ps.viewangles, forward, vright, up );
|
|
}
|
|
}
|
|
|
|
ent->alt_fire = alt_fire;
|
|
if (!pVeh)
|
|
{
|
|
if (ent->NPC && (ent->NPC->scriptFlags&SCF_FIRE_WEAPON_NO_ANIM))
|
|
{
|
|
VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle );
|
|
VectorCopy( ent->client->renderInfo.muzzleDir, forward );
|
|
MakeNormalVectors(forward, vright, up);
|
|
}
|
|
else
|
|
{
|
|
CalcMuzzlePoint ( ent, forward, vright, up, muzzle , 0);
|
|
}
|
|
}
|
|
|
|
// fire the specific weapon
|
|
switch( ent->s.weapon )
|
|
{
|
|
// Player weapons
|
|
//-----------------
|
|
case WP_SABER:
|
|
return;
|
|
break;
|
|
|
|
case WP_BRYAR_PISTOL:
|
|
case WP_BLASTER_PISTOL:
|
|
WP_FireBryarPistol( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_BLASTER:
|
|
WP_FireBlaster( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_TUSKEN_RIFLE:
|
|
if ( alt_fire )
|
|
{
|
|
WP_FireTuskenRifle( ent );
|
|
}
|
|
else
|
|
{
|
|
WP_Melee( ent );
|
|
}
|
|
break;
|
|
|
|
case WP_DISRUPTOR:
|
|
alert = 50; // if you want it to alert enemies, remove this
|
|
WP_FireDisruptor( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_BOWCASTER:
|
|
WP_FireBowcaster( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_REPEATER:
|
|
WP_FireRepeater( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_DEMP2:
|
|
WP_FireDEMP2( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_FLECHETTE:
|
|
WP_FireFlechette( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_ROCKET_LAUNCHER:
|
|
WP_FireRocket( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_CONCUSSION:
|
|
if ( alt_fire )
|
|
{
|
|
WP_FireConcussionAlt( ent );
|
|
}
|
|
else
|
|
{
|
|
WP_FireConcussion( ent );
|
|
}
|
|
break;
|
|
|
|
case WP_THERMAL:
|
|
WP_FireThermalDetonator( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_TRIP_MINE:
|
|
alert = 0; // if you want it to alert enemies, remove this
|
|
WP_PlaceLaserTrap( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_DET_PACK:
|
|
alert = 0; // if you want it to alert enemies, remove this
|
|
WP_FireDetPack( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_BOT_LASER:
|
|
WP_BotLaser( ent );
|
|
break;
|
|
|
|
case WP_EMPLACED_GUN:
|
|
// doesn't care about whether it's alt-fire or not. We can do an alt-fire if needed
|
|
WP_EmplacedFire( ent );
|
|
break;
|
|
|
|
case WP_MELEE:
|
|
alert = 0; // if you want it to alert enemies, remove this
|
|
if ( !alt_fire || !g_debugMelee->integer )
|
|
{
|
|
WP_Melee( ent );
|
|
}
|
|
break;
|
|
|
|
case WP_ATST_MAIN:
|
|
WP_ATSTMainFire( ent );
|
|
break;
|
|
|
|
case WP_ATST_SIDE:
|
|
|
|
// TEMP
|
|
if ( alt_fire )
|
|
{
|
|
// WP_FireRocket( ent, qfalse );
|
|
WP_ATSTSideAltFire(ent);
|
|
}
|
|
else
|
|
{
|
|
// FIXME!
|
|
/* if ( ent->s.number == 0
|
|
&& ent->client->NPC_class == CLASS_VEHICLE
|
|
&& vehicleData[((CVehicleNPC *)ent->NPC)->m_iVehicleTypeID].type == VH_FIGHTER )
|
|
{
|
|
WP_ATSTMainFire( ent );
|
|
}
|
|
else*/
|
|
{
|
|
WP_ATSTSideFire(ent);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WP_TIE_FIGHTER:
|
|
// TEMP
|
|
WP_EmplacedFire( ent );
|
|
break;
|
|
|
|
case WP_RAPID_FIRE_CONC:
|
|
// TEMP
|
|
if ( alt_fire )
|
|
{
|
|
WP_FireRepeater( ent, alt_fire );
|
|
}
|
|
else
|
|
{
|
|
WP_EmplacedFire( ent );
|
|
}
|
|
break;
|
|
|
|
case WP_STUN_BATON:
|
|
WP_FireStunBaton( ent, alt_fire );
|
|
break;
|
|
|
|
// case WP_BLASTER_PISTOL:
|
|
case WP_JAWA:
|
|
WP_FireBryarPistol( ent, qfalse ); // never an alt-fire?
|
|
break;
|
|
|
|
case WP_SCEPTER:
|
|
WP_FireScepter( ent, alt_fire );
|
|
break;
|
|
|
|
case WP_NOGHRI_STICK:
|
|
if ( !alt_fire )
|
|
{
|
|
WP_FireNoghriStick( ent );
|
|
}
|
|
//else does melee attack/damage/func
|
|
break;
|
|
|
|
case WP_TUSKEN_STAFF:
|
|
default:
|
|
return;
|
|
break;
|
|
}
|
|
|
|
if ( !ent->s.number )
|
|
{
|
|
if ( ent->s.weapon == WP_FLECHETTE || (ent->s.weapon == WP_BOWCASTER && !alt_fire) )
|
|
{//these can fire multiple shots, count them individually within the firing functions
|
|
}
|
|
else if ( W_AccuracyLoggableWeapon( ent->s.weapon, alt_fire, MOD_UNKNOWN ) )
|
|
{
|
|
ent->client->sess.missionStats.shotsFired++;
|
|
}
|
|
}
|
|
// We should probably just use this as a default behavior, in special cases, just set alert to false.
|
|
if ( ent->s.number == 0 && alert > 0 )
|
|
{
|
|
if ( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD//FIXME: check for sand contents type?
|
|
&& ent->s.weapon != WP_STUN_BATON
|
|
&& ent->s.weapon != WP_MELEE
|
|
&& ent->s.weapon != WP_TUSKEN_STAFF
|
|
&& ent->s.weapon != WP_THERMAL
|
|
&& ent->s.weapon != WP_TRIP_MINE
|
|
&& ent->s.weapon != WP_DET_PACK )
|
|
{//the vibration of the shot carries through your feet into the ground
|
|
AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED, qfalse, qtrue );
|
|
}
|
|
else
|
|
{//an in-air alert
|
|
AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED );
|
|
}
|
|
AddSightEvent( ent, muzzle, alert*2, AEL_DISCOVERED, 20 );
|
|
}
|
|
}
|
|
|
|
//NOTE: Emplaced gun moved to g_emplaced.cpp
|
|
|
|
/*QUAKED misc_weapon_shooter (1 0 0) (-8 -8 -8) (8 8 8) ALTFIRE TOGGLE
|
|
ALTFIRE - fire the alt-fire of the chosen weapon
|
|
TOGGLE - keep firing until used again (fires at intervals of "wait")
|
|
|
|
"wait" - debounce time between refires (defaults to 500)
|
|
"delay" - speed of WP_THERMAL (default is 900)
|
|
"random" - ranges from 0 to random, added to wait (defaults to 0)
|
|
|
|
"target" - what to aim at (will update aim every frame if it's a moving target)
|
|
|
|
"weapon" - specify the weapon to use (default is WP_BLASTER)
|
|
WP_BRYAR_PISTOL
|
|
WP_BLASTER
|
|
WP_DISRUPTOR
|
|
WP_BOWCASTER
|
|
WP_REPEATER
|
|
WP_DEMP2
|
|
WP_FLECHETTE
|
|
WP_ROCKET_LAUNCHER
|
|
WP_CONCUSSION
|
|
WP_THERMAL
|
|
WP_TRIP_MINE
|
|
WP_DET_PACK
|
|
WP_STUN_BATON
|
|
WP_EMPLACED_GUN
|
|
WP_BOT_LASER
|
|
WP_TURRET
|
|
WP_ATST_MAIN
|
|
WP_ATST_SIDE
|
|
WP_TIE_FIGHTER
|
|
WP_RAPID_FIRE_CONC
|
|
WP_BLASTER_PISTOL
|
|
*/
|
|
void misc_weapon_shooter_fire( gentity_t *self )
|
|
{
|
|
FireWeapon( self, (self->spawnflags&1) );
|
|
if ( (self->spawnflags&2) )
|
|
{//repeat
|
|
self->e_ThinkFunc = thinkF_misc_weapon_shooter_fire;
|
|
if (self->random)
|
|
{
|
|
self->nextthink = level.time + self->wait + (int)(random()*self->random);
|
|
}
|
|
else
|
|
{
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
}
|
|
}
|
|
|
|
void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
{
|
|
if ( self->e_ThinkFunc == thinkF_misc_weapon_shooter_fire )
|
|
{//repeating fire, stop
|
|
self->e_ThinkFunc = thinkF_NULL;
|
|
self->nextthink = -1;
|
|
return;
|
|
}
|
|
//otherwise, fire
|
|
misc_weapon_shooter_fire( self );
|
|
}
|
|
|
|
void misc_weapon_shooter_aim( gentity_t *self )
|
|
{
|
|
//update my aim
|
|
if ( self->target )
|
|
{
|
|
gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
|
|
if ( targ )
|
|
{
|
|
self->enemy = targ;
|
|
VectorSubtract( targ->currentOrigin, self->currentOrigin, self->client->renderInfo.muzzleDir );
|
|
VectorCopy( targ->currentOrigin, self->pos1 );
|
|
vectoangles( self->client->renderInfo.muzzleDir, self->client->ps.viewangles );
|
|
SetClientViewAngle( self, self->client->ps.viewangles );
|
|
//FIXME: don't keep doing this unless target is a moving target?
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
self->enemy = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern stringID_table_t WPTable[];
|
|
void SP_misc_weapon_shooter( gentity_t *self )
|
|
{
|
|
//alloc a client just for the weapon code to use
|
|
self->client = (gclient_s *)gi.Malloc(sizeof(gclient_s), TAG_G_ALLOC, qtrue);
|
|
|
|
//set weapon
|
|
self->s.weapon = self->client->ps.weapon = WP_BLASTER;
|
|
if ( self->paintarget )
|
|
{//use a different weapon
|
|
self->s.weapon = self->client->ps.weapon = GetIDForString( WPTable, self->paintarget );
|
|
}
|
|
|
|
//set where our muzzle is
|
|
VectorCopy( self->s.origin, self->client->renderInfo.muzzlePoint );
|
|
//permanently updated
|
|
self->client->renderInfo.mPCalcTime = Q3_INFINITE;
|
|
|
|
//set up to link
|
|
if ( self->target )
|
|
{
|
|
self->e_ThinkFunc = thinkF_misc_weapon_shooter_aim;
|
|
self->nextthink = level.time + START_TIME_LINK_ENTS;
|
|
}
|
|
else
|
|
{//just set aim angles
|
|
VectorCopy( self->s.angles, self->client->ps.viewangles );
|
|
AngleVectors( self->s.angles, self->client->renderInfo.muzzleDir, NULL, NULL );
|
|
}
|
|
|
|
//set up to fire when used
|
|
self->e_UseFunc = useF_misc_weapon_shooter_use;
|
|
|
|
if ( !self->wait )
|
|
{
|
|
self->wait = 500;
|
|
}
|
|
}
|