mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-15 08:52:15 +00:00
4597b03873
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
1699 lines
47 KiB
C++
1699 lines
47 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 1999 - 2005, Id Software, Inc.
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
// g_weapon.c
|
|
// perform the server side effects of a weapon firing
|
|
|
|
#include "g_local.h"
|
|
#include "g_functions.h"
|
|
#include "anims.h"
|
|
#include "b_local.h"
|
|
#include "wp_saber.h"
|
|
#include "g_vehicles.h"
|
|
#include "w_local.h"
|
|
#include "../cgame/cg_local.h"
|
|
|
|
vec3_t forwardVec, vrightVec, up;
|
|
vec3_t muzzle;
|
|
|
|
gentity_t *ent_list[MAX_GENTITIES];
|
|
extern cvar_t *g_debugMelee;
|
|
|
|
// some naughty little things that are used cg side
|
|
int g_rocketLockEntNum = ENTITYNUM_NONE;
|
|
int g_rocketLockTime = 0;
|
|
int g_rocketSlackTime = 0;
|
|
|
|
// 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];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
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, (EG2_Collision)0, 0 );
|
|
|
|
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 )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void WP_Stick( gentity_t *missile, trace_t *trace, float fudge_distance )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
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 forwardVec={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, forwardVec, NULL, NULL );
|
|
}
|
|
|
|
if ( self->fxID > 0 )
|
|
{
|
|
G_PlayEffect( self->fxID, self->currentOrigin, forwardVec );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
bool WP_MissileTargetHint(gentity_t* shooter, vec3_t start, vec3_t out)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
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 forwardVec, 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, forwardVec, muzzlePoint );
|
|
VectorMA( muzzlePoint, 6, vrightVec, 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, forwardVec, 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, forwardVec, muzzlePoint ); // NPC, don't set too far forwardVec otherwise the projectile can go through doors
|
|
|
|
VectorMA( muzzlePoint, 1, vrightVec, 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, forwardVec, muzzlePoint );
|
|
VectorMA( muzzlePoint, 16, vrightVec, 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, forwardVec, right, up;
|
|
|
|
AngleVectors( ent->client->ps.viewangles, forwardVec, 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], forwardVec, 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, (EG2_Collision)0, 0);
|
|
|
|
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)
|
|
{
|
|
//Should only be for speeders - mainly for t2_trip
|
|
if (ent->m_pVehicle->m_pVehicleInfo && ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER)
|
|
{
|
|
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;
|
|
missile->s.otherEntityNum2 = (vehWeapon-&g_vehWeaponInfo[0]);
|
|
|
|
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 WP_VehLeadCrosshairVeh( gentity_t *camTraceEnt, vec3_t newEnd, const vec3_t dir, const vec3_t shotStart, vec3_t shotDir )
|
|
{
|
|
//FIXME: implement from MP?
|
|
}
|
|
|
|
qboolean WP_VehCheckTraceFromCamPos( gentity_t *ent, const vec3_t shotStart, vec3_t shotDir )
|
|
{
|
|
//FIXME: implement from MP?
|
|
return qfalse;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
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 ( pVeh->m_pVehicleInfo &&
|
|
(pVeh->m_pVehicleInfo->type != VH_FIGHTER || (pVeh->m_ulFlags&VEH_WINGSOPEN)) ) // NOTE: Wings open also denotes that it has already launched.
|
|
{//fighters can only fire when wings are open
|
|
int weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE;
|
|
int delay = 1000;
|
|
qboolean aimCorrect = qfalse;
|
|
qboolean linkedFiring = qfalse;
|
|
|
|
if ( !alt_fire )
|
|
{
|
|
weaponNum = 0;
|
|
}
|
|
else
|
|
{
|
|
weaponNum = 1;
|
|
}
|
|
|
|
vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID;
|
|
|
|
if ( pVeh->weaponStatus[weaponNum].ammo <= 0 )
|
|
{//no ammo for this weapon
|
|
if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
|
|
{// let the client know he's out of ammo
|
|
int i;
|
|
//but only if one of the vehicle muzzles is actually ready to fire this weapon
|
|
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
|
|
{
|
|
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
|
|
{//this muzzle doesn't match the weapon we're trying to use
|
|
continue;
|
|
}
|
|
if ( pVeh->m_iMuzzleTag[i] != -1
|
|
&& pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
|
|
{//this one would have fired, send the no ammo message
|
|
G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay;
|
|
aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect;
|
|
if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2//always linked
|
|
|| ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable
|
|
&& pVeh->weaponStatus[weaponNum].linked ) )//linked
|
|
{//we're linking the primary or alternate weapons, so we'll do *all* the muzzles
|
|
linkedFiring = qtrue;
|
|
}
|
|
|
|
if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS )
|
|
{//invalid vehicle weapon
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int i, numMuzzles = 0, numMuzzlesReady = 0, cumulativeDelay = 0, cumulativeAmmo = 0;
|
|
qboolean sentAmmoWarning = qfalse;
|
|
|
|
vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[vehWeaponIndex];
|
|
|
|
if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2 )
|
|
{//always linked weapons don't accumulate delay, just use specified delay
|
|
cumulativeDelay = delay;
|
|
}
|
|
//find out how many we've got for this weapon
|
|
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
|
|
{
|
|
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
|
|
{//this muzzle doesn't match the weapon we're trying to use
|
|
continue;
|
|
}
|
|
if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
|
|
{
|
|
numMuzzlesReady++;
|
|
}
|
|
if ( pVeh->m_pVehicleInfo->weapMuzzle[pVeh->weaponStatus[weaponNum].nextMuzzle] != vehWeaponIndex )
|
|
{//Our designated next muzzle for this weapon isn't valid for this weapon (happens when ships fire for the first time)
|
|
//set the next to this one
|
|
pVeh->weaponStatus[weaponNum].nextMuzzle = i;
|
|
}
|
|
if ( linkedFiring )
|
|
{
|
|
cumulativeAmmo += vehWeapon->iAmmoPerShot;
|
|
if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable != 2 )
|
|
{//always linked weapons don't accumulate delay, just use specified delay
|
|
cumulativeDelay += delay;
|
|
}
|
|
}
|
|
numMuzzles++;
|
|
}
|
|
|
|
if ( linkedFiring )
|
|
{//firing all muzzles at once
|
|
if ( numMuzzlesReady != numMuzzles )
|
|
{//can't fire all linked muzzles yet
|
|
return;
|
|
}
|
|
else
|
|
{//can fire all linked muzzles, check ammo
|
|
if ( pVeh->weaponStatus[weaponNum].ammo < cumulativeAmmo )
|
|
{//can't fire, not enough ammo
|
|
if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
|
|
{// let the client know he's out of ammo
|
|
G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
|
|
{
|
|
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
|
|
{//this muzzle doesn't match the weapon we're trying to use
|
|
continue;
|
|
}
|
|
if ( !linkedFiring
|
|
&& i != pVeh->weaponStatus[weaponNum].nextMuzzle )
|
|
{//we're only firing one muzzle and this isn't it
|
|
continue;
|
|
}
|
|
|
|
// Fire this muzzle.
|
|
if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
|
|
{
|
|
vec3_t start, dir;
|
|
|
|
if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot )
|
|
{//out of ammo!
|
|
if ( !sentAmmoWarning )
|
|
{
|
|
sentAmmoWarning = qtrue;
|
|
if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
|
|
{// let the client know he's out of ammo
|
|
G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{//have enough ammo to shoot
|
|
//do the firing
|
|
//WP_CalcVehMuzzle(ent, i);
|
|
VectorCopy( pVeh->m_Muzzles[i].m_vMuzzlePos, start );
|
|
VectorCopy( pVeh->m_Muzzles[i].m_vMuzzleDir, dir );
|
|
if ( WP_VehCheckTraceFromCamPos( ent, start, dir ) )
|
|
{//auto-aim at whatever crosshair would be over from camera's point of view (if closer)
|
|
}
|
|
else if ( aimCorrect )
|
|
{//auto-aim the missile at the crosshair if there's anything there
|
|
trace_t trace;
|
|
vec3_t end;
|
|
vec3_t ang;
|
|
vec3_t fixedDir;
|
|
|
|
if (pVeh->m_pVehicleInfo->type == VH_SPEEDER)
|
|
{
|
|
VectorSet(ang, 0.0f, pVeh->m_vOrientation[1], 0.0f);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(pVeh->m_vOrientation, ang);
|
|
}
|
|
AngleVectors( ang, fixedDir, NULL, NULL );
|
|
//VectorMA( ent->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, (EG2_Collision)0, 0 );
|
|
if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid )
|
|
{
|
|
vec3_t newEnd;
|
|
VectorCopy( trace.endpos, newEnd );
|
|
WP_VehLeadCrosshairVeh( &g_entities[trace.entityNum], newEnd, fixedDir, start, dir );
|
|
}
|
|
}
|
|
|
|
//play the weapon's muzzle effect if we have one
|
|
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 ( linkedFiring )
|
|
{//we're linking the weapon, so continue on and fire all appropriate muzzles
|
|
continue;
|
|
}
|
|
//else just firing one
|
|
//take the ammo, set the next muzzle and set the delay on it
|
|
if ( numMuzzles > 1 )
|
|
{//more than one, look for it
|
|
int nextMuzzle = pVeh->weaponStatus[weaponNum].nextMuzzle;
|
|
while ( 1 )
|
|
{
|
|
nextMuzzle++;
|
|
if ( nextMuzzle >= MAX_VEHICLE_MUZZLES )
|
|
{
|
|
nextMuzzle = 0;
|
|
}
|
|
if ( nextMuzzle == pVeh->weaponStatus[weaponNum].nextMuzzle )
|
|
{//WTF? Wrapped without finding another valid one!
|
|
break;
|
|
}
|
|
if ( pVeh->m_pVehicleInfo->weapMuzzle[nextMuzzle] == vehWeaponIndex )
|
|
{//this is the next muzzle for this weapon
|
|
pVeh->weaponStatus[weaponNum].nextMuzzle = nextMuzzle;
|
|
break;
|
|
}
|
|
}
|
|
}//else, just stay on the one we just fired
|
|
//set the delay on the next muzzle
|
|
pVeh->m_Muzzles[pVeh->weaponStatus[weaponNum].nextMuzzle].m_iMuzzleWait = level.time + delay;
|
|
//take away the ammo
|
|
pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot;
|
|
//NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
|
|
if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
|
|
{
|
|
((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
|
|
}
|
|
//done!
|
|
//we'll get in here again next frame and try the next muzzle...
|
|
//return;
|
|
return;
|
|
}
|
|
}
|
|
//we went through all the muzzles, so apply the cumulative delay and ammo cost
|
|
if ( cumulativeAmmo )
|
|
{//taking ammo one shot at a time
|
|
//take the ammo
|
|
pVeh->weaponStatus[weaponNum].ammo -= cumulativeAmmo;
|
|
//NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
|
|
if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
|
|
{
|
|
((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
|
|
}
|
|
}
|
|
if ( cumulativeDelay )
|
|
{//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles
|
|
for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
|
|
{
|
|
if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
|
|
{//this muzzle doesn't match the weapon we're trying to use
|
|
continue;
|
|
}
|
|
//apply the cumulative delay
|
|
pVeh->m_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, forwardVec);
|
|
VectorMA( start, shotRange, forwardVec, 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, forwardVec, 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, forwardVec, spot );
|
|
AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
|
|
}
|
|
VectorMA( start, shotDist-4, forwardVec, 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, forwardVec, vrightVec, 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, forwardVec, vrightVec, up );
|
|
//CalcMuzzlePoint( ent, forwardVec, vrightVec, up, muzzle, 0 );
|
|
}
|
|
else if ( !ent->enemy )
|
|
{//an NPC with no enemy to auto-aim at
|
|
VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec );
|
|
}
|
|
else
|
|
{//NPC, auto-aim at enemy
|
|
CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
|
|
|
|
VectorSubtract (enemy_org1, muzzle1, delta1);
|
|
|
|
vectoangles ( delta1, angleToEnemy1 );
|
|
AngleVectors (angleToEnemy1, forwardVec, vrightVec, 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, forwardVec, vrightVec, 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, forwardVec );
|
|
vectoangles( forwardVec, aimAngles );
|
|
//we're only keeping the yaw
|
|
aimAngles[PITCH] = ent->client->ps.viewangles[PITCH];
|
|
aimAngles[ROLL] = 0;
|
|
AngleVectors( aimAngles, forwardVec, vrightVec, 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, forwardVec);
|
|
}
|
|
|
|
// Aiming Right
|
|
//--------------
|
|
else if (ent->client->ps.torsoAnim==BOTH_VT_ATR_G || ent->client->ps.torsoAnim==BOTH_VS_ATR_G)
|
|
{
|
|
VectorCopy(actorRight, forwardVec);
|
|
}
|
|
|
|
// Aiming Forward
|
|
//----------------
|
|
else
|
|
{
|
|
VectorCopy(actorFwd, forwardVec);
|
|
}
|
|
|
|
// 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, forwardVec)>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, forwardVec);
|
|
}
|
|
else
|
|
{
|
|
forwardVec[0] += Q_flrand(-0.1f, 0.1f);
|
|
forwardVec[1] += Q_flrand(-0.1f, 0.1f);
|
|
forwardVec[2] += Q_flrand(-0.1f, 0.1f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AngleVectors( ent->client->ps.viewangles, forwardVec, vrightVec, 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, forwardVec );
|
|
MakeNormalVectors(forwardVec, vrightVec, up);
|
|
}
|
|
else
|
|
{
|
|
CalcMuzzlePoint ( ent, forwardVec, vrightVec, 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:
|
|
WP_Concussion( ent, alt_fire );
|
|
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, (qboolean)((self->spawnflags&1) != 0) );
|
|
if ( (self->spawnflags&2) )
|
|
{//repeat
|
|
self->e_ThinkFunc = thinkF_misc_weapon_shooter_fire;
|
|
if (self->random)
|
|
{
|
|
self->nextthink = level.time + self->wait + (int)(Q_flrand(0.0f, 1.0f)*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_t *)gi.Malloc(sizeof(gclient_t), 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;
|
|
}
|
|
}
|