2022-09-18 15:37:21 +00:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
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/>.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
# include "g_headers.h"
# include "g_local.h"
# include "anims.h"
# include "b_local.h"
# include "bg_local.h"
# include "g_functions.h"
# include "wp_saber.h"
# include "../../code/qcommon/tri_coll_test.h"
2022-10-15 13:53:53 +00:00
# include "../cgame/FxScheduler.h"
2022-11-03 22:38:34 +00:00
# include <JKVR/VrClientInfo.h>
2022-09-18 15:37:21 +00:00
# define MAX_SABER_VICTIMS 16
static int victimEntityNum [ MAX_SABER_VICTIMS ] ;
static float totalDmg [ MAX_SABER_VICTIMS ] ;
static vec3_t dmgDir [ MAX_SABER_VICTIMS ] ;
static vec3_t dmgSpot [ MAX_SABER_VICTIMS ] ;
static float dmgFraction [ MAX_SABER_VICTIMS ] ;
static int hitLoc [ MAX_SABER_VICTIMS ] ;
static qboolean hitDismember [ MAX_SABER_VICTIMS ] ;
static int hitDismemberLoc [ MAX_SABER_VICTIMS ] ;
static vec3_t saberHitLocation , saberHitNormal = { 0 , 0 , 1.0 } ;
static float saberHitFraction ;
static float sabersCrossed ;
static int saberHitEntity ;
static int numVictims = 0 ;
# define SABER_PITCH_HACK 90
extern cvar_t * g_timescale ;
extern cvar_t * g_dismemberment ;
extern qboolean Q3_TaskIDPending ( gentity_t * ent , taskID_t taskType ) ;
extern qboolean G_ClearViewEntity ( gentity_t * ent ) ;
extern void G_SetViewEntity ( gentity_t * self , gentity_t * viewEntity ) ;
extern qboolean G_ControlledByPlayer ( gentity_t * self ) ;
extern void G_AddVoiceEvent ( gentity_t * self , int event , int speakDebounceTime ) ;
extern void CG_ChangeWeapon ( int num ) ;
extern void G_AngerAlert ( gentity_t * self ) ;
extern void G_ReflectMissile ( gentity_t * ent , gentity_t * missile , vec3_t forward ) ;
extern int G_CheckLedgeDive ( gentity_t * self , float checkDist , vec3_t checkVel , qboolean tryOpposite , qboolean tryPerp ) ;
extern void G_BounceMissile ( gentity_t * ent , trace_t * trace ) ;
extern qboolean G_PointInBounds ( const vec3_t point , const vec3_t mins , const vec3_t maxs ) ;
extern void G_SoundOnEnt ( gentity_t * ent , soundChannel_t channel , const char * soundPath ) ;
extern void NPC_UseResponse ( gentity_t * self , gentity_t * user , qboolean useWhenDone ) ;
extern void WP_FireDreadnoughtBeam ( gentity_t * ent ) ;
extern void G_MissileImpacted ( gentity_t * ent , gentity_t * other , vec3_t impactPos , vec3_t normal , int hitLoc = HL_NONE ) ;
extern evasionType_t Jedi_SaberBlockGo ( gentity_t * self , usercmd_t * cmd , vec3_t pHitloc , vec3_t phitDir , gentity_t * incoming , float dist = 0.0f ) ;
extern int PM_PickAnim ( gentity_t * self , int minAnim , int maxAnim ) ;
extern void NPC_SetPainEvent ( gentity_t * self ) ;
extern qboolean PM_SwimmingAnim ( int anim ) ;
extern qboolean PM_InAnimForSaberMove ( int anim , int saberMove ) ;
extern qboolean PM_SpinningSaberAnim ( int anim ) ;
extern qboolean PM_SaberInSpecialAttack ( int anim ) ;
extern qboolean PM_SaberInAttack ( int move ) ;
extern qboolean PM_SaberInTransition ( int move ) ;
extern qboolean PM_SaberInStart ( int move ) ;
extern qboolean PM_SaberInTransitionAny ( int move ) ;
extern qboolean PM_SaberInBounce ( int move ) ;
extern qboolean PM_SaberInParry ( int move ) ;
extern qboolean PM_SaberInKnockaway ( int move ) ;
extern qboolean PM_SaberInBrokenParry ( int move ) ;
extern qboolean PM_SpinningSaberAnim ( int anim ) ;
extern int PM_SaberBounceForAttack ( int move ) ;
extern int PM_BrokenParryForAttack ( int move ) ;
extern int PM_KnockawayForParry ( int move ) ;
extern qboolean PM_FlippingAnim ( int anim ) ;
extern qboolean PM_RollingAnim ( int anim ) ;
extern qboolean PM_CrouchAnim ( int anim ) ;
extern qboolean PM_SaberInIdle ( int move ) ;
extern qboolean PM_SaberInReflect ( int move ) ;
extern qboolean PM_InSpecialJump ( int anim ) ;
extern qboolean PM_InKnockDown ( playerState_t * ps ) ;
extern int PM_PowerLevelForSaberAnim ( playerState_t * ps ) ;
extern void PM_VelocityForSaberMove ( playerState_t * ps , vec3_t throwDir ) ;
extern qboolean PM_VelocityForBlockedMove ( playerState_t * ps , vec3_t throwDir ) ;
extern int Jedi_ReCalcParryTime ( gentity_t * self , evasionType_t evasionType ) ;
extern qboolean Jedi_DodgeEvasion ( gentity_t * self , gentity_t * shooter , trace_t * tr , int hitLoc ) ;
extern void Jedi_PlayDeflectSound ( gentity_t * self ) ;
extern void Jedi_PlayBlockedPushSound ( gentity_t * self ) ;
extern qboolean Jedi_WaitingAmbush ( gentity_t * self ) ;
extern void Jedi_Ambush ( gentity_t * self ) ;
extern qboolean Jedi_SaberBusy ( gentity_t * self ) ;
void WP_ForcePowerStart ( gentity_t * self , forcePowers_t forcePower , int overrideAmt ) ;
qboolean WP_ForcePowerUsable ( gentity_t * self , forcePowers_t forcePower , int overrideAmt ) ;
void WP_SaberInFlightReflectCheck ( gentity_t * self , usercmd_t * ucmd ) ;
void WP_SaberDrop ( gentity_t * self , gentity_t * saber ) ;
qboolean WP_SaberLose ( gentity_t * self , vec3_t throwDir ) ;
void WP_SaberReturn ( gentity_t * self , gentity_t * saber ) ;
void WP_SaberBlock ( gentity_t * saber , vec3_t hitloc , qboolean missleBlock ) ;
void WP_SaberBlockNonRandom ( gentity_t * self , vec3_t hitloc , qboolean missileBlock ) ;
void ForceThrow ( gentity_t * self , qboolean pull ) ;
qboolean WP_ForcePowerAvailable ( gentity_t * self , forcePowers_t forcePower , int overrideAmt ) ;
void WP_ForcePowerDrain ( gentity_t * self , forcePowers_t forcePower , int overrideAmt ) ;
2022-10-13 22:19:44 +00:00
extern cvar_t * g_saberAutoDeflect1stPerson ;
2022-09-18 15:37:21 +00:00
extern cvar_t * g_saberAutoBlocking ;
extern cvar_t * g_saberRealisticCombat ;
extern int g_crosshairEntNum ;
int g_saberFlashTime = 0 ;
vec3_t g_saberFlashPos = { 0 , 0 , 0 } ;
int forcePowerNeeded [ NUM_FORCE_POWERS ] =
{
0 , //FP_HEAL,//instant
10 , //FP_LEVITATION,//hold/duration
50 , //FP_SPEED,//duration
15 , //FP_PUSH,//hold/duration
15 , //FP_PULL,//hold/duration
20 , //FP_TELEPATHY,//instant
1 , //FP_GRIP,//hold/duration - FIXME: 30?
1 , //FP_LIGHTNING,//hold/duration
20 , //FP_SABERTHROW,
1 , //FP_SABER_DEFENSE,
0 , //FP_SABER_OFFENSE,
//NUM_FORCE_POWERS
} ;
float forceJumpStrength [ NUM_FORCE_POWER_LEVELS ] =
{
JUMP_VELOCITY , //normal jump
420 ,
590 ,
840
} ;
float forceJumpHeight [ NUM_FORCE_POWER_LEVELS ] =
{
32 , //normal jump (+stepheight+crouchdiff = 66)
96 , //(+stepheight+crouchdiff = 130)
192 , //(+stepheight+crouchdiff = 226)
384 //(+stepheight+crouchdiff = 418)
} ;
float forceJumpHeightMax [ NUM_FORCE_POWER_LEVELS ] =
{
66 , //normal jump (32+stepheight(18)+crouchdiff(24) = 74)
130 , //(96+stepheight(18)+crouchdiff(24) = 138)
226 , //(192+stepheight(18)+crouchdiff(24) = 234)
418 //(384+stepheight(18)+crouchdiff(24) = 426)
} ;
float forcePushPullRadius [ NUM_FORCE_POWER_LEVELS ] =
{
0 , //none
384 , //256,
448 , //384,
512
} ;
float forcePushCone [ NUM_FORCE_POWER_LEVELS ] =
{
1.0f , //none
1.0f ,
0.8f ,
0.6f
} ;
float forcePullCone [ NUM_FORCE_POWER_LEVELS ] =
{
1.0f , //none
1.0f ,
1.0f ,
0.8f
} ;
float forceSpeedValue [ NUM_FORCE_POWER_LEVELS ] =
{
1.0f , //none
0.75f ,
0.5f ,
0.25f
} ;
float forceSpeedRangeMod [ NUM_FORCE_POWER_LEVELS ] =
{
0.0f , //none
30.0f ,
45.0f ,
60.0f
} ;
float forceSpeedFOVMod [ NUM_FORCE_POWER_LEVELS ] =
{
0.0f , //none
20.0f ,
30.0f ,
40.0f
} ;
int forceGripDamage [ NUM_FORCE_POWER_LEVELS ] =
{
0 , //none
0 ,
6 ,
9
} ;
int mindTrickTime [ NUM_FORCE_POWER_LEVELS ] =
{
0 , //none
5000 ,
10000 ,
15000
} ;
//NOTE: keep in synch with table below!!!
int saberThrowDist [ NUM_FORCE_POWER_LEVELS ] =
{
0 , //none
256 ,
400 ,
400
} ;
//NOTE: keep in synch with table above!!!
int saberThrowDistSquared [ NUM_FORCE_POWER_LEVELS ] =
{
0 , //none
65536 ,
160000 ,
160000
} ;
int parryDebounce [ NUM_FORCE_POWER_LEVELS ] =
{
1000000 , //if don't even have defense, can't use defense!
300 ,
150 ,
50
} ;
float saberAnimSpeedMod [ NUM_FORCE_POWER_LEVELS ] =
{
0.0f , //if don't even have offense, can't use offense!
0.75f ,
1.0f ,
2.0f
} ;
//SABER INITIALIZATION======================================================================
void G_CreateG2AttachedWeaponModel ( gentity_t * ent , const char * psWeaponModel )
{
if ( ! psWeaponModel )
{
2022-10-03 21:19:00 +00:00
// assert (psWeaponModel);
2022-09-18 15:37:21 +00:00
return ;
}
if ( ent & & ent - > client & & ent - > client - > NPC_class = = CLASS_GALAKMECH )
{ //hack for galakmech, no weaponmodel
ent - > weaponModel = - 1 ;
return ;
}
char weaponModel [ MAX_QPATH ] ;
Q_strncpyz ( weaponModel , psWeaponModel , sizeof ( weaponModel ) ) ;
if ( char * spot = ( char * ) Q_stristr ( weaponModel , " .md3 " ) ) {
* spot = 0 ;
spot = ( char * ) Q_stristr ( weaponModel , " _w " ) ; //i'm using the in view weapon array instead of scanning the item list, so put the _w back on
if ( ! spot & & ! Q_stristr ( weaponModel , " noweap " ) )
{
Q_strcat ( weaponModel , sizeof ( weaponModel ) , " _w " ) ;
}
Q_strcat ( weaponModel , sizeof ( weaponModel ) , " .glm " ) ; //and change to ghoul2
}
if ( ent - > playerModel = = - 1 )
{
return ;
}
// give us a sabre model
ent - > weaponModel = gi . G2API_InitGhoul2Model ( ent - > ghoul2 , weaponModel , G_ModelIndex ( weaponModel ) , NULL_HANDLE , NULL_HANDLE , 0 , 0 ) ;
if ( ent - > weaponModel ! = - 1 )
{
// attach it to the hand
gi . G2API_AttachG2Model ( & ent - > ghoul2 [ ent - > weaponModel ] , & ent - > ghoul2 [ ent - > playerModel ] ,
ent - > handRBolt , ent - > playerModel ) ;
// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
gi . G2API_AddBolt ( & ent - > ghoul2 [ ent - > weaponModel ] , " *flash " ) ;
//gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel], 0 );
}
}
//----------------------------------------------------------
void G_Throw ( gentity_t * targ , vec3_t newDir , float push )
//----------------------------------------------------------
{
vec3_t kvel ;
float mass ;
if ( targ - > physicsBounce > 0 ) //overide the mass
{
mass = targ - > physicsBounce ;
}
else
{
mass = 200 ;
}
if ( g_gravity - > value > 0 )
{
VectorScale ( newDir , g_knockback - > value * ( float ) push / mass * 0.8 , kvel ) ;
kvel [ 2 ] = newDir [ 2 ] * g_knockback - > value * ( float ) push / mass * 1.5 ;
}
else
{
VectorScale ( newDir , g_knockback - > value * ( float ) push / mass , kvel ) ;
}
if ( targ - > client )
{
VectorAdd ( targ - > client - > ps . velocity , kvel , targ - > client - > ps . velocity ) ;
}
else if ( targ - > s . pos . trType ! = TR_STATIONARY & & targ - > s . pos . trType ! = TR_LINEAR_STOP & & targ - > s . pos . trType ! = TR_NONLINEAR_STOP )
{
VectorAdd ( targ - > s . pos . trDelta , kvel , targ - > s . pos . trDelta ) ;
VectorCopy ( targ - > currentOrigin , targ - > s . pos . trBase ) ;
targ - > s . pos . trTime = level . time ;
}
// set the timer so that the other client can't cancel
// out the movement immediately
if ( targ - > client & & ! targ - > client - > ps . pm_time )
{
int t ;
t = push * 2 ;
if ( t < 50 )
{
t = 50 ;
}
if ( t > 200 )
{
t = 200 ;
}
targ - > client - > ps . pm_time = t ;
targ - > client - > ps . pm_flags | = PMF_TIME_KNOCKBACK ;
}
}
int WP_SetSaberModel ( gclient_t * client , class_t npcClass )
{
if ( client )
{
switch ( npcClass )
{
case CLASS_DESANN : //Desann
client - > ps . saberModel = " models/weapons2/saber_desann/saber_w.glm " ;
break ;
case CLASS_LUKE : //Luke
client - > ps . saberModel = " models/weapons2/saber_luke/saber_w.glm " ;
break ;
case CLASS_KYLE : //Kyle NPC and player
client - > ps . saberModel = " models/weapons2/saber/saber_w.glm " ;
break ;
default : //reborn and tavion and everyone else
client - > ps . saberModel = " models/weapons2/saber_reborn/saber_w.glm " ;
break ;
}
return ( G_ModelIndex ( client - > ps . saberModel ) ) ;
}
else
{
switch ( npcClass )
{
case CLASS_DESANN : //Desann
return ( G_ModelIndex ( " models/weapons2/saber_desann/saber_w.glm " ) ) ;
break ;
case CLASS_LUKE : //Luke
return ( G_ModelIndex ( " models/weapons2/saber_luke/saber_w.glm " ) ) ;
break ;
case CLASS_KYLE : //Kyle NPC and player
return ( G_ModelIndex ( " models/weapons2/saber/saber_w.glm " ) ) ;
break ;
default : //reborn and tavion and everyone else
return ( G_ModelIndex ( " models/weapons2/saber_reborn/saber_w.glm " ) ) ;
break ;
}
}
}
void WP_SaberInitBladeData ( gentity_t * ent )
{
gentity_t * saberent ;
if ( ent - > client )
{
VectorClear ( ent - > client - > renderInfo . muzzlePoint ) ;
VectorClear ( ent - > client - > renderInfo . muzzlePointOld ) ;
//VectorClear( ent->client->renderInfo.muzzlePointNext );
VectorClear ( ent - > client - > renderInfo . muzzleDir ) ;
VectorClear ( ent - > client - > renderInfo . muzzleDirOld ) ;
//VectorClear( ent->client->renderInfo.muzzleDirNext );
ent - > client - > ps . saberLengthOld = ent - > client - > ps . saberLength = 0 ;
ent - > client - > ps . saberLockEnemy = ENTITYNUM_NONE ;
ent - > client - > ps . saberLockTime = 0 ;
if ( ent - > s . number )
{
if ( ent - > client - > NPC_class = = CLASS_DESANN )
{
ent - > client - > ps . saberAnimLevel = FORCE_LEVEL_4 ;
}
else if ( ent - > client - > NPC_class = = CLASS_TAVION )
{
ent - > client - > ps . saberAnimLevel = FORCE_LEVEL_5 ;
}
else if ( ent - > NPC & & ent - > client - > playerTeam = = TEAM_ENEMY & & ( ent - > NPC - > rank = = RANK_CIVILIAN | | ent - > NPC - > rank = = RANK_LT_JG ) )
{ //grunt and fencer always uses quick attacks
ent - > client - > ps . saberAnimLevel = FORCE_LEVEL_1 ;
}
else if ( ent - > NPC & & ent - > client - > playerTeam = = TEAM_ENEMY & & ( ent - > NPC - > rank = = RANK_CREWMAN | | ent - > NPC - > rank = = RANK_ENSIGN ) )
{ //acrobat & force-users always use medium attacks
ent - > client - > ps . saberAnimLevel = FORCE_LEVEL_2 ;
}
else if ( ent - > client - > playerTeam = = TEAM_ENEMY & & ent - > client - > NPC_class = = CLASS_SHADOWTROOPER )
{ //shadowtroopers
ent - > client - > ps . saberAnimLevel = Q_irand ( FORCE_LEVEL_1 , FORCE_LEVEL_3 ) ;
}
else if ( ent - > NPC & & ent - > client - > playerTeam = = TEAM_ENEMY & & ent - > NPC - > rank = = RANK_LT )
{ //boss always starts with strong attacks
ent - > client - > ps . saberAnimLevel = FORCE_LEVEL_3 ;
}
else if ( ent - > client - > NPC_class = = CLASS_KYLE )
{
ent - > client - > ps . saberAnimLevel = g_entities [ 0 ] . client - > ps . saberAnimLevel ;
}
else
{ //?
ent - > client - > ps . saberAnimLevel = Q_irand ( FORCE_LEVEL_1 , FORCE_LEVEL_3 ) ;
}
}
else
{
if ( ! ent - > client - > ps . saberAnimLevel )
{ //initialize, but don't reset
ent - > client - > ps . saberAnimLevel = FORCE_LEVEL_2 ;
}
cg . saberAnimLevelPending = ent - > client - > ps . saberAnimLevel ;
if ( ent - > client - > sess . missionStats . weaponUsed [ WP_SABER ] < = 0 )
{ //let missionStats know that we actually do have the saber, even if we never use it
ent - > client - > sess . missionStats . weaponUsed [ WP_SABER ] = 1 ;
}
}
ent - > client - > ps . saberAttackChainCount = 0 ;
if ( ent - > client - > NPC_class = = CLASS_DESANN )
{ //longer saber
ent - > client - > ps . saberLengthMax = 48 ;
}
else if ( ent - > client - > NPC_class = = CLASS_REBORN )
{ //shorter saber
ent - > client - > ps . saberLengthMax = 32 ;
}
else
{ //standard saber length
ent - > client - > ps . saberLengthMax = 40 ;
}
if ( ent - > client - > ps . saberEntityNum < = 0 | | ent - > client - > ps . saberEntityNum > = ENTITYNUM_WORLD )
{
saberent = G_Spawn ( ) ;
ent - > client - > ps . saberEntityNum = saberent - > s . number ;
saberent - > classname = " lightsaber " ;
saberent - > s . eType = ET_GENERAL ;
saberent - > svFlags = SVF_USE_CURRENT_ORIGIN ;
saberent - > s . weapon = WP_SABER ;
saberent - > owner = ent ;
saberent - > s . otherEntityNum = ent - > s . number ;
saberent - > clipmask = MASK_SOLID | CONTENTS_LIGHTSABER ;
saberent - > contents = CONTENTS_LIGHTSABER ; //|CONTENTS_SHOTCLIP;
VectorSet ( saberent - > mins , - 3.0f , - 3.0f , - 3.0f ) ;
VectorSet ( saberent - > maxs , 3.0f , 3.0f , 3.0f ) ;
saberent - > mass = 10 ; //necc?
saberent - > s . eFlags | = EF_NODRAW ;
saberent - > svFlags | = SVF_NOCLIENT ;
/*
Ghoul2 Insert Start
*/
//FIXME: get saberModel from NPCs.cfg for NPCs?
saberent - > s . modelindex = WP_SetSaberModel ( ent - > client , ent - > client - > NPC_class ) ;
gi . G2API_InitGhoul2Model ( saberent - > ghoul2 , ent - > client - > ps . saberModel , saberent - > s . modelindex , NULL_HANDLE , NULL_HANDLE , 0 , 0 ) ;
// set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
gi . G2API_AddBolt ( & saberent - > ghoul2 [ 0 ] , " *flash " ) ;
//gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
/*
Ghoul2 Insert End
*/
ent - > client - > ps . saberInFlight = qfalse ;
ent - > client - > ps . saberEntityDist = 0 ;
ent - > client - > ps . saberEntityState = SES_LEAVING ;
ent - > client - > ps . saberMove = 0 ;
//FIXME: need a think function to create alerts when turned on or is on, etc.
}
}
else
{
ent - > client - > ps . saberEntityNum = ENTITYNUM_NONE ;
ent - > client - > ps . saberInFlight = qfalse ;
ent - > client - > ps . saberEntityDist = 0 ;
ent - > client - > ps . saberEntityState = SES_LEAVING ;
}
}
void WP_SaberUpdateOldBladeData ( gentity_t * ent )
{
if ( ent - > client )
{
VectorCopy ( ent - > client - > renderInfo . muzzlePoint , ent - > client - > renderInfo . muzzlePointOld ) ;
VectorCopy ( ent - > client - > renderInfo . muzzleDir , ent - > client - > renderInfo . muzzleDirOld ) ;
if ( ent - > client - > ps . saberLengthOld < = 0 & & ent - > client - > ps . saberLength > 0 )
{ //just turned on
//do sound event
vec3_t saberOrg ;
VectorCopy ( g_entities [ ent - > client - > ps . saberEntityNum ] . currentOrigin , saberOrg ) ;
AddSoundEvent ( ent , saberOrg , 256 , AEL_SUSPICIOUS ) ;
}
ent - > client - > ps . saberLengthOld = ent - > client - > ps . saberLength ;
}
}
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
//SABER DAMAGE==============================================================================
int WPDEBUG_SaberColor ( saber_colors_t saberColor )
{
switch ( ( int ) ( saberColor ) )
{
case SABER_RED :
return 0x000000ff ;
break ;
case SABER_ORANGE :
return 0x000088ff ;
break ;
case SABER_YELLOW :
return 0x0000ffff ;
break ;
case SABER_GREEN :
return 0x0000ff00 ;
break ;
case SABER_BLUE :
return 0x00ff0000 ;
break ;
case SABER_PURPLE :
return 0x00ff00ff ;
break ;
default :
return 0x00ffffff ; //white
break ;
}
}
qboolean WP_GetSaberDeflectionAngle ( gentity_t * attacker , gentity_t * defender )
{
vec3_t temp , att_SaberBase , att_StartPos , saberMidNext , att_HitDir , att_HitPos , def_BladeDir ;
float att_SaberHitLength , hitDot ;
if ( ! attacker | | ! attacker - > client | | attacker - > client - > ps . saberInFlight | | attacker - > client - > ps . saberLength < = 0 )
{
return qfalse ;
}
if ( ! defender | | ! defender - > client | | defender - > client - > ps . saberInFlight | | defender - > client - > ps . saberLength < = 0 )
{
return qfalse ;
}
attacker - > client - > ps . saberBounceMove = LS_NONE ;
//get the attacker's saber base pos at time of impact
VectorSubtract ( attacker - > client - > renderInfo . muzzlePoint , attacker - > client - > renderInfo . muzzlePointOld , temp ) ;
VectorMA ( attacker - > client - > renderInfo . muzzlePointOld , saberHitFraction , temp , att_SaberBase ) ;
//get the position along the length of the blade where the hit occured
att_SaberHitLength = Distance ( saberHitLocation , att_SaberBase ) / attacker - > client - > ps . saberLength ;
//now get the start of that midpoint in the swing and the actual impact point in the swing (shouldn't the latter just be saberHitLocation?)
VectorMA ( attacker - > client - > renderInfo . muzzlePointOld , att_SaberHitLength , attacker - > client - > renderInfo . muzzleDirOld , att_StartPos ) ;
VectorMA ( attacker - > client - > renderInfo . muzzlePoint , att_SaberHitLength , attacker - > client - > renderInfo . muzzleDir , saberMidNext ) ;
VectorSubtract ( saberMidNext , att_StartPos , att_HitDir ) ;
VectorMA ( att_StartPos , saberHitFraction , att_HitDir , att_HitPos ) ;
VectorNormalize ( att_HitDir ) ;
//get the defender's saber dir at time of impact
VectorSubtract ( defender - > client - > renderInfo . muzzleDirOld , defender - > client - > renderInfo . muzzleDir , temp ) ;
VectorMA ( defender - > client - > renderInfo . muzzleDirOld , saberHitFraction , temp , def_BladeDir ) ;
//now compare
hitDot = DotProduct ( att_HitDir , def_BladeDir ) ;
if ( hitDot < 0.25f & & hitDot > - 0.25f )
{ //hit pretty much perpendicular, pop straight back
attacker - > client - > ps . saberBounceMove = PM_SaberBounceForAttack ( attacker - > client - > ps . saberMove ) ;
return qfalse ;
}
else
{ //a deflection
vec3_t att_Right , att_Up , att_DeflectionDir ;
float swingRDot , swingUDot ;
//get the direction of the deflection
VectorScale ( def_BladeDir , hitDot , att_DeflectionDir ) ;
//get our bounce straight back direction
VectorScale ( att_HitDir , - 1.0f , temp ) ;
//add the bounce back and deflection
VectorAdd ( att_DeflectionDir , temp , att_DeflectionDir ) ;
//normalize the result to determine what direction our saber should bounce back toward
VectorNormalize ( att_DeflectionDir ) ;
//need to know the direction of the deflectoin relative to the attacker's facing
VectorSet ( temp , 0 , attacker - > client - > ps . viewangles [ YAW ] , 0 ) ; //presumes no pitch!
AngleVectors ( temp , NULL , att_Right , att_Up ) ;
swingRDot = DotProduct ( att_Right , att_DeflectionDir ) ;
swingUDot = DotProduct ( att_Up , att_DeflectionDir ) ;
if ( swingRDot > 0.25f )
{ //deflect to right
if ( swingUDot > 0.25f )
{ //deflect to top
attacker - > client - > ps . saberBounceMove = LS_D1_TR ;
}
else if ( swingUDot < - 0.25f )
{ //deflect to bottom
attacker - > client - > ps . saberBounceMove = LS_D1_BR ;
}
else
{ //deflect horizontally
attacker - > client - > ps . saberBounceMove = LS_D1__R ;
}
}
else if ( swingRDot < - 0.25f )
{ //deflect to left
if ( swingUDot > 0.25f )
{ //deflect to top
attacker - > client - > ps . saberBounceMove = LS_D1_TL ;
}
else if ( swingUDot < - 0.25f )
{ //deflect to bottom
attacker - > client - > ps . saberBounceMove = LS_D1_BL ;
}
else
{ //deflect horizontally
attacker - > client - > ps . saberBounceMove = LS_D1__L ;
}
}
else
{ //deflect in middle
if ( swingUDot > 0.25f )
{ //deflect to top
attacker - > client - > ps . saberBounceMove = LS_D1_T_ ;
}
else if ( swingUDot < - 0.25f )
{ //deflect to bottom
attacker - > client - > ps . saberBounceMove = LS_D1_B_ ;
}
else
{ //deflect horizontally? Well, no such thing as straight back in my face, so use top
if ( swingRDot > 0 )
{
attacker - > client - > ps . saberBounceMove = LS_D1_TR ;
}
else if ( swingRDot < 0 )
{
attacker - > client - > ps . saberBounceMove = LS_D1_TL ;
}
else
{
attacker - > client - > ps . saberBounceMove = LS_D1_T_ ;
}
}
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_BLUE " %s deflected from %s to %s \n " , attacker - > targetname , saberMoveData [ attacker - > client - > ps . saberMove ] . name , saberMoveData [ attacker - > client - > ps . saberBounceMove ] . name ) ;
}
# endif
return qtrue ;
}
}
void WP_SaberClearDamageForEntNum ( int entityNum )
{
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
if ( entityNum )
{
Com_Printf ( " clearing damage for entnum %d \n " , entityNum ) ;
}
}
# endif // FINAL_BUILD
if ( g_saberRealisticCombat - > integer > 1 )
{
return ;
}
for ( int i = 0 ; i < numVictims ; i + + )
{
if ( victimEntityNum [ i ] = = entityNum )
{
totalDmg [ i ] = 0 ; //no damage
hitLoc [ i ] = HL_NONE ;
hitDismemberLoc [ i ] = HL_NONE ;
hitDismember [ i ] = qfalse ;
victimEntityNum [ i ] = ENTITYNUM_NONE ; //like we never hit him
}
}
}
extern float damageModifier [ ] ;
extern float hitLocHealthPercentage [ ] ;
qboolean WP_SaberApplyDamage ( gentity_t * ent , float baseDamage , int baseDFlags , qboolean brokenParry )
{
qboolean didDamage = qfalse ;
gentity_t * victim ;
int dFlags = baseDFlags ;
float maxDmg ;
if ( ! numVictims )
{
return qfalse ;
}
for ( int i = 0 ; i < numVictims ; i + + )
{
dFlags = baseDFlags | DAMAGE_DEATH_KNOCKBACK | DAMAGE_NO_HIT_LOC ;
if ( victimEntityNum [ i ] ! = ENTITYNUM_NONE & & & g_entities [ victimEntityNum [ i ] ] ! = NULL )
{ // Don't bother with this damage if the fraction is higher than the saber's fraction
if ( dmgFraction [ i ] < saberHitFraction | | brokenParry )
{
victim = & g_entities [ victimEntityNum [ i ] ] ;
if ( ! victim )
{
continue ;
}
if ( victim - > e_DieFunc = = dieF_maglock_die )
{ //*sigh*, special check for maglocks
vec3_t testFrom ;
if ( ent - > client - > ps . saberInFlight )
{
VectorCopy ( g_entities [ ent - > client - > ps . saberEntityNum ] . currentOrigin , testFrom ) ;
}
else
{
VectorCopy ( ent - > currentOrigin , testFrom ) ;
}
testFrom [ 2 ] = victim - > currentOrigin [ 2 ] ;
trace_t testTrace ;
gi . trace ( & testTrace , testFrom , vec3_origin , vec3_origin , victim - > currentOrigin , ent - > s . number , MASK_SHOT , G2_NOCOLLIDE , 0 ) ;
if ( testTrace . entityNum ! = victim - > s . number )
{ //can only damage maglocks if have a clear trace to the thing's origin
continue ;
}
}
if ( totalDmg [ i ] > 0 )
{ //actually want to do *some* damage here
if ( victim - > s . weapon = = WP_SABER & & victim - > client & & ! g_saberRealisticCombat - > integer )
{ //dmg vs other saber fighters is modded by hitloc and capped
totalDmg [ i ] * = damageModifier [ hitLoc [ i ] ] ;
if ( hitLoc [ i ] = = HL_NONE )
{
maxDmg = 33 * baseDamage ;
}
else
{
maxDmg = 50 * hitLocHealthPercentage [ hitLoc [ i ] ] * baseDamage ; //*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f;
}
if ( maxDmg < totalDmg [ i ] )
{
totalDmg [ i ] = maxDmg ;
}
//dFlags |= DAMAGE_NO_HIT_LOC;
}
//clamp the dmg
if ( victim - > s . weapon ! = WP_SABER )
{ //clamp the dmg between 25 and maxhealth
/*
if ( totalDmg [ i ] > victim - > max_health )
{
totalDmg [ i ] = victim - > max_health ;
}
else */ if ( totalDmg [ i ] < 25 )
{
totalDmg [ i ] = 25 ;
}
if ( totalDmg [ i ] > 100 ) //+(50*g_spskill->integer) )
{ //clamp using same adjustment as in NPC_Begin
totalDmg [ i ] = 100 ; //+(50*g_spskill->integer);
}
}
else
{ //clamp the dmg between 5 and 100
if ( ! victim - > s . number & & totalDmg [ i ] > 50 )
{ //never do more than half full health damage to player
//prevents one-hit kills
totalDmg [ i ] = 50 ;
}
else if ( totalDmg [ i ] > 100 )
{
totalDmg [ i ] = 100 ;
}
else
{
if ( totalDmg [ i ] < 5 )
{
totalDmg [ i ] = 5 ;
}
}
}
if ( totalDmg [ i ] > 0 )
{
didDamage = qtrue ;
if ( victim - > client )
{
if ( victim - > client - > ps . pm_time > 0 & & victim - > client - > ps . pm_flags & PMF_TIME_KNOCKBACK & & victim - > client - > ps . velocity [ 2 ] > 0 )
{ //already being knocked around
dFlags | = DAMAGE_NO_KNOCKBACK ;
}
if ( g_dismemberment - > integer > = 11381138 | | g_saberRealisticCombat - > integer )
{
dFlags | = DAMAGE_DISMEMBER ;
if ( hitDismember [ i ] )
{
victim - > client - > dismembered = qfalse ;
}
}
else if ( hitDismember [ i ] )
{
dFlags | = DAMAGE_DISMEMBER ;
}
if ( baseDamage < = 1.0f )
{ //very mild damage
if ( victim - > s . number = = 0 | | victim - > client - > ps . weapon = = WP_SABER | | victim - > client - > NPC_class = = CLASS_GALAKMECH )
{ //if it's the player or a saber-user, don't kill them with this blow
dFlags | = DAMAGE_NO_KILL ;
}
}
}
else
{
if ( victim - > takedamage )
{ //some other breakable thing
//create a flash here
g_saberFlashTime = level . time - 50 ;
VectorCopy ( dmgSpot [ i ] , g_saberFlashPos ) ;
}
}
//victim->hitLoc = hitLoc[i];
dFlags | = DAMAGE_NO_KNOCKBACK ; //okay, let's try no knockback whatsoever...
dFlags & = ~ DAMAGE_DEATH_KNOCKBACK ;
if ( g_saberRealisticCombat - > integer )
{
dFlags | = DAMAGE_NO_KNOCKBACK ;
dFlags & = ~ DAMAGE_DEATH_KNOCKBACK ;
dFlags & = ~ DAMAGE_NO_KILL ;
}
if ( ent - > client & & ! ent - > s . number )
{
switch ( hitLoc [ i ] )
{
case HL_FOOT_RT :
case HL_FOOT_LT :
case HL_LEG_RT :
case HL_LEG_LT :
ent - > client - > sess . missionStats . legAttacksCnt + + ;
break ;
case HL_WAIST :
case HL_BACK_RT :
case HL_BACK_LT :
case HL_BACK :
case HL_CHEST_RT :
case HL_CHEST_LT :
case HL_CHEST :
ent - > client - > sess . missionStats . torsoAttacksCnt + + ;
break ;
case HL_ARM_RT :
case HL_ARM_LT :
case HL_HAND_RT :
case HL_HAND_LT :
ent - > client - > sess . missionStats . armAttacksCnt + + ;
break ;
default :
ent - > client - > sess . missionStats . otherAttacksCnt + + ;
break ;
}
}
G_Damage ( victim , ent , ent , dmgDir [ i ] , dmgSpot [ i ] , ceil ( totalDmg [ i ] ) , dFlags , MOD_SABER , hitDismemberLoc [ i ] ) ;
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_RED " damage: %4.2f, hitLoc %d \n " , totalDmg [ i ] , hitLoc [ i ] ) ;
}
# endif
//do the effect
//G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], dmgDir[i] );
if ( ent - > s . number = = 0 )
{
AddSoundEvent ( victim - > owner , dmgSpot [ i ] , 256 , AEL_DISCOVERED ) ;
AddSightEvent ( victim - > owner , dmgSpot [ i ] , 512 , AEL_DISCOVERED , 50 ) ;
}
if ( ent - > client )
{
if ( ent - > enemy & & ent - > enemy = = victim )
{ //just so Jedi knows that he hit his enemy
ent - > client - > ps . saberEventFlags | = SEF_HITENEMY ;
}
else
{
ent - > client - > ps . saberEventFlags | = SEF_HITOBJECT ;
}
}
}
}
}
}
}
return didDamage ;
}
void WP_SaberDamageAdd ( float trDmg , int trVictimEntityNum , vec3_t trDmgDir , vec3_t trDmgSpot , float dmg , float fraction , int trHitLoc , qboolean trDismember , int trDismemberLoc )
{
int curVictim = 0 ;
int i ;
if ( trVictimEntityNum < 0 | | trVictimEntityNum > = ENTITYNUM_WORLD )
{
return ;
}
if ( trDmg * dmg < 10.0f )
{ //too piddly an amount of damage to really count?
//FIXME: but already did the effect, didn't we... sigh...
//return;
}
if ( trDmg )
{ //did some damage to something
for ( i = 0 ; i < numVictims ; i + + )
{
if ( victimEntityNum [ i ] = = trVictimEntityNum )
{ //already hit this guy before
curVictim = i ;
break ;
}
}
if ( i = = numVictims )
{ //haven't hit his guy before
if ( numVictims + 1 > = MAX_SABER_VICTIMS )
{ //can't add another victim at this time
return ;
}
//add a new victim to the list
curVictim = numVictims ;
victimEntityNum [ numVictims + + ] = trVictimEntityNum ;
}
float addDmg = trDmg * dmg ;
if ( trHitLoc ! = HL_NONE & & ( hitLoc [ curVictim ] = = HL_NONE | | hitLocHealthPercentage [ trHitLoc ] > hitLocHealthPercentage [ hitLoc [ curVictim ] ] ) )
{ //this hitLoc is more critical than the previous one this frame
hitLoc [ curVictim ] = trHitLoc ;
}
totalDmg [ curVictim ] + = addDmg ;
if ( ! VectorLengthSquared ( dmgDir [ curVictim ] ) )
{
VectorCopy ( trDmgDir , dmgDir [ curVictim ] ) ;
}
if ( ! VectorLengthSquared ( dmgSpot [ curVictim ] ) )
{
VectorCopy ( trDmgSpot , dmgSpot [ curVictim ] ) ;
}
// Make sure we keep track of the fraction. Why?
// Well, if the saber hits something that stops it, the damage isn't done past that point.
dmgFraction [ curVictim ] = fraction ;
if ( ( trDismemberLoc ! = HL_NONE & & hitDismemberLoc [ curVictim ] = = HL_NONE )
| | ( ! hitDismember [ curVictim ] & & trDismember ) )
{ //either this is the first dismember loc we got or we got a loc before, but it wasn't a dismember loc, so take the new one
hitDismemberLoc [ curVictim ] = trDismemberLoc ;
}
if ( trDismember )
{ //we scored a dismemberment hit...
hitDismember [ curVictim ] = trDismember ;
}
}
}
/*
WP_SabersIntersect
Breaks the two saber paths into 2 tris each and tests each tri for the first saber path against each of the other saber path ' s tris
FIXME : subdivide the arc into a consistant increment
FIXME : test the intersection to see if the sabers really did intersect ( weren ' t going in the same direction and / or passed through same point at different times ) ?
*/
extern qboolean tri_tri_intersect ( vec3_t V0 , vec3_t V1 , vec3_t V2 , vec3_t U0 , vec3_t U1 , vec3_t U2 ) ;
qboolean WP_SabersIntersect ( gentity_t * ent1 , gentity_t * ent2 , qboolean checkDir )
{
vec3_t saberBase1 , saberTip1 , saberBaseNext1 , saberTipNext1 ;
vec3_t saberBase2 , saberTip2 , saberBaseNext2 , saberTipNext2 ;
vec3_t dir ;
/*
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_GREEN " Doing precise saber intersection check \n " ) ;
}
# endif
*/
if ( ! ent1 | | ! ent2 )
{
return qfalse ;
}
if ( ! ent1 - > client | | ! ent2 - > client )
{
return qfalse ;
}
if ( ent1 - > client - > ps . saberLength < = 0 | | ent2 - > client - > ps . saberLength < = 0 )
{
return qfalse ;
}
//if ( ent1->client->ps.saberInFlight )
{
VectorCopy ( ent1 - > client - > renderInfo . muzzlePointOld , saberBase1 ) ;
VectorCopy ( ent1 - > client - > renderInfo . muzzlePoint , saberBaseNext1 ) ;
VectorSubtract ( ent1 - > client - > renderInfo . muzzlePoint , ent1 - > client - > renderInfo . muzzlePointOld , dir ) ;
VectorNormalize ( dir ) ;
VectorMA ( saberBaseNext1 , SABER_EXTRAPOLATE_DIST , dir , saberBaseNext1 ) ;
VectorMA ( saberBase1 , ent1 - > client - > ps . saberLength , ent1 - > client - > renderInfo . muzzleDirOld , saberTip1 ) ;
VectorMA ( saberBaseNext1 , ent1 - > client - > ps . saberLength , ent1 - > client - > renderInfo . muzzleDir , saberTipNext1 ) ;
VectorSubtract ( saberTipNext1 , saberTip1 , dir ) ;
VectorNormalize ( dir ) ;
VectorMA ( saberTipNext1 , SABER_EXTRAPOLATE_DIST , dir , saberTipNext1 ) ;
}
/*
else
{
VectorCopy ( ent1 - > client - > renderInfo . muzzlePoint , saberBase1 ) ;
VectorCopy ( ent1 - > client - > renderInfo . muzzlePointNext , saberBaseNext1 ) ;
VectorMA ( saberBase1 , ent1 - > client - > ps . saberLength , ent1 - > client - > renderInfo . muzzleDir , saberTip1 ) ;
VectorMA ( saberBaseNext1 , ent1 - > client - > ps . saberLength , ent1 - > client - > renderInfo . muzzleDirNext , saberTipNext1 ) ;
}
*/
//if ( ent2->client->ps.saberInFlight )
{
VectorCopy ( ent2 - > client - > renderInfo . muzzlePointOld , saberBase2 ) ;
VectorCopy ( ent2 - > client - > renderInfo . muzzlePoint , saberBaseNext2 ) ;
VectorSubtract ( ent2 - > client - > renderInfo . muzzlePoint , ent2 - > client - > renderInfo . muzzlePointOld , dir ) ;
VectorNormalize ( dir ) ;
VectorMA ( saberBaseNext2 , SABER_EXTRAPOLATE_DIST , dir , saberBaseNext2 ) ;
VectorMA ( saberBase2 , ent2 - > client - > ps . saberLength , ent2 - > client - > renderInfo . muzzleDirOld , saberTip2 ) ;
VectorMA ( saberBaseNext2 , ent2 - > client - > ps . saberLength , ent2 - > client - > renderInfo . muzzleDir , saberTipNext2 ) ;
VectorSubtract ( saberTipNext2 , saberTip2 , dir ) ;
VectorNormalize ( dir ) ;
VectorMA ( saberTipNext2 , SABER_EXTRAPOLATE_DIST , dir , saberTipNext2 ) ;
}
/*
else
{
VectorCopy ( ent2 - > client - > renderInfo . muzzlePoint , saberBase2 ) ;
VectorCopy ( ent2 - > client - > renderInfo . muzzlePointNext , saberBaseNext2 ) ;
VectorMA ( saberBase2 , ent2 - > client - > ps . saberLength , ent2 - > client - > renderInfo . muzzleDir , saberTip2 ) ;
VectorMA ( saberBaseNext2 , ent2 - > client - > ps . saberLength , ent2 - > client - > renderInfo . muzzleDirNext , saberTipNext2 ) ;
}
*/
if ( checkDir )
{ //check the direction of the two swings to make sure the sabers are swinging towards each other
vec3_t saberDir1 , saberDir2 ;
VectorSubtract ( saberTipNext1 , saberTip1 , saberDir1 ) ;
VectorSubtract ( saberTipNext2 , saberTip2 , saberDir2 ) ;
VectorNormalize ( saberDir1 ) ;
VectorNormalize ( saberDir2 ) ;
if ( DotProduct ( saberDir1 , saberDir2 ) > 0.6f )
{ //sabers moving in same dir, probably didn't actually hit
return qfalse ;
}
//now check orientation of sabers, make sure they're not parallel or close to it
float dot = DotProduct ( ent1 - > client - > renderInfo . muzzleDir , ent2 - > client - > renderInfo . muzzleDir ) ;
if ( dot > 0.9f | | dot < - 0.9f )
{ //too parallel to really block effectively?
return qfalse ;
}
}
if ( tri_tri_intersect ( saberBase1 , saberTip1 , saberBaseNext1 , saberBase2 , saberTip2 , saberBaseNext2 ) )
{
return qtrue ;
}
if ( tri_tri_intersect ( saberBase1 , saberTip1 , saberBaseNext1 , saberBase2 , saberTip2 , saberTipNext2 ) )
{
return qtrue ;
}
if ( tri_tri_intersect ( saberBase1 , saberTip1 , saberTipNext1 , saberBase2 , saberTip2 , saberBaseNext2 ) )
{
return qtrue ;
}
if ( tri_tri_intersect ( saberBase1 , saberTip1 , saberTipNext1 , saberBase2 , saberTip2 , saberTipNext2 ) )
{
return qtrue ;
}
return qfalse ;
}
float WP_SabersDistance ( gentity_t * ent1 , gentity_t * ent2 )
{
vec3_t saberBaseNext1 , saberTipNext1 , saberPoint1 ;
vec3_t saberBaseNext2 , saberTipNext2 , saberPoint2 ;
/*
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_GREEN " Doing precise saber intersection check \n " ) ;
}
# endif
*/
if ( ! ent1 | | ! ent2 )
{
return qfalse ;
}
if ( ! ent1 - > client | | ! ent2 - > client )
{
return qfalse ;
}
if ( ent1 - > client - > ps . saberLength < = 0 | | ent2 - > client - > ps . saberLength < = 0 )
{
return qfalse ;
}
//if ( ent1->client->ps.saberInFlight )
{
VectorCopy ( ent1 - > client - > renderInfo . muzzlePoint , saberBaseNext1 ) ;
VectorMA ( saberBaseNext1 , ent1 - > client - > ps . saberLength , ent1 - > client - > renderInfo . muzzleDir , saberTipNext1 ) ;
}
/*
else
{
VectorCopy ( ent1 - > client - > renderInfo . muzzlePointNext , saberBaseNext1 ) ;
VectorMA ( saberBaseNext1 , ent1 - > client - > ps . saberLength , ent1 - > client - > renderInfo . muzzleDirNext , saberTipNext1 ) ;
}
*/
//if ( ent2->client->ps.saberInFlight )
{
VectorCopy ( ent2 - > client - > renderInfo . muzzlePoint , saberBaseNext2 ) ;
VectorMA ( saberBaseNext2 , ent2 - > client - > ps . saberLength , ent2 - > client - > renderInfo . muzzleDir , saberTipNext2 ) ;
}
/*
else
{
VectorCopy ( ent2 - > client - > renderInfo . muzzlePointNext , saberBaseNext2 ) ;
VectorMA ( saberBaseNext2 , ent2 - > client - > ps . saberLength , ent2 - > client - > renderInfo . muzzleDirNext , saberTipNext2 ) ;
}
*/
float sabersDist = ShortestLineSegBewteen2LineSegs ( saberBaseNext1 , saberTipNext1 , saberBaseNext2 , saberTipNext2 , saberPoint1 , saberPoint2 ) ;
//okay, this is a super hack, but makes saber collisions look better from the player point of view
/*
if ( sabersDist < 16.0f )
{
vec3_t saberDistDir , saberMidPoint , camLookDir ;
VectorSubtract ( saberPoint2 , saberPoint1 , saberDistDir ) ;
VectorMA ( saberPoint1 , 0.5f , saberDistDir , saberMidPoint ) ;
VectorSubtract ( saberMidPoint , cg . refdef . vieworg , camLookDir ) ;
VectorNormalize ( saberDistDir ) ;
VectorNormalize ( camLookDir ) ;
float dot = fabs ( DotProduct ( camLookDir , saberDistDir ) ) ;
sabersDist - = 8.0f * dot ;
if ( sabersDist < 0.0f )
{
sabersDist = 0.0f ;
}
}
*/
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 2 )
{
G_DebugLine ( saberPoint1 , saberPoint2 , FRAMETIME , 0x00ffffff , qtrue ) ;
}
# endif
return sabersDist ;
}
qboolean WP_SabersIntersection ( gentity_t * ent1 , gentity_t * ent2 , vec3_t intersect )
{
vec3_t saberBaseNext1 , saberTipNext1 , saberPoint1 ;
vec3_t saberBaseNext2 , saberTipNext2 , saberPoint2 ;
if ( ! ent1 | | ! ent2 )
{
return qfalse ;
}
if ( ! ent1 - > client | | ! ent2 - > client )
{
return qfalse ;
}
if ( ent1 - > client - > ps . saberLength < = 0 | | ent2 - > client - > ps . saberLength < = 0 )
{
return qfalse ;
}
VectorCopy ( ent1 - > client - > renderInfo . muzzlePoint , saberBaseNext1 ) ;
VectorMA ( saberBaseNext1 , ent1 - > client - > ps . saberLength , ent1 - > client - > renderInfo . muzzleDir , saberTipNext1 ) ;
VectorCopy ( ent2 - > client - > renderInfo . muzzlePoint , saberBaseNext2 ) ;
VectorMA ( saberBaseNext2 , ent2 - > client - > ps . saberLength , ent2 - > client - > renderInfo . muzzleDir , saberTipNext2 ) ;
ShortestLineSegBewteen2LineSegs ( saberBaseNext1 , saberTipNext1 , saberBaseNext2 , saberTipNext2 , saberPoint1 , saberPoint2 ) ;
VectorAdd ( saberPoint1 , saberPoint2 , intersect ) ;
VectorScale ( intersect , 0.5 , intersect ) ;
return qtrue ;
}
char * hit_blood_sparks = " blood_sparks " ;
char * hit_sparks = " saber_cut " ;
extern qboolean G_GetHitLocFromSurfName ( gentity_t * ent , const char * surfName , int * hitLoc , vec3_t point , vec3_t dir , vec3_t bladeDir , int mod ) ;
qboolean WP_SaberDamageEffects ( trace_t * tr , const vec3_t start , float length , float dmg , vec3_t dmgDir , vec3_t bladeDir , int enemyTeam )
{
int hitEntNum [ MAX_G2_COLLISIONS ] = { ENTITYNUM_NONE } ;
float hitEntDmgAdd [ MAX_G2_COLLISIONS ] = { 0 } ;
float hitEntDmgSub [ MAX_G2_COLLISIONS ] = { 0 } ;
vec3_t hitEntPoint [ MAX_G2_COLLISIONS ] ;
vec3_t hitEntDir [ MAX_G2_COLLISIONS ] ;
float hitEntStartFrac [ MAX_G2_COLLISIONS ] = { 0 } ;
int trHitLoc = HL_NONE ;
int trDismemberLoc = HL_NONE ;
qboolean trDismember = qfalse ;
int i , z ;
int numHitEnts = 0 ;
float distFromStart , doDmg ;
char * hitEffect ;
gentity_t * hitEnt ;
for ( z = 0 ; z < MAX_G2_COLLISIONS ; z + + )
{
if ( tr - > G2CollisionMap [ z ] . mEntityNum = = - 1 )
{ //actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
continue ; //break;//
}
CCollisionRecord & coll = tr - > G2CollisionMap [ z ] ;
//distFromStart = Distance( start, coll.mCollisionPosition );
distFromStart = coll . mDistance ;
/*
//FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*...
if ( length & & saberHitFraction < 1.0f & & ( distFromStart / length ) < 1.0f & & ( distFromStart / length ) > saberHitFraction )
{ //a saber was hit before this point, don't count it
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_MAGENTA " rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f \n " , ( distFromStart / length ) , saberHitFraction ) ;
}
# endif
continue ;
}
*/
for ( i = 0 ; i < numHitEnts ; i + + )
{
if ( hitEntNum [ i ] ! = ENTITYNUM_NONE )
{ //we hit this ent before
//we'll want to add this dist
hitEntDmgAdd [ i ] = distFromStart ;
break ;
}
}
if ( i = = numHitEnts )
{ //first time we hit this ent
if ( numHitEnts = = MAX_G2_COLLISIONS )
{ //hit too many damn ents!
continue ;
}
hitEntNum [ numHitEnts ] = coll . mEntityNum ;
if ( ! coll . mFlags )
{ //hmm, we came out first, so we must have started inside
//we'll want to subtract this dist
hitEntDmgAdd [ numHitEnts ] = distFromStart ;
}
else
{ //we're entering the model
//we'll want to subtract this dist
hitEntDmgSub [ numHitEnts ] = distFromStart ;
}
//keep track of how far in the damage was done
hitEntStartFrac [ numHitEnts ] = hitEntDmgSub [ numHitEnts ] / length ;
//remember the entrance point
VectorCopy ( coll . mCollisionPosition , hitEntPoint [ numHitEnts ] ) ;
//remember the entrance dir
VectorCopy ( coll . mCollisionNormal , hitEntDir [ numHitEnts ] ) ;
VectorNormalize ( hitEntDir [ numHitEnts ] ) ;
//do the effect
//FIXME: check material rather than team?
hitEnt = & g_entities [ hitEntNum [ numHitEnts ] ] ;
hitEffect = hit_blood_sparks ;
if ( hitEnt ! = NULL )
{
if ( hitEnt - > client )
{
class_t npc_class = hitEnt - > client - > NPC_class ;
if ( npc_class = = CLASS_SEEKER | | npc_class = = CLASS_PROBE | | npc_class = = CLASS_MOUSE | | npc_class = = CLASS_REMOTE | |
npc_class = = CLASS_GONK | | npc_class = = CLASS_R2D2 | | npc_class = = CLASS_R5D2 | |
npc_class = = CLASS_PROTOCOL | | npc_class = = CLASS_MARK1 | | npc_class = = CLASS_MARK2 | |
npc_class = = CLASS_INTERROGATOR | | npc_class = = CLASS_ATST | | npc_class = = CLASS_SENTRY )
{
hitEffect = hit_sparks ;
}
}
else
{
hitEffect = hit_sparks ;
}
}
//FIXME: play less if damage is less?
G_PlayEffect ( hitEffect , coll . mCollisionPosition , coll . mCollisionNormal ) ;
//Get the hit location based on surface name
if ( ( hitLoc [ numHitEnts ] = = HL_NONE & & trHitLoc = = HL_NONE )
| | ( hitDismemberLoc [ numHitEnts ] = = HL_NONE & & trDismemberLoc = = HL_NONE )
| | ( ! hitDismember [ numHitEnts ] & & ! trDismember ) )
{ //no hit loc set for this ent this damage cycle yet
//FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName?
trDismember = G_GetHitLocFromSurfName ( & g_entities [ coll . mEntityNum ] , gi . G2API_GetSurfaceName ( & g_entities [ coll . mEntityNum ] . ghoul2 [ coll . mModelIndex ] , coll . mSurfaceIndex ) , & trHitLoc , coll . mCollisionPosition , dmgDir , bladeDir , MOD_SABER ) ;
trDismemberLoc = trHitLoc ;
}
numHitEnts + + ;
}
}
//now go through all the ents we hit and do the damage
for ( i = 0 ; i < numHitEnts ; i + + )
{
doDmg = dmg ;
if ( hitEntNum [ i ] ! = ENTITYNUM_NONE )
{
if ( doDmg < 10 )
{ //base damage is less than 10
if ( hitEntNum [ i ] ! = 0 )
{ //not the player
hitEnt = & g_entities [ hitEntNum [ i ] ] ;
if ( ! hitEnt - > client | | ( hitEnt - > client - > ps . weapon ! = WP_SABER & & hitEnt - > client - > NPC_class ! = CLASS_GALAKMECH & & hitEnt - > client - > playerTeam = = enemyTeam ) )
{ //did *not* hit a jedi and did *not* hit the player
//make sure the base damage is high against non-jedi, feels better
doDmg = 10 ;
}
}
}
if ( ! hitEntDmgAdd [ i ] & & ! hitEntDmgSub [ i ] )
{ //spent entire time in model
//NOTE: will we even get a collision then?
doDmg * = length ;
}
else if ( hitEntDmgAdd [ i ] & & hitEntDmgSub [ i ] )
{ //we did enter and exit
doDmg * = hitEntDmgAdd [ i ] - hitEntDmgSub [ i ] ;
}
else if ( ! hitEntDmgAdd [ i ] )
{ //we didn't exit, just entered
doDmg * = length - hitEntDmgSub [ i ] ;
}
else if ( ! hitEntDmgSub [ i ] )
{ //we didn't enter, only exited
doDmg * = hitEntDmgAdd [ i ] ;
}
if ( doDmg > 0 )
{
WP_SaberDamageAdd ( 1.0 , hitEntNum [ i ] , hitEntDir [ i ] , hitEntPoint [ i ] , ceil ( doDmg ) , hitEntStartFrac [ i ] , trHitLoc , trDismember , trDismemberLoc ) ;
}
}
}
return ( qboolean ) ( numHitEnts > 0 ) ;
}
void WP_SaberKnockaway ( gentity_t * attacker , trace_t * tr )
{
WP_SaberDrop ( attacker , & g_entities [ attacker - > client - > ps . saberEntityNum ] ) ;
G_Sound ( & g_entities [ attacker - > client - > ps . saberEntityNum ] , G_SoundIndex ( va ( " sound/weapons/saber/saberblock%d.wav " , Q_irand ( 1 , 9 ) ) ) ) ;
G_PlayEffect ( " saber_block " , tr - > endpos ) ;
saberHitFraction = tr - > fraction ;
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_MAGENTA " WP_SaberKnockaway: saberHitFraction %4.2f \n " , saberHitFraction ) ;
}
# endif
VectorCopy ( tr - > endpos , saberHitLocation ) ;
saberHitEntity = tr - > entityNum ;
g_saberFlashTime = level . time - 50 ;
VectorCopy ( saberHitLocation , g_saberFlashPos ) ;
//FIXME: make hitEnt play an attack anim or some other special anim when this happens
//gentity_t *hitEnt = &g_entities[tr->entityNum];
//NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
}
# define SABER_COLLISION_DIST 6 //was 2//was 4//was 8//was 16
extern qboolean InFront ( vec3_t spot , vec3_t from , vec3_t fromAngles , float threshHold = 0.0f ) ;
qboolean WP_SaberDamageForTrace ( int ignore , vec3_t start , vec3_t end , float dmg ,
vec3_t bladeDir , qboolean noGhoul , int attackStrength ,
qboolean extrapolate = qtrue )
{
trace_t tr ;
vec3_t dir ;
int mask = ( MASK_SHOT | CONTENTS_LIGHTSABER ) ;
gentity_t * attacker = & g_entities [ ignore ] ;
vec3_t end2 ;
VectorCopy ( end , end2 ) ;
if ( extrapolate )
{
//NOTE: since we can no longer use the predicted point, extrapolate the trace some.
// this may allow saber hits that aren't actually hits, but it doesn't look too bad
vec3_t diff ;
VectorSubtract ( end , start , diff ) ;
VectorNormalize ( diff ) ;
VectorMA ( end2 , SABER_EXTRAPOLATE_DIST , diff , end2 ) ;
}
if ( ! noGhoul )
{
if ( ! attacker - > s . number | | ( attacker - > client & & ( attacker - > client - > playerTeam = = TEAM_PLAYER | | attacker - > client - > NPC_class = = CLASS_SHADOWTROOPER | | attacker - > client - > NPC_class = = CLASS_TAVION | | attacker - > client - > NPC_class = = CLASS_DESANN ) ) ) //&&attackStrength==FORCE_LEVEL_3)
{ //player,. player allies, shadowtroopers, tavion and desann use larger traces
vec3_t traceMins = { - 2 , - 2 , - 2 } , traceMaxs = { 2 , 2 , 2 } ;
gi . trace ( & tr , start , traceMins , traceMaxs , end2 , ignore , mask , G2_COLLIDE , 10 ) ; //G2_SUPERSIZEDBBOX
}
/*
else if ( ! attacker - > s . number )
{
vec3_t traceMins = { - 1 , - 1 , - 1 } , traceMaxs = { 1 , 1 , 1 } ;
gi . trace ( & tr , start , traceMins , traceMaxs , end2 , ignore , mask , G2_COLLIDE , 10 ) ; //G2_SUPERSIZEDBBOX
}
*/
else
{ //reborn use smaller traces
gi . trace ( & tr , start , NULL , NULL , end2 , ignore , mask , G2_COLLIDE , 10 ) ; //G2_SUPERSIZEDBBOX
}
}
else
{
gi . trace ( & tr , start , NULL , NULL , end2 , ignore , mask , G2_NOCOLLIDE , 10 ) ;
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 1 )
{
if ( attacker ! = NULL & & attacker - > client ! = NULL )
{
G_DebugLine ( start , end2 , FRAMETIME , WPDEBUG_SaberColor ( attacker - > client - > ps . saberColor ) , qtrue ) ;
}
}
# endif
if ( tr . entityNum = = ENTITYNUM_NONE )
{
return qfalse ;
}
if ( tr . entityNum = = ENTITYNUM_WORLD )
{
return qtrue ;
}
if ( & g_entities [ tr . entityNum ] )
{
gentity_t * hitEnt = & g_entities [ tr . entityNum ] ;
gentity_t * owner = g_entities [ tr . entityNum ] . owner ;
if ( hitEnt - > contents & CONTENTS_LIGHTSABER )
{
if ( attacker & & attacker - > client & & attacker - > client - > ps . saberInFlight )
{ //thrown saber hit something
if ( owner
& & owner - > s . number
& & owner - > client
& & owner - > NPC
& & owner - > health > 0
& & ( owner - > client - > NPC_class = = CLASS_TAVION
/*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 ))
| | ( Q_irand ( - 5 , owner - > NPC - > rank ) > RANK_CIVILIAN & & ! Q_irand ( 0 , g_spskill - > integer * 3 ) ) */ )
)
{ //Tavion can toss a blocked thrown saber aside
WP_SaberKnockaway ( attacker , & tr ) ;
Jedi_PlayDeflectSound ( owner ) ;
return qfalse ;
}
}
//FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow?
qboolean sabersIntersect = WP_SabersIntersect ( attacker , owner , qfalse ) ; //qtrue );
float sabersDist ;
if ( attacker & & attacker - > client & & attacker - > client - > ps . saberInFlight
& & owner & & owner - > s . number = = 0 & & ( g_saberAutoBlocking - > integer | | attacker - > client - > ps . saberBlockingTime > level . time ) ) //NPC flying saber hit player's saber bounding box
{ //players have g_saberAutoBlocking, do the more generous check against flying sabers
//FIXME: instead of hitting the player's saber bounding box
//and picking an anim afterwards, have him use AI similar
//to the AI the jedi use for picking a saber melee block...?
sabersDist = 0 ;
}
else
{ //sabers must actually collide with the attacking saber
sabersDist = WP_SabersDistance ( attacker , owner ) ;
if ( attacker & & attacker - > client & & attacker - > client - > ps . saberInFlight )
{
sabersDist / = 2.0f ;
if ( sabersDist < = 16.0f )
{
sabersIntersect = qtrue ;
}
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 1 )
{
gi . Printf ( " sabersDist: %4.2f \n " , sabersDist ) ;
}
# endif //FINAL_BUILD
}
if ( sabersCrossed = = - 1 | | sabersCrossed > sabersDist )
{
sabersCrossed = sabersDist ;
}
float collisionDist ;
if ( g_saberRealisticCombat - > integer )
{
collisionDist = SABER_COLLISION_DIST ;
}
else
{
collisionDist = SABER_COLLISION_DIST + 6 + g_spskill - > integer * 4 ;
}
if ( owner & & owner - > client & & ( attacker ! = NULL )
& & ( sabersDist > collisionDist ) //|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f ))
& & ! sabersIntersect ) //was qtrue, but missed too much?
{ //swing came from behind and/or was not stopped by a lightsaber
//re-try the trace without checking for lightsabers
gi . trace ( & tr , start , NULL , NULL , end2 , ignore , mask & ~ CONTENTS_LIGHTSABER , G2_NOCOLLIDE , 10 ) ;
if ( tr . entityNum = = ENTITYNUM_WORLD )
{
return qtrue ;
}
if ( tr . entityNum = = ENTITYNUM_NONE | | & g_entities [ tr . entityNum ] = = NULL )
{ //didn't hit the owner
/*
if ( attacker
& & attacker - > client
& & ( PM_SaberInAttack ( attacker - > client - > ps . saberMove ) | | PM_SaberInStart ( attacker - > client - > ps . saberMove ) )
& & DistanceSquared ( tr . endpos , owner - > currentOrigin ) < 10000 )
{
if ( owner - > NPC
& & ! owner - > client - > ps . saberInFlight
& & owner - > client - > ps . saberBlocked ! = BLOCKED_PARRY_BROKEN
& & ! Jedi_SaberBusy ( owner ) )
{ //owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
if ( g_spskill - > integer & & ( g_spskill - > integer > 1 | | Q_irand ( 0 , 1 ) ) )
{ //if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
//FIXME: also take into account the owner's FP_DEFENSE?
if ( Q_irand ( 0 , owner - > NPC - > rank ) > = RANK_CIVILIAN )
{ //lower-rank Jedi aren't as good blockers
vec3_t attDir ;
VectorSubtract ( end2 , start , attDir ) ;
VectorNormalize ( attDir ) ;
Jedi_SaberBlockGo ( owner , owner - > NPC - > last_ucmd , start , attDir , NULL ) ;
}
}
}
}
*/
return qfalse ; // Exit, but we didn't hit the wall.
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 1 )
{
if ( ! attacker - > s . number )
{
gi . Printf ( S_COLOR_MAGENTA " %d saber hit owner through saber %4.2f, dist = %4.2f \n " , level . time , saberHitFraction , sabersDist ) ;
}
}
# endif //FINAL_BUILD
hitEnt = & g_entities [ tr . entityNum ] ;
owner = g_entities [ tr . entityNum ] . owner ;
}
else
{ //hit a lightsaber
if ( ( tr . fraction < saberHitFraction | | tr . startsolid )
& & sabersDist < ( 8.0f + g_spskill - > value ) * 4.0f // 50.0f//16.0f
& & ( sabersIntersect | | sabersDist < ( 4.0f + g_spskill - > value ) * 2.0f ) ) //32.0f) )
{ // This saber hit closer than the last one.
if ( ( tr . allsolid | | tr . startsolid ) & & owner & & owner - > client )
{ //tr.fraction will be 0, unreliable... so calculate actual
float dist = Distance ( start , end2 ) ;
if ( dist )
{
float hitFrac = WP_SabersDistance ( attacker , owner ) / dist ;
if ( hitFrac > 1.0f )
{ //umm... minimum distance between sabers was longer than trace...?
hitFrac = 1.0f ;
}
if ( hitFrac < saberHitFraction )
{
saberHitFraction = hitFrac ;
}
}
else
{
saberHitFraction = 0.0f ;
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 1 )
{
if ( ! attacker - > s . number )
{
gi . Printf ( S_COLOR_GREEN " %d saber hit saber dist %4.2f allsolid %4.2f \n " , level . time , sabersDist , saberHitFraction ) ;
}
}
# endif //FINAL_BUILD
}
else
{
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 1 )
{
if ( ! attacker - > s . number )
{
gi . Printf ( S_COLOR_BLUE " %d saber hit saber dist %4.2f, frac %4.2f \n " , level . time , sabersDist , saberHitFraction ) ;
}
saberHitFraction = tr . fraction ;
}
# endif //FINAL_BUILD
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_MAGENTA " hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d \n " , saberHitFraction , tr . allsolid , tr . startsolid ) ;
}
# endif //FINAL_BUILD
VectorCopy ( tr . endpos , saberHitLocation ) ;
saberHitEntity = tr . entityNum ;
}
/*
if ( owner
& & owner - > client
& & attacker
& & attacker - > client
& & ( PM_SaberInAttack ( attacker - > client - > ps . saberMove ) | | PM_SaberInStart ( attacker - > client - > ps . saberMove ) )
& & DistanceSquared ( tr . endpos , owner - > currentOrigin ) < 10000 )
{
if ( owner - > NPC
& & ! owner - > client - > ps . saberInFlight
& & owner - > client - > ps . saberBlocked ! = BLOCKED_PARRY_BROKEN
& & ! Jedi_SaberBusy ( owner ) )
{ //owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
if ( g_spskill - > integer & & ( g_spskill - > integer > 1 | | Q_irand ( 0 , 1 ) ) )
{ //if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
//FIXME: also take into account the owner's FP_DEFENSE?
if ( Q_irand ( 0 , owner - > NPC - > rank ) > = RANK_CIVILIAN )
{ //lower-rank Jedi aren't as good blockers
vec3_t attDir ;
VectorSubtract ( end2 , start , attDir ) ;
VectorNormalize ( attDir ) ;
Jedi_SaberBlockGo ( owner , owner - > NPC - > last_ucmd , start , attDir , NULL ) ;
}
}
}
}
*/
return qfalse ; // Exit, but we didn't hit the wall.
}
}
else
{
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 1 )
{
if ( ! attacker - > s . number )
{
gi . Printf ( S_COLOR_RED " %d saber hit owner directly %4.2f \n " , level . time , saberHitFraction ) ;
}
}
# endif //FINAL_BUILD
}
if ( attacker & & attacker - > client & & attacker - > client - > ps . saberInFlight )
{ //thrown saber hit something
if ( ( hitEnt & & hitEnt - > client & & hitEnt - > health > 0 & & ( hitEnt - > client - > NPC_class = = CLASS_DESANN | | hitEnt - > client - > NPC_class = = CLASS_LUKE | | ( hitEnt - > client - > NPC_class = = CLASS_GALAKMECH & & hitEnt - > client - > ps . powerups [ PW_GALAK_SHIELD ] > 0 ) ) ) | |
( owner & & owner - > client & & owner - > health > 0 & & ( owner - > client - > NPC_class = = CLASS_DESANN | | owner - > client - > NPC_class = = CLASS_LUKE | | ( owner - > client - > NPC_class = = CLASS_GALAKMECH & & owner - > client - > ps . powerups [ PW_GALAK_SHIELD ] > 0 ) ) ) )
{ //Luke and Desann slap thrown sabers aside
//FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin?
WP_SaberKnockaway ( attacker , & tr ) ;
if ( hitEnt - > client )
{
Jedi_PlayDeflectSound ( hitEnt ) ;
}
else
{
Jedi_PlayDeflectSound ( owner ) ;
}
return qfalse ; // Exit, but we didn't hit the wall.
}
}
if ( hitEnt - > takedamage )
{
//no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) )
{
//multiply the damage by the total distance of the swipe
VectorSubtract ( end2 , start , dir ) ;
float len = VectorNormalize ( dir ) ; //VectorLength( dir );
if ( noGhoul | | ! hitEnt - > ghoul2 . size ( ) )
{ //we weren't doing a ghoul trace
char * hitEffect ;
if ( dmg > = 1.0 & & hitEnt - > bmodel )
{
dmg = 1.0 ;
}
if ( len > 1 )
{
dmg * = len ;
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 1 )
{
if ( ! ( hitEnt - > contents & CONTENTS_LIGHTSABER ) )
{
gi . Printf ( S_COLOR_GREEN " Hit ent, but no ghoul collisions \n " ) ;
}
}
# endif
float trFrac , dmgFrac ;
if ( tr . allsolid )
{ //totally inside them
trFrac = 1.0 ;
dmgFrac = 0.0 ;
}
else if ( tr . startsolid )
{ //started inside them
//we don't know how much was inside, we know it's less than all, so use half?
trFrac = 0.5 ;
dmgFrac = 0.0 ;
}
else
{ //started outside them and hit them
//yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2)
trFrac = ( 1.0f - tr . fraction ) ;
dmgFrac = tr . fraction ;
}
WP_SaberDamageAdd ( trFrac , tr . entityNum , dir , tr . endpos , dmg , dmgFrac , HL_NONE , qfalse , HL_NONE ) ;
if ( ! tr . allsolid & & ! tr . startsolid )
{
VectorScale ( dir , - 1 , dir ) ;
}
//FIXME: don't do blood sparks on non-living things
hitEffect = hit_blood_sparks ;
if ( hitEnt ! = NULL )
{
if ( hitEnt - > client )
{
class_t npc_class = hitEnt - > client - > NPC_class ;
if ( npc_class = = CLASS_SEEKER | | npc_class = = CLASS_PROBE | | npc_class = = CLASS_MOUSE | |
npc_class = = CLASS_GONK | | npc_class = = CLASS_R2D2 | | npc_class = = CLASS_R5D2 | | npc_class = = CLASS_REMOTE | |
npc_class = = CLASS_PROTOCOL | | npc_class = = CLASS_MARK1 | | npc_class = = CLASS_MARK2 | |
npc_class = = CLASS_INTERROGATOR | | npc_class = = CLASS_ATST | | npc_class = = CLASS_SENTRY )
{
hitEffect = hit_sparks ;
}
}
else
{
hitEffect = hit_sparks ;
}
}
G_PlayEffect ( hitEffect , tr . endpos , dir ) ; //"saber_cut"
}
else
{ //we were doing a ghoul trace
if ( ! WP_SaberDamageEffects ( & tr , start , len , dmg , dir , bladeDir , attacker - > client - > enemyTeam ) )
{ //didn't hit a ghoul ent
/*
if ( & & hitEnt - > ghoul2 . size ( ) )
{ //it was a ghoul2 model so we should have hit it
return qfalse ;
}
*/
}
}
}
}
}
return qfalse ;
}
typedef enum
{
LOCK_FIRST = 0 ,
LOCK_TOP = LOCK_FIRST ,
LOCK_DIAG_TR ,
LOCK_DIAG_TL ,
LOCK_DIAG_BR ,
LOCK_DIAG_BL ,
LOCK_R ,
LOCK_L ,
LOCK_RANDOM
} sabersLockMode_t ;
# define LOCK_IDEAL_DIST_TOP 32.0f
# define LOCK_IDEAL_DIST_CIRCLE 48.0f
extern void PM_SetAnimFrame ( gentity_t * gent , int frame , qboolean torso , qboolean legs ) ;
extern qboolean ValidAnimFileIndex ( int index ) ;
qboolean WP_SabersCheckLock2 ( gentity_t * attacker , gentity_t * defender , sabersLockMode_t lockMode )
{
animation_t * anim ;
int attAnim , defAnim , advance = 0 ;
float attStart = 0.5f ;
float idealDist = 48.0f ;
//MATCH ANIMS
if ( lockMode = = LOCK_RANDOM )
{
lockMode = ( sabersLockMode_t ) Q_irand ( ( int ) LOCK_FIRST , ( int ) ( LOCK_RANDOM ) - 1 ) ;
}
switch ( lockMode )
{
case LOCK_TOP :
attAnim = BOTH_BF2LOCK ;
defAnim = BOTH_BF1LOCK ;
attStart = 0.5f ;
idealDist = LOCK_IDEAL_DIST_TOP ;
break ;
case LOCK_DIAG_TR :
attAnim = BOTH_CCWCIRCLELOCK ;
defAnim = BOTH_CWCIRCLELOCK ;
attStart = 0.5f ;
idealDist = LOCK_IDEAL_DIST_CIRCLE ;
break ;
case LOCK_DIAG_TL :
attAnim = BOTH_CWCIRCLELOCK ;
defAnim = BOTH_CCWCIRCLELOCK ;
attStart = 0.5f ;
idealDist = LOCK_IDEAL_DIST_CIRCLE ;
break ;
case LOCK_DIAG_BR :
attAnim = BOTH_CWCIRCLELOCK ;
defAnim = BOTH_CCWCIRCLELOCK ;
attStart = 0.85f ;
idealDist = LOCK_IDEAL_DIST_CIRCLE ;
break ;
case LOCK_DIAG_BL :
attAnim = BOTH_CCWCIRCLELOCK ;
defAnim = BOTH_CWCIRCLELOCK ;
attStart = 0.85f ;
idealDist = LOCK_IDEAL_DIST_CIRCLE ;
break ;
case LOCK_R :
attAnim = BOTH_CWCIRCLELOCK ;
defAnim = BOTH_CCWCIRCLELOCK ;
attStart = 0.75f ;
idealDist = LOCK_IDEAL_DIST_CIRCLE ;
break ;
case LOCK_L :
attAnim = BOTH_CCWCIRCLELOCK ;
defAnim = BOTH_CWCIRCLELOCK ;
attStart = 0.75f ;
idealDist = LOCK_IDEAL_DIST_CIRCLE ;
break ;
default :
return qfalse ;
break ;
}
NPC_SetAnim ( attacker , SETANIM_BOTH , attAnim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
NPC_SetAnim ( defender , SETANIM_BOTH , defAnim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
if ( ValidAnimFileIndex ( attacker - > client - > clientInfo . animFileIndex ) )
{
anim = & level . knownAnimFileSets [ attacker - > client - > clientInfo . animFileIndex ] . animations [ attAnim ] ;
advance = floor ( anim - > numFrames * attStart ) ;
PM_SetAnimFrame ( attacker , anim - > firstFrame + advance , qtrue , qtrue ) ;
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
Com_Printf ( " %s starting saber lock, anim = %s, %d frames to go! \n " , attacker - > NPC_type , animTable [ attAnim ] . name , anim - > numFrames - advance ) ;
}
# endif
}
if ( ValidAnimFileIndex ( defender - > client - > clientInfo . animFileIndex ) )
{
anim = & level . knownAnimFileSets [ defender - > client - > clientInfo . animFileIndex ] . animations [ defAnim ] ;
PM_SetAnimFrame ( defender , anim - > firstFrame + advance , qtrue , qtrue ) ; //was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
Com_Printf ( " %s starting saber lock, anim = %s, %d frames to go! \n " , defender - > NPC_type , animTable [ defAnim ] . name , advance ) ;
}
# endif
}
VectorClear ( attacker - > client - > ps . velocity ) ;
VectorClear ( defender - > client - > ps . velocity ) ;
attacker - > client - > ps . saberLockTime = defender - > client - > ps . saberLockTime = level . time + SABER_LOCK_TIME ;
attacker - > client - > ps . legsAnimTimer = attacker - > client - > ps . torsoAnimTimer = defender - > client - > ps . legsAnimTimer = defender - > client - > ps . torsoAnimTimer = SABER_LOCK_TIME ;
//attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME;
attacker - > client - > ps . saberLockEnemy = defender - > s . number ;
defender - > client - > ps . saberLockEnemy = attacker - > s . number ;
//MATCH ANGLES
//FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer?
float defPitchAdd = 0 , zDiff = ( ( attacker - > currentOrigin [ 2 ] + attacker - > client - > standheight ) - ( defender - > currentOrigin [ 2 ] + defender - > client - > standheight ) ) ;
if ( zDiff > 24 )
{
defPitchAdd = - 30 ;
}
else if ( zDiff < - 24 )
{
defPitchAdd = 30 ;
}
else
{
defPitchAdd = zDiff / 24.0f * - 30.0f ;
}
if ( attacker - > NPC & & defender - > NPC )
{ //if 2 NPCs, just set pitch to 0
attacker - > client - > ps . viewangles [ PITCH ] = - defPitchAdd ;
defender - > client - > ps . viewangles [ PITCH ] = defPitchAdd ;
}
else
{ //if a player is involved, clamp player's pitch and match NPC's to player
if ( ! attacker - > s . number )
{
//clamp to defPitch
if ( attacker - > client - > ps . viewangles [ PITCH ] > - defPitchAdd + 10 )
{
attacker - > client - > ps . viewangles [ PITCH ] = - defPitchAdd + 10 ;
}
else if ( attacker - > client - > ps . viewangles [ PITCH ] < - defPitchAdd - 10 )
{
attacker - > client - > ps . viewangles [ PITCH ] = - defPitchAdd - 10 ;
}
//clamp to sane numbers
if ( attacker - > client - > ps . viewangles [ PITCH ] > 50 )
{
attacker - > client - > ps . viewangles [ PITCH ] = 50 ;
}
else if ( attacker - > client - > ps . viewangles [ PITCH ] < - 50 )
{
attacker - > client - > ps . viewangles [ PITCH ] = - 50 ;
}
defender - > client - > ps . viewangles [ PITCH ] = attacker - > client - > ps . viewangles [ PITCH ] * - 1 ;
defPitchAdd = defender - > client - > ps . viewangles [ PITCH ] ;
}
else if ( ! defender - > s . number )
{
//clamp to defPitch
if ( defender - > client - > ps . viewangles [ PITCH ] > defPitchAdd + 10 )
{
defender - > client - > ps . viewangles [ PITCH ] = defPitchAdd + 10 ;
}
else if ( defender - > client - > ps . viewangles [ PITCH ] < defPitchAdd - 10 )
{
defender - > client - > ps . viewangles [ PITCH ] = defPitchAdd - 10 ;
}
//clamp to sane numbers
if ( defender - > client - > ps . viewangles [ PITCH ] > 50 )
{
defender - > client - > ps . viewangles [ PITCH ] = 50 ;
}
else if ( defender - > client - > ps . viewangles [ PITCH ] < - 50 )
{
defender - > client - > ps . viewangles [ PITCH ] = - 50 ;
}
defPitchAdd = defender - > client - > ps . viewangles [ PITCH ] ;
attacker - > client - > ps . viewangles [ PITCH ] = defender - > client - > ps . viewangles [ PITCH ] * - 1 ;
}
}
vec3_t attAngles , defAngles , defDir ;
VectorSubtract ( defender - > currentOrigin , attacker - > currentOrigin , defDir ) ;
VectorCopy ( attacker - > client - > ps . viewangles , attAngles ) ;
attAngles [ YAW ] = vectoyaw ( defDir ) ;
SetClientViewAngle ( attacker , attAngles ) ;
defAngles [ PITCH ] = attAngles [ PITCH ] * - 1 ;
defAngles [ YAW ] = AngleNormalize180 ( attAngles [ YAW ] + 180 ) ;
defAngles [ ROLL ] = 0 ;
SetClientViewAngle ( defender , defAngles ) ;
//MATCH POSITIONS
vec3_t newOrg ;
/*
idealDist - = fabs ( defPitchAdd ) / 8.0f ;
*/
float scale = VectorLength ( attacker - > s . modelScale ) ;
if ( scale )
{
idealDist + = 8 * ( scale - 1.0f ) ;
}
scale = VectorLength ( defender - > s . modelScale ) ;
if ( scale )
{
idealDist + = 8 * ( scale - 1.0f ) ;
}
float diff = VectorNormalize ( defDir ) - idealDist ; //diff will be the total error in dist
//try to move attacker half the diff towards the defender
VectorMA ( attacker - > currentOrigin , diff * 0.5f , defDir , newOrg ) ;
trace_t trace ;
gi . trace ( & trace , attacker - > currentOrigin , attacker - > mins , attacker - > maxs , newOrg , attacker - > s . number , attacker - > clipmask , G2_NOCOLLIDE , 0 ) ;
if ( ! trace . startsolid & & ! trace . allsolid )
{
G_SetOrigin ( attacker , trace . endpos ) ;
gi . linkentity ( attacker ) ;
}
//now get the defender's dist and do it for him too
vec3_t attDir ;
VectorSubtract ( attacker - > currentOrigin , defender - > currentOrigin , attDir ) ;
diff = VectorNormalize ( attDir ) - idealDist ; //diff will be the total error in dist
//try to move defender all of the remaining diff towards the attacker
VectorMA ( defender - > currentOrigin , diff , attDir , newOrg ) ;
gi . trace ( & trace , defender - > currentOrigin , defender - > mins , defender - > maxs , newOrg , defender - > s . number , defender - > clipmask , G2_NOCOLLIDE , 0 ) ;
if ( ! trace . startsolid & & ! trace . allsolid )
{
G_SetOrigin ( defender , trace . endpos ) ;
gi . linkentity ( defender ) ;
}
//DONE!
return qtrue ;
}
qboolean WP_SabersCheckLock ( gentity_t * ent1 , gentity_t * ent2 )
{
if ( ent1 - > client - > playerTeam = = ent2 - > client - > playerTeam )
{
return qfalse ;
}
if ( ent1 - > client - > ps . groundEntityNum = = ENTITYNUM_NONE | |
ent2 - > client - > ps . groundEntityNum = = ENTITYNUM_NONE )
{
return qfalse ;
}
if ( ent1 - > painDebounceTime > level . time - 1000 | | ent2 - > painDebounceTime > level . time - 1000 )
{ //can't saberlock if you're not ready
return qfalse ;
}
if ( fabs ( ent1 - > currentOrigin [ 2 ] - ent2 - > currentOrigin [ 2 ] ) > 18 )
{
return qfalse ;
}
float dist = DistanceSquared ( ent1 - > currentOrigin , ent2 - > currentOrigin ) ;
if ( dist < 64 | | dist > 6400 ) //( dist < 128 || dist > 2304 )
{ //between 8 and 80 from each other//was 16 and 48
return qfalse ;
}
if ( ! InFOV ( ent1 , ent2 , 40 , 180 ) | | ! InFOV ( ent2 , ent1 , 40 , 180 ) )
{
return qfalse ;
}
//Check for certain anims that *cannot* lock
//FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A2_STABBACK1 & & ent1 - > client - > ps . torsoAnimTimer > 300 )
{ //can't lock when saber is behind you
return qfalse ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A2_STABBACK1 & & ent2 - > client - > ps . torsoAnimTimer > 300 )
{ //can't lock when saber is behind you
return qfalse ;
}
//BR to TL lock
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_BR_TL )
{ //ent1 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_BR ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_BR_TL )
{ //ent2 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_BR ) ;
}
//BL to TR lock
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_BL_TR )
{ //ent1 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_BL ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_BL_TR )
{ //ent2 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_BL ) ;
}
//L to R lock
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1__L__R | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2__L__R | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3__L__R | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4__L__R | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5__L__R )
{ //ent1 is attacking l to r
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_L ) ;
/*
if ( ent2BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_L ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_BL )
{ //ent2 is attacking or blocking on the r
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_L ) ;
}
if ( ent2Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_L ) ;
}
*/
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1__L__R | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2__L__R | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3__L__R | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4__L__R | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5__L__R )
{ //ent2 is attacking l to r
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_L ) ;
/*
if ( ent1BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_L ) ;
}
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_BL )
{ //ent1 is attacking or blocking on the r
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_L ) ;
}
if ( ent1Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_L ) ;
}
*/
}
//R to L lock
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1__R__L | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2__R__L | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3__R__L | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4__R__L | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5__R__L )
{ //ent1 is attacking r to l
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_R ) ;
/*
if ( ent2BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_R ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_BR )
{ //ent2 is attacking or blocking on the l
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_R ) ;
}
if ( ent2Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_R ) ;
}
*/
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1__R__L | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2__R__L | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3__R__L | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4__R__L | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5__R__L )
{ //ent2 is attacking r to l
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_R ) ;
/*
if ( ent1BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_R ) ;
}
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_BR )
{ //ent1 is attacking or blocking on the l
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_R ) ;
}
if ( ent1Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_R ) ;
}
*/
}
//TR to BL lock
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_TR_BL )
{ //ent1 is attacking diagonally
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TR ) ;
/*
if ( ent2BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TR ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_TL )
{ //ent2 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TR ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_BR_TL | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_BL )
{ //ent2 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_BL ) ;
}
if ( ent2Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TR ) ;
}
*/
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_TR_BL | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_TR_BL )
{ //ent2 is attacking diagonally
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TR ) ;
/*
if ( ent1BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TR ) ;
}
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_TR_BL | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_TL )
{ //ent1 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TR ) ;
}
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_BR_TL | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_BL )
{ //ent1 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_BL ) ;
}
if ( ent1Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TR ) ;
}
*/
}
//TL to BR lock
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_TL_BR )
{ //ent1 is attacking diagonally
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TL ) ;
/*
if ( ent2BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TL ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_TR )
{ //ent2 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TL ) ;
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_BL_TR | |
ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_BR )
{ //ent2 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_BR ) ;
}
if ( ent2Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_DIAG_TL ) ;
}
*/
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_TL_BR | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_TL_BR )
{ //ent2 is attacking diagonally
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TL ) ;
/*
if ( ent1BlockingPlayer )
{ //player will block this anyway
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TL ) ;
}
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_TL_BR | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_TR )
{ //ent1 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TL ) ;
}
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_BL_TR | |
ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_BR )
{ //ent1 is attacking in the opposite diagonal
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_BR ) ;
}
if ( ent1Boss & & ! Q_irand ( 0 , 3 ) )
{
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_DIAG_TL ) ;
}
*/
}
//T to B lock
if ( ent1 - > client - > ps . torsoAnim = = BOTH_A1_T__B_ | |
ent1 - > client - > ps . torsoAnim = = BOTH_A2_T__B_ | |
ent1 - > client - > ps . torsoAnim = = BOTH_A3_T__B_ | |
ent1 - > client - > ps . torsoAnim = = BOTH_A4_T__B_ | |
ent1 - > client - > ps . torsoAnim = = BOTH_A5_T__B_ )
{ //ent1 is attacking top-down
/*
if ( ent2 - > client - > ps . torsoAnim = = BOTH_P1_S1_T_ | |
ent2 - > client - > ps . torsoAnim = = BOTH_K1_S1_T_ )
*/
{ //ent2 is blocking at top
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_TOP ) ;
}
}
if ( ent2 - > client - > ps . torsoAnim = = BOTH_A1_T__B_ | |
ent2 - > client - > ps . torsoAnim = = BOTH_A2_T__B_ | |
ent2 - > client - > ps . torsoAnim = = BOTH_A3_T__B_ | |
ent2 - > client - > ps . torsoAnim = = BOTH_A4_T__B_ | |
ent2 - > client - > ps . torsoAnim = = BOTH_A5_T__B_ )
{ //ent2 is attacking top-down
/*
if ( ent1 - > client - > ps . torsoAnim = = BOTH_P1_S1_T_ | |
ent1 - > client - > ps . torsoAnim = = BOTH_K1_S1_T_ )
*/
{ //ent1 is blocking at top
return WP_SabersCheckLock2 ( ent2 , ent1 , LOCK_TOP ) ;
}
}
/*
if ( ! Q_irand ( 0 , 10 ) )
{
return WP_SabersCheckLock2 ( ent1 , ent2 , LOCK_RANDOM ) ;
}
*/
return qfalse ;
}
qboolean WP_SaberParry ( gentity_t * victim , gentity_t * attacker )
{
if ( ! victim | | ! victim - > client | | ! attacker )
{
return qfalse ;
}
if ( victim - > s . number | | g_saberAutoBlocking - > integer | | victim - > client - > ps . saberBlockingTime > level . time )
{ //either an NPC or a player who is blocking
if ( ! PM_SaberInTransitionAny ( victim - > client - > ps . saberMove )
& & ! PM_SaberInBounce ( victim - > client - > ps . saberMove )
& & ! PM_SaberInKnockaway ( victim - > client - > ps . saberMove ) )
{ //I'm not attacking, in transition or in a bounce or knockaway, so play a parry
WP_SaberBlockNonRandom ( victim , saberHitLocation , qfalse ) ;
}
victim - > client - > ps . saberEventFlags | = SEF_PARRIED ;
//since it was parried, take away any damage done
//FIXME: what if the damage was done before the parry?
WP_SaberClearDamageForEntNum ( victim - > s . number ) ;
//tell the victim to get mad at me
if ( victim - > enemy ! = attacker & & victim - > client - > playerTeam ! = attacker - > client - > playerTeam )
{ //they're not mad at me and they're not on my team
G_ClearEnemy ( victim ) ;
G_SetEnemy ( victim , attacker ) ;
}
return qtrue ;
}
return qfalse ;
}
qboolean WP_BrokenParryKnockDown ( gentity_t * victim )
{
if ( ! victim | | ! victim - > client )
{
return qfalse ;
}
if ( victim - > client - > ps . saberMove = = LS_PARRY_UP
| | victim - > client - > ps . saberMove = = LS_PARRY_UR
| | victim - > client - > ps . saberMove = = LS_PARRY_UL
| | victim - > client - > ps . saberMove = = LS_H1_BR
| | victim - > client - > ps . saberMove = = LS_H1_B_
| | victim - > client - > ps . saberMove = = LS_H1_BL )
{ //knock their asses down!
int knockAnim = BOTH_KNOCKDOWN1 ;
if ( PM_CrouchAnim ( victim - > client - > ps . legsAnim ) )
{
knockAnim = BOTH_KNOCKDOWN4 ;
}
NPC_SetAnim ( victim , SETANIM_BOTH , knockAnim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
G_AddEvent ( victim , EV_PAIN , victim - > health ) ;
return qtrue ;
}
return qfalse ;
}
/*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void WP_SaberDamageTrace ( gentity_t * ent )
Constantly trace from the old blade pos to new , down the saber beam and do damage
FIXME : if the dot product of the old muzzle dir and the new muzzle dir is < 0.75 , subdivide it and do multiple traces so we don ' t flatten out the arc !
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
# define MAX_SABER_SWING_INC 0.33f
void WP_SaberDamageTrace ( gentity_t * ent )
{
vec3_t mp1 , mp2 , md1 , md2 , baseOld , baseNew , baseDiff , endOld , endNew , bladePointOld , bladePointNew ;
float tipDmgMod = 1.0f ;
float baseDamage ;
int baseDFlags = 0 ;
qboolean hit_wall = qfalse ;
qboolean brokenParry = qfalse ;
for ( int ven = 0 ; ven < MAX_SABER_VICTIMS ; ven + + )
{
victimEntityNum [ ven ] = ENTITYNUM_NONE ;
}
memset ( totalDmg , 0 , sizeof ( totalDmg ) ) ;
memset ( dmgDir , 0 , sizeof ( dmgDir ) ) ;
memset ( dmgSpot , 0 , sizeof ( dmgSpot ) ) ;
memset ( dmgFraction , 0 , sizeof ( dmgFraction ) ) ;
memset ( hitLoc , HL_NONE , sizeof ( hitLoc ) ) ;
memset ( hitDismemberLoc , HL_NONE , sizeof ( hitDismemberLoc ) ) ;
memset ( hitDismember , qfalse , sizeof ( hitDismember ) ) ;
numVictims = 0 ;
VectorClear ( saberHitLocation ) ;
VectorClear ( saberHitNormal ) ;
saberHitFraction = 1.0 ; // Closest saber hit. The saber can do no damage past this point.
saberHitEntity = ENTITYNUM_NONE ;
sabersCrossed = - 1 ;
if ( ! ent - > client )
{
return ;
}
if ( ! ent - > s . number )
{ //player never uses these
ent - > client - > ps . saberEventFlags & = ~ SEF_EVENTS ;
}
if ( ent - > client - > ps . saberLength < = 1 ) //cen get down to 1 when in a wall
{ //saber is not on
return ;
}
if ( VectorCompare ( ent - > client - > renderInfo . muzzlePointOld , vec3_origin ) | | VectorCompare ( ent - > client - > renderInfo . muzzleDirOld , vec3_origin ) )
{
//just started up the saber?
return ;
}
int saberContents = gi . pointcontents ( ent - > client - > renderInfo . muzzlePoint , ent - > client - > ps . saberEntityNum ) ;
if ( ( saberContents & CONTENTS_WATER ) | |
( saberContents & CONTENTS_SLIME ) | |
( saberContents & CONTENTS_LAVA ) )
{ //um... turn off? Or just set length to 1?
//FIXME: short-out effect/sound?
ent - > client - > ps . saberActive = qfalse ;
return ;
}
else if ( saberContents & CONTENTS_OUTSIDE )
{
if ( ( level . worldFlags & WF_RAINING ) )
{
//add steam in rain
if ( Q_flrand ( 0 , 500 ) < ent - > client - > ps . saberLength )
{
vec3_t end , normal = { 0 , 0 , 1 } ; //FIXME: opposite of rain angles?
VectorMA ( ent - > client - > renderInfo . muzzlePoint , ent - > client - > ps . saberLength * Q_flrand ( 0 , 1 ) , ent - > client - > renderInfo . muzzleDir , end ) ;
G_PlayEffect ( " saber/fizz " , end , normal ) ;
}
}
}
//FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because
// we animate so much faster that the arc is pretty much flat...
int entPowerLevel ;
if ( ! ent - > s . number & & ( ent - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) ) )
{
entPowerLevel = FORCE_LEVEL_3 ;
}
else
{
entPowerLevel = PM_PowerLevelForSaberAnim ( & ent - > client - > ps ) ;
}
if ( ent - > client - > ps . saberInFlight )
{ //flying sabers are much more deadly
//unless you're dead
if ( ent - > health < = 0 & & ! g_saberRealisticCombat - > integer )
{ //so enemies don't keep trying to block it
//FIXME: still do damage, just not to humanoid clients who should try to avoid it
//baseDamage = 0.0f;
return ;
}
//or unless returning
else if ( ent - > client - > ps . saberEntityState = = SES_RETURNING )
{ //special case, since we're returning, chances are if we hit something
//it's going to be butt-first. So do less damage.
baseDamage = 0.1f ;
}
else
{
if ( ! ent - > s . number )
{ //cheat for player
baseDamage = 10.0f ;
}
else
{
baseDamage = 2.5f ;
}
}
//Use old to current since can't predict it
VectorCopy ( ent - > client - > renderInfo . muzzlePointOld , mp1 ) ;
VectorCopy ( ent - > client - > renderInfo . muzzleDirOld , md1 ) ;
VectorCopy ( ent - > client - > renderInfo . muzzlePoint , mp2 ) ;
VectorCopy ( ent - > client - > renderInfo . muzzleDir , md2 ) ;
}
else
{
if ( ent - > client - > ps . saberMove = = LS_READY )
{ //just do effects
if ( g_saberRealisticCombat - > integer < 2 )
{ //don't kill with this hit
baseDFlags = DAMAGE_NO_KILL ;
}
baseDamage = 0 ;
}
else if ( ent - > client - > ps . saberLockTime > level . time )
{ //just do effects
baseDamage = 0 ;
}
else if ( ent - > client - > ps . saberBlocked > BLOCKED_NONE
| | ( ! PM_SaberInAttack ( ent - > client - > ps . saberMove )
& & ! PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim )
& & ! PM_SaberInTransitionAny ( ent - > client - > ps . saberMove )
)
)
{ //don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks
baseDamage = 0 ;
}
else
{ //okay, in a saberMove that does damage
//make sure we're in the right anim
if ( ! PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim )
& & ! PM_InAnimForSaberMove ( ent - > client - > ps . torsoAnim , ent - > client - > ps . saberMove ) )
{ //forced into some other animation somehow, like a pain or death?
baseDamage = 0 ;
}
else if ( ent - > client - > ps . weaponstate = = WEAPON_FIRING & & ent - > client - > ps . saberBlocked = = BLOCKED_NONE & &
( PM_SaberInAttack ( ent - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim ) | | PM_SpinningSaberAnim ( ent - > client - > ps . torsoAnim ) | | entPowerLevel > FORCE_LEVEL_2 ) )
{ //normal attack swing swinging/spinning (or if using strong set), do normal damage
//FIXME: more damage for higher attack power levels?
// More damage based on length/color of saber?
//FIXME: Desann does double damage?
if ( g_saberRealisticCombat - > integer )
{
switch ( entPowerLevel )
{
case FORCE_LEVEL_3 :
baseDamage = 10.0f ;
break ;
case FORCE_LEVEL_2 :
baseDamage = 5.0f ;
break ;
default :
case FORCE_LEVEL_1 :
baseDamage = 2.5f ;
break ;
}
}
else
{
baseDamage = 2.5f * ( float ) entPowerLevel ;
}
}
else
{ //saber is transitioning, defending or idle, don't do as much damage
//FIXME: strong attacks and returns should do damage and be unblockable
if ( g_timescale - > value < 1.0 )
{ //in slow mo or force speed, we need to do damage during the transitions
if ( g_saberRealisticCombat - > integer )
{
switch ( entPowerLevel )
{
case FORCE_LEVEL_3 :
baseDamage = 10.0f ;
break ;
case FORCE_LEVEL_2 :
baseDamage = 5.0f ;
break ;
default :
case FORCE_LEVEL_1 :
baseDamage = 2.5f ;
break ;
}
}
else
{
baseDamage = 2.5f * ( float ) entPowerLevel ;
}
}
else // if ( !ent->s.number )
{ //I have to do *some* damage in transitions or else you feel like a total gimp
baseDamage = 0.1f ;
}
/*
else
{
baseDamage = 0 ; //was 1.0f;//was 0.25
}
*/
}
}
//Use current to next since can predict it
//FIXME: if they're closer than the saber blade start, we don't want the
// arm to pass through them without any damage... so check the radius
// and push them away (do pain & knockback)
//FIXME: if going into/coming from a parry/reflection or going into a deflection, don't use old mp & dir? Otherwise, deflections will cut through?
//VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 );
//VectorCopy( ent->client->renderInfo.muzzleDir, md1 );
//VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 );
//VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 );
//prediction was causing gaps in swing (G2 problem) so *don't* predict
VectorCopy ( ent - > client - > renderInfo . muzzlePointOld , mp1 ) ;
VectorCopy ( ent - > client - > renderInfo . muzzleDirOld , md1 ) ;
VectorCopy ( ent - > client - > renderInfo . muzzlePoint , mp2 ) ;
VectorCopy ( ent - > client - > renderInfo . muzzleDir , md2 ) ;
//NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow...
//see if anyone is so close that they're within the dist from my origin to the start of the saber
if ( ent - > health > 0 & & ! ent - > client - > ps . saberLockTime )
{
trace_t trace ;
gi . trace ( & trace , ent - > currentOrigin , vec3_origin , vec3_origin , mp1 , ent - > s . number , ( MASK_SHOT & ~ ( CONTENTS_CORPSE | CONTENTS_ITEM ) ) , G2_NOCOLLIDE , 0 ) ;
if ( trace . entityNum < ENTITYNUM_WORLD & & ( trace . entityNum > 0 | | ent - > client - > NPC_class = = CLASS_DESANN ) ) //NPCs don't push player away, unless it's Desann
{ //a valid ent
gentity_t * traceEnt = & g_entities [ trace . entityNum ] ;
if ( traceEnt
& & traceEnt - > client
& & traceEnt - > health > 0
& & traceEnt - > client - > playerTeam ! = ent - > client - > playerTeam
& & ! PM_InKnockDown ( & traceEnt - > client - > ps ) )
{ //enemy client, push them away
if ( ! traceEnt - > client - > ps . saberLockTime & & ! traceEnt - > message )
{ //don't push people in saberlock or with security keys
vec3_t hitDir ;
VectorSubtract ( trace . endpos , ent - > currentOrigin , hitDir ) ;
float totalDist = Distance ( mp1 , ent - > currentOrigin ) ;
float knockback = ( totalDist - VectorNormalize ( hitDir ) ) / totalDist * 200.0f ;
hitDir [ 2 ] = 0 ;
//FIXME: do we need to call G_Throw? Seems unfair to put actual knockback on them, stops the attack
//G_Throw( traceEnt, hitDir, knockback );
VectorMA ( traceEnt - > client - > ps . velocity , knockback , hitDir , traceEnt - > client - > ps . velocity ) ;
traceEnt - > client - > ps . pm_time = 200 ;
traceEnt - > client - > ps . pm_flags | = PMF_TIME_NOFRICTION ;
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( " %s pushing away %s at %s \n " , ent - > NPC_type , traceEnt - > NPC_type , vtos ( traceEnt - > client - > ps . velocity ) ) ;
}
# endif
}
}
}
}
}
if ( g_saberRealisticCombat - > integer > 1 )
{ //always do damage, and lots of it
if ( g_saberRealisticCombat - > integer > 2 )
{ //always do damage, and lots of it
baseDamage = 25.0f ;
}
else if ( baseDamage > 0.1f )
{ //only do super damage if we would have done damage according to normal rules
baseDamage = 25.0f ;
}
}
else if ( ( ! ent - > s . number & & ent - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) ) )
{
baseDamage * = ( 1.0f - g_timescale - > value ) ;
}
// Get the old state of the blade
VectorCopy ( mp1 , baseOld ) ;
VectorMA ( baseOld , ent - > client - > ps . saberLength , md1 , endOld ) ;
// Get the future state of the blade
VectorCopy ( mp2 , baseNew ) ;
VectorMA ( baseNew , ent - > client - > ps . saberLength , md2 , endNew ) ;
sabersCrossed = - 1 ;
if ( VectorCompare2 ( baseOld , baseNew ) & & VectorCompare2 ( endOld , endNew ) )
{
hit_wall = WP_SaberDamageForTrace ( ent - > s . number , mp2 , endNew , baseDamage * 4 , md2 , qfalse , entPowerLevel , qfalse ) ;
}
else
{
float aveLength , step = 8 , stepsize = 8 ;
vec3_t ma1 , ma2 , md2ang , curBase1 , curBase2 ;
int xx ;
//do the trace at the base first
hit_wall = WP_SaberDamageForTrace ( ent - > s . number , baseOld , baseNew , baseDamage , md2 , qfalse , entPowerLevel ) ;
//if hit a saber, shorten rest of traces to match
if ( saberHitFraction < 1.0 )
{
//adjust muzzleDir...
vec3_t ma1 , ma2 ;
vectoangles ( md1 , ma1 ) ;
vectoangles ( md2 , ma2 ) ;
for ( xx = 0 ; xx < 3 ; xx + + )
{
md2ang [ xx ] = LerpAngle ( ma1 [ xx ] , ma2 [ xx ] , saberHitFraction ) ;
}
AngleVectors ( md2ang , md2 , NULL , NULL ) ;
//shorten the base pos
VectorSubtract ( mp2 , mp1 , baseDiff ) ;
VectorMA ( mp1 , saberHitFraction , baseDiff , baseNew ) ;
VectorMA ( baseNew , ent - > client - > ps . saberLength , md2 , endNew ) ;
}
//If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
float dirInc , curDirFrac ;
if ( PM_SaberInAttack ( ent - > client - > ps . saberMove )
| | PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim )
| | PM_SpinningSaberAnim ( ent - > client - > ps . torsoAnim )
| | PM_InSpecialJump ( ent - > client - > ps . torsoAnim )
| | ( g_timescale - > value < 1.0f & & PM_SaberInTransitionAny ( ent - > client - > ps . saberMove ) ) )
{
curDirFrac = DotProduct ( md1 , md2 ) ;
}
else
{
curDirFrac = 1.0f ;
}
//NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
if ( fabs ( curDirFrac ) < 1.0f - MAX_SABER_SWING_INC )
{ //the saber blade spun more than 33 degrees since the last damage trace
curDirFrac = dirInc = 1.0f / ( ( 1.0f - curDirFrac ) / MAX_SABER_SWING_INC ) ;
}
else
{
curDirFrac = 1.0f ;
dirInc = 0.0f ;
}
qboolean hit_saber = qfalse ;
vectoangles ( md1 , ma1 ) ;
vectoangles ( md2 , ma2 ) ;
vec3_t curMD1 , curMD2 ; //, mdDiff, dirDiff;
//VectorSubtract( md2, md1, mdDiff );
VectorCopy ( md1 , curMD2 ) ;
VectorCopy ( baseOld , curBase2 ) ;
while ( 1 )
{
VectorCopy ( curMD2 , curMD1 ) ;
VectorCopy ( curBase2 , curBase1 ) ;
if ( curDirFrac > = 1.0f )
{
VectorCopy ( md2 , curMD2 ) ;
VectorCopy ( baseNew , curBase2 ) ;
}
else
{
for ( xx = 0 ; xx < 3 ; xx + + )
{
md2ang [ xx ] = LerpAngle ( ma1 [ xx ] , ma2 [ xx ] , curDirFrac ) ;
}
AngleVectors ( md2ang , curMD2 , NULL , NULL ) ;
//VectorMA( md1, curDirFrac, mdDiff, curMD2 );
VectorSubtract ( baseNew , baseOld , baseDiff ) ;
VectorMA ( baseOld , curDirFrac , baseDiff , curBase2 ) ;
}
// Move up the blade in intervals of stepsize
for ( step = stepsize ; step < ent - > client - > ps . saberLength & & step < ent - > client - > ps . saberLengthOld ; step + = 12 )
{
VectorMA ( curBase1 , step , curMD1 , bladePointOld ) ;
VectorMA ( curBase2 , step , curMD2 , bladePointNew ) ;
if ( WP_SaberDamageForTrace ( ent - > s . number , bladePointOld , bladePointNew , baseDamage , curMD2 , qfalse , entPowerLevel ) )
{
hit_wall = qtrue ;
}
//if hit a saber, shorten rest of traces to match
if ( saberHitFraction < 1.0 )
{
//adjust muzzle endpoint
VectorSubtract ( mp2 , mp1 , baseDiff ) ;
VectorMA ( mp1 , saberHitFraction , baseDiff , baseNew ) ;
VectorMA ( baseNew , ent - > client - > ps . saberLength , curMD2 , endNew ) ;
//adjust muzzleDir...
vec3_t curMA1 , curMA2 ;
vectoangles ( curMD1 , curMA1 ) ;
vectoangles ( curMD2 , curMA2 ) ;
for ( xx = 0 ; xx < 3 ; xx + + )
{
md2ang [ xx ] = LerpAngle ( curMA1 [ xx ] , curMA2 [ xx ] , saberHitFraction ) ;
}
AngleVectors ( md2ang , curMD2 , NULL , NULL ) ;
/*
VectorSubtract ( curMD2 , curMD1 , dirDiff ) ;
VectorMA ( curMD1 , saberHitFraction , dirDiff , curMD2 ) ;
*/
hit_saber = qtrue ;
}
if ( hit_wall )
{
break ;
}
}
if ( hit_wall | | hit_saber )
{
break ;
}
if ( curDirFrac > = 1.0f )
{
break ;
}
else
{
curDirFrac + = dirInc ;
if ( curDirFrac > = 1.0f )
{
curDirFrac = 1.0f ;
}
}
}
//do the trace at the end last
//Special check- adjust for length of blade not being a multiple of 12
aveLength = ( ent - > client - > ps . saberLengthOld + ent - > client - > ps . saberLength ) / 2 ;
if ( step > aveLength )
{ //less dmg if the last interval was not stepsize
tipDmgMod = ( stepsize - ( step - aveLength ) ) / stepsize ;
}
//NOTE: since this is the tip, we do not extrapolate the extra 16
if ( WP_SaberDamageForTrace ( ent - > s . number , endOld , endNew , tipDmgMod * baseDamage , md2 , qfalse , entPowerLevel , qfalse ) )
{
hit_wall = qtrue ;
}
}
if ( ( saberHitFraction < 1.0f | | ( sabersCrossed > = 0 & & sabersCrossed < = 32.0f ) ) & & ( ent - > client - > ps . weaponstate = = WEAPON_FIRING | | ent - > client - > ps . saberInFlight ) )
{ // The saber (in-hand) hit another saber, mano.
qboolean inFlightSaberBlocked = qfalse ;
qboolean collisionResolved = qfalse ;
qboolean deflected = qfalse ;
gentity_t * hitEnt = & g_entities [ saberHitEntity ] ;
gentity_t * hitOwner = NULL ;
int hitOwnerPowerLevel = FORCE_LEVEL_0 ;
if ( hitEnt )
{
hitOwner = hitEnt - > owner ;
}
if ( hitOwner & & hitOwner - > client )
{
hitOwnerPowerLevel = PM_PowerLevelForSaberAnim ( & hitOwner - > client - > ps ) ;
if ( entPowerLevel = = FORCE_LEVEL_3 & & PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim ) )
{ //a special "unblockable" attack
if ( hitOwner - > client - > NPC_class = = CLASS_DESANN
| | hitOwner - > client - > NPC_class = = CLASS_TAVION
| | hitOwner - > client - > NPC_class = = CLASS_LUKE )
{ //these masters can even block unblockables (stops cheap kills)
entPowerLevel = FORCE_LEVEL_2 ;
}
}
}
//FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose
//SEF_LOCKED
if ( ent - > client - > ps . saberInFlight & &
ent - > client - > ps . saberActive & &
ent - > client - > ps . saberEntityNum ! = ENTITYNUM_NONE & &
ent - > client - > ps . saberEntityState ! = SES_RETURNING )
{ //saber was blocked, return it
inFlightSaberBlocked = qtrue ;
}
//FIXME: based on strength, position and angle of attack & defense, decide if:
// defender and attacker lock sabers
// *defender's parry should hold and attack bounces (or deflects, based on angle of sabers)
// *defender's parry is somewhat broken and both bounce (or deflect)
// *defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead)
// defender is knocked down and attack goes through
//Check deflections and broken parries
if ( hitOwner & & hitOwner - > health > 0 & & ent - > health > 0 //both are alive
& & ! inFlightSaberBlocked & & hitOwner - > client & & ! hitOwner - > client - > ps . saberInFlight & & ! ent - > client - > ps . saberInFlight //both have sabers in-hand
& & ent - > client - > ps . saberBlocked ! = BLOCKED_PARRY_BROKEN
& & ent - > client - > ps . saberLockTime < level . time
& & hitOwner - > client - > ps . saberLockTime < level . time )
{ //2 in-hand sabers hit
//FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown...
if ( baseDamage )
{ //there is damage involved, not just effects
qboolean entAttacking = qfalse ;
qboolean hitOwnerAttacking = qfalse ;
qboolean entDefending = qfalse ;
qboolean hitOwnerDefending = qfalse ;
if ( PM_SaberInAttack ( ent - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim ) )
{
entAttacking = qtrue ;
}
else if ( entPowerLevel > FORCE_LEVEL_2 )
{
if ( PM_SaberInAttack ( ent - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim ) | | PM_SaberInTransitionAny ( ent - > client - > ps . saberMove ) )
{
entAttacking = qtrue ;
}
}
if ( PM_SaberInParry ( ent - > client - > ps . saberMove )
| | ent - > client - > ps . saberMove = = LS_READY )
{
entDefending = qtrue ;
}
if ( PM_SaberInAttack ( hitOwner - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( hitOwner - > client - > ps . torsoAnim ) )
{
hitOwnerAttacking = qtrue ;
}
else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
{
if ( PM_SaberInAttack ( hitOwner - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( hitOwner - > client - > ps . torsoAnim ) | | PM_SaberInTransitionAny ( hitOwner - > client - > ps . saberMove ) )
{
hitOwnerAttacking = qtrue ;
}
}
if ( PM_SaberInParry ( hitOwner - > client - > ps . saberMove )
| | hitOwner - > client - > ps . saberMove = = LS_READY )
{
hitOwnerDefending = qtrue ;
}
if ( entAttacking
& & hitOwnerAttacking
& & ( entPowerLevel = = hitOwnerPowerLevel
| | ( entPowerLevel > FORCE_LEVEL_2 & & hitOwnerPowerLevel > FORCE_LEVEL_2 )
| | ( entPowerLevel < FORCE_LEVEL_3 & & hitOwnerPowerLevel < FORCE_LEVEL_3 & & hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] > FORCE_LEVEL_2 & & Q_irand ( 0 , 2 ) )
| | ( entPowerLevel < FORCE_LEVEL_2 & & hitOwnerPowerLevel < FORCE_LEVEL_3 & & hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] > FORCE_LEVEL_1 & & Q_irand ( 0 , 1 ) )
| | ( hitOwnerPowerLevel < FORCE_LEVEL_3 & & entPowerLevel < FORCE_LEVEL_3 & & ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] > FORCE_LEVEL_2 & & ! Q_irand ( 0 , 1 ) )
| | ( hitOwnerPowerLevel < FORCE_LEVEL_2 & & entPowerLevel < FORCE_LEVEL_3 & & ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] > FORCE_LEVEL_1 & & ! Q_irand ( 0 , 1 ) ) )
& & WP_SabersCheckLock ( ent , hitOwner ) )
{
collisionResolved = qtrue ;
}
else if ( hitOwnerAttacking
& & entDefending
& & ! Q_irand ( 0 , 2 )
& & ( ent - > client - > ps . saberMove ! = LS_READY | | ( hitOwnerPowerLevel - ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] ) < Q_irand ( - 6 , 0 ) )
& & ( ( hitOwnerPowerLevel < FORCE_LEVEL_3 & & ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_2 ) | |
( hitOwnerPowerLevel < FORCE_LEVEL_2 & & ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_1 ) | |
( hitOwnerPowerLevel < FORCE_LEVEL_3 & & ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_0 & & ! Q_irand ( 0 , ( hitOwnerPowerLevel - ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] + 1 ) * 2 ) ) )
& & WP_SabersCheckLock ( hitOwner , ent ) )
{
collisionResolved = qtrue ;
}
else if ( entAttacking & & hitOwnerDefending )
{ //I'm attacking hit, they're parrying
qboolean activeDefense = ( qboolean ) (
hitOwner - > s . number | |
g_saberAutoBlocking - > integer | |
hitOwner - > client - > ps . saberBlockingTime > level . time ) ;
if ( ! Q_irand ( 0 , 2 )
& & activeDefense
& & ( hitOwner - > client - > ps . saberMove ! = LS_READY | | ( entPowerLevel - hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] ) < Q_irand ( - 6 , 0 ) )
& & ( ( entPowerLevel < FORCE_LEVEL_3 & & hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_2 )
| | ( entPowerLevel < FORCE_LEVEL_2 & & hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_1 )
| | ( entPowerLevel < FORCE_LEVEL_3 & & hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_0 & & ! Q_irand ( 0 , ( entPowerLevel - hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] + 1 ) * 2 ) ) )
& & WP_SabersCheckLock ( ent , hitOwner ) )
{
collisionResolved = qtrue ;
}
else if ( saberHitFraction < 1.0f )
{ //an actual collision
if ( entPowerLevel < FORCE_LEVEL_3 & & activeDefense )
{ //strong attacks cannot be deflected
//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
deflected = WP_GetSaberDeflectionAngle ( ent , hitOwner ) ;
//just so Jedi knows that he was blocked
ent - > client - > ps . saberEventFlags | = SEF_BLOCKED ;
}
//base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
if ( entPowerLevel < FORCE_LEVEL_3
//&& ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_3//if you have high saber offense, you cannot have your attack knocked away, regardless of what style you're using?
& & hitOwner - > client - > ps . saberAnimLevel ! = FORCE_LEVEL_5
& & activeDefense
& & ( hitOwnerPowerLevel > FORCE_LEVEL_2 | | ( hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_2 & & Q_irand ( 0 , hitOwner - > client - > ps . saberAnimLevel ) ) ) )
{ //knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion)
//make me parry
WP_SaberParry ( hitOwner , ent ) ;
//turn the parry into a knockaway
hitOwner - > client - > ps . saberBounceMove = PM_KnockawayForParry ( hitOwner - > client - > ps . saberBlocked ) ;
//make them go into a broken parry
ent - > client - > ps . saberBounceMove = PM_BrokenParryForAttack ( ent - > client - > ps . saberMove ) ;
ent - > client - > ps . saberBlocked = BLOCKED_PARRY_BROKEN ;
if ( ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] < FORCE_LEVEL_2
& & ( ent - > s . number | | g_saberRealisticCombat - > integer ) ) //&& !Q_irand( 0, 3 ) )
{ //knocked the saber right out of his hand! (never happens to player)
//Get a good velocity to send the saber in based on my parry move
vec3_t throwDir ;
if ( ! PM_VelocityForBlockedMove ( & hitOwner - > client - > ps , throwDir ) )
{
PM_VelocityForSaberMove ( & ent - > client - > ps , throwDir ) ;
}
WP_SaberLose ( ent , throwDir ) ;
}
//just so Jedi knows that he was blocked
ent - > client - > ps . saberEventFlags | = SEF_BLOCKED ;
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( S_COLOR_RED " %s knockaway %s's attack, new move = %s, anim = %s \n " , hitOwner - > NPC_type , ent - > NPC_type , saberMoveData [ ent - > client - > ps . saberBounceMove ] . name , animTable [ saberMoveData [ ent - > client - > ps . saberBounceMove ] . animToUse ] . name ) ;
}
# endif
}
else if ( entPowerLevel > FORCE_LEVEL_2
| | ! activeDefense
| | ( ! deflected & & Q_irand ( 0 , PM_PowerLevelForSaberAnim ( & ent - > client - > ps ) - hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] /*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) )
{ //broke their parry altogether
if ( entPowerLevel > FORCE_LEVEL_2 | | Q_irand ( 0 , ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] - hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] ) )
{ //chance of continuing with the attack (not bouncing back)
ent - > client - > ps . saberEventFlags & = ~ SEF_BLOCKED ;
ent - > client - > ps . saberBounceMove = LS_NONE ;
brokenParry = qtrue ;
}
//do some time-consuming saber-knocked-aside broken parry anim
hitOwner - > client - > ps . saberBlocked = BLOCKED_PARRY_BROKEN ;
hitOwner - > client - > ps . saberBounceMove = LS_NONE ;
if ( hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] < FORCE_LEVEL_2
& & ( ent - > s . number | | g_saberRealisticCombat - > integer )
& & ! Q_irand ( 0 , 2 ) )
{ //knocked the saber right out of his hand!
//get the right velocity for my attack direction
vec3_t throwDir ;
PM_VelocityForSaberMove ( & ent - > client - > ps , throwDir ) ;
WP_SaberLose ( hitOwner , throwDir ) ;
if ( ( ent - > client - > ps . saberAnimLevel = = FORCE_LEVEL_3 & & ! Q_irand ( 0 , 3 ) )
| | ( ent - > client - > ps . saberAnimLevel = = FORCE_LEVEL_4 & & ! Q_irand ( 0 , 1 ) ) )
{ // a strong attack
if ( WP_BrokenParryKnockDown ( hitOwner ) )
{
hitOwner - > client - > ps . saberBlocked = BLOCKED_NONE ;
hitOwner - > client - > ps . saberBounceMove = LS_NONE ;
}
}
}
else
{
if ( ( ent - > client - > ps . saberAnimLevel = = FORCE_LEVEL_3 & & ! Q_irand ( 0 , 5 ) )
| | ( ent - > client - > ps . saberAnimLevel = = FORCE_LEVEL_4 & & ! Q_irand ( 0 , 3 ) ) )
{ // a strong attack
if ( WP_BrokenParryKnockDown ( hitOwner ) )
{
hitOwner - > client - > ps . saberBlocked = BLOCKED_NONE ;
hitOwner - > client - > ps . saberBounceMove = LS_NONE ;
}
}
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
if ( ent - > client - > ps . saberEventFlags & SEF_BLOCKED )
{
gi . Printf ( S_COLOR_RED " %s parry broken (bounce/deflect)! \n " , hitOwner - > targetname ) ;
}
else
{
gi . Printf ( S_COLOR_RED " %s parry broken (follow-through)! \n " , hitOwner - > targetname ) ;
}
}
# endif
}
else
{
WP_SaberParry ( hitOwner , ent ) ;
if ( PM_SaberInBounce ( ent - > client - > ps . saberMove ) //FIXME: saberMove not set until pmove!
& & activeDefense
& & hitOwner - > client - > ps . saberAnimLevel ! = FORCE_LEVEL_1 & & hitOwner - > client - > ps . saberAnimLevel ! = FORCE_LEVEL_5
& & hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] > FORCE_LEVEL_2 )
{ //attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks
//turn the parry into a knockaway
hitOwner - > client - > ps . saberBounceMove = PM_KnockawayForParry ( hitOwner - > client - > ps . saberBlocked ) ;
}
else if ( ( ent - > client - > ps . saberAnimLevel = = FORCE_LEVEL_3 & & ! Q_irand ( 0 , 6 ) )
| | ( ent - > client - > ps . saberAnimLevel = = FORCE_LEVEL_4 & & ! Q_irand ( 0 , 3 ) ) )
{ // a strong attack can sometimes do a knockdown
//HMM... maybe only if they're moving backwards?
if ( WP_BrokenParryKnockDown ( hitOwner ) )
{
hitOwner - > client - > ps . saberBlocked = BLOCKED_NONE ;
hitOwner - > client - > ps . saberBounceMove = LS_NONE ;
}
}
}
collisionResolved = qtrue ;
}
}
/*
else if ( entDefending & & hitOwnerAttacking )
{ //I'm parrying, they're attacking
if ( hitOwnerPowerLevel < FORCE_LEVEL_3 )
{ //strong attacks cannot be deflected
//based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
deflected = WP_GetSaberDeflectionAngle ( hitOwner , ent ) ;
//just so Jedi knows that he was blocked
hitOwner - > client - > ps . saberEventFlags | = SEF_BLOCKED ;
}
//FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
if ( hitOwnerPowerLevel > FORCE_LEVEL_2 | | ( ! deflected & & Q_irand ( 0 , PM_PowerLevelForSaberAnim ( & hitOwner - > client - > ps ) - ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] ) > 0 ) )
{ //broke my parry altogether
if ( hitOwnerPowerLevel > FORCE_LEVEL_2 | | Q_irand ( 0 , hitOwner - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] - ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] ) )
{ //chance of continuing with the attack (not bouncing back)
hitOwner - > client - > ps . saberEventFlags & = ~ SEF_BLOCKED ;
hitOwner - > client - > ps . saberBounceMove = LS_NONE ;
}
//do some time-consuming saber-knocked-aside broken parry anim
ent - > client - > ps . saberBlocked = BLOCKED_PARRY_BROKEN ;
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
if ( hitOwner - > client - > ps . saberEventFlags & SEF_BLOCKED )
{
gi . Printf ( S_COLOR_RED " %s parry broken (bounce/deflect)! \n " , ent - > targetname ) ;
}
else
{
gi . Printf ( S_COLOR_RED " %s parry broken (follow-through)! \n " , ent - > targetname ) ;
}
}
# endif
}
else
{
WP_SaberParry ( ent , hitOwner ) ;
}
collisionResolved = qtrue ;
}
*/
else
{ //some other kind of in-hand saber collision
}
}
}
else
{ //some kind of in-flight collision
}
if ( saberHitFraction < 1.0f )
{
if ( ! collisionResolved & & baseDamage )
{ //some other kind of in-hand saber collision
//handle my reaction
if ( ! ent - > client - > ps . saberInFlight
& & ent - > client - > ps . saberLockTime < level . time )
{ //my saber is in hand
if ( ent - > client - > ps . saberBlocked ! = BLOCKED_PARRY_BROKEN )
{
if ( PM_SaberInAttack ( ent - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( ent - > client - > ps . torsoAnim ) | |
( entPowerLevel > FORCE_LEVEL_2 & & ! PM_SaberInIdle ( ent - > client - > ps . saberMove ) & & ! PM_SaberInParry ( ent - > client - > ps . saberMove ) & & ! PM_SaberInReflect ( ent - > client - > ps . saberMove ) ) )
{ //in the middle of attacking
if ( entPowerLevel < FORCE_LEVEL_3 & & hitOwner - > health > 0 )
{ //don't deflect/bounce in strong attack or when enemy is dead
WP_GetSaberDeflectionAngle ( ent , hitOwner ) ;
ent - > client - > ps . saberEventFlags | = SEF_BLOCKED ;
//since it was blocked/deflected, take away any damage done
//FIXME: what if the damage was done before the parry?
WP_SaberClearDamageForEntNum ( hitOwner - > s . number ) ;
}
}
else
{ //saber collided when not attacking, parry it
//since it was blocked/deflected, take away any damage done
//FIXME: what if the damage was done before the parry?
WP_SaberClearDamageForEntNum ( hitOwner - > s . number ) ;
/*
if ( ent - > s . number | | g_saberAutoBlocking - > integer | | ent - > client - > ps . saberBlockingTime > level . time )
{ //either an NPC or a player who has blocking
if ( ! PM_SaberInTransitionAny ( ent - > client - > ps . saberMove ) & & ! PM_SaberInBounce ( ent - > client - > ps . saberMove ) )
{ //I'm not attacking, in transition or in a bounce, so play a parry
//just so Jedi knows that he parried something
WP_SaberBlockNonRandom ( ent , saberHitLocation , qfalse ) ;
}
ent - > client - > ps . saberEventFlags | = SEF_PARRIED ;
}
*/
}
}
else
{
//since it was blocked/deflected, take away any damage done
//FIXME: what if the damage was done before the parry?
WP_SaberClearDamageForEntNum ( hitOwner - > s . number ) ;
}
}
else
{ //nothing happens to *me* when my inFlight saber hits something
}
//handle their reaction
if ( hitOwner
& & hitOwner - > health > 0
& & hitOwner - > client
& & ! hitOwner - > client - > ps . saberInFlight
& & hitOwner - > client - > ps . saberLockTime < level . time )
{ //their saber is in hand
if ( PM_SaberInAttack ( hitOwner - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( hitOwner - > client - > ps . torsoAnim ) | |
( hitOwner - > client - > ps . saberAnimLevel > FORCE_LEVEL_2 & & ! PM_SaberInIdle ( hitOwner - > client - > ps . saberMove ) & & ! PM_SaberInParry ( hitOwner - > client - > ps . saberMove ) & & ! PM_SaberInReflect ( hitOwner - > client - > ps . saberMove ) ) )
{ //in the middle of attacking
/*
if ( hitOwner - > client - > ps . saberAnimLevel < FORCE_LEVEL_3 )
{ //don't deflect/bounce in strong attack
WP_GetSaberDeflectionAngle ( hitOwner , ent ) ;
hitOwner - > client - > ps . saberEventFlags | = SEF_BLOCKED ;
}
*/
}
else
{ //saber collided when not attacking, parry it
if ( ! PM_SaberInBrokenParry ( hitOwner - > client - > ps . saberMove ) )
{ //not currently in a broken parry
if ( ! WP_SaberParry ( hitOwner , ent ) )
{ //FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim?
//hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
}
}
}
}
else
{ //nothing happens to *hitOwner* when their inFlight saber hits something
}
}
//collision must have been handled by now
//Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim
if ( ent - > client - > ps . saberEventFlags & SEF_BLOCKED )
{
if ( ent - > client - > ps . saberBlocked ! = BLOCKED_PARRY_BROKEN )
{
ent - > client - > ps . saberBlocked = BLOCKED_ATK_BOUNCE ;
}
}
/*
if ( hitOwner & & hitOwner - > client - > ps . saberEventFlags & SEF_BLOCKED )
{
hitOwner - > client - > ps . saberBlocked = BLOCKED_ATK_BOUNCE ;
}
*/
}
if ( saberHitFraction < 1.0f | | collisionResolved )
{ //either actually hit or locked
if ( ent - > client - > ps . saberLockTime < level . time )
{
if ( inFlightSaberBlocked )
{ //FIXME: never hear this sound
G_Sound ( & g_entities [ ent - > client - > ps . saberEntityNum ] , G_SoundIndex ( va ( " sound/weapons/saber/saberbounce%d.wav " , Q_irand ( 1 , 3 ) ) ) ) ;
}
else
{
if ( deflected )
{
G_Sound ( ent , G_SoundIndex ( va ( " sound/weapons/saber/saberbounce%d.wav " , Q_irand ( 1 , 3 ) ) ) ) ;
}
else
{
G_Sound ( ent , G_SoundIndex ( va ( " sound/weapons/saber/saberblock%d.wav " , Q_irand ( 1 , 9 ) ) ) ) ;
}
}
G_PlayEffect ( " saber_block " , saberHitLocation , saberHitNormal ) ;
}
// Set the little screen flash - only when an attack is blocked
g_saberFlashTime = level . time - 50 ;
VectorCopy ( saberHitLocation , g_saberFlashPos ) ;
}
if ( saberHitFraction < 1.0f )
{
if ( inFlightSaberBlocked )
{ //we threw a saber and it was blocked, do any effects, etc.
int knockAway = 5 ;
if ( hitEnt
& & hitOwner
& & hitOwner - > client
& & ( PM_SaberInAttack ( hitOwner - > client - > ps . saberMove ) | | PM_SaberInSpecialAttack ( hitOwner - > client - > ps . torsoAnim ) | | PM_SpinningSaberAnim ( hitOwner - > client - > ps . torsoAnim ) ) )
{ //if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away
if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
{ //string attacks almost always knock it aside!
knockAway = 1 ;
}
else
{ //33% chance
knockAway = 2 ;
}
}
if ( ! Q_irand ( 0 , knockAway ) | | //random
( hitOwner & & hitOwner - > client & &
( hitOwner - > client - > NPC_class = = CLASS_DESANN | | hitOwner - > client - > NPC_class = = CLASS_TAVION | | hitOwner - > client - > NPC_class = = CLASS_LUKE )
) //or if blocked by a Boss character FIXME: or base on defense level?
) //FIXME: player should not auto-block a flying saber, let him override the parry with an attack to knock the saber from the air, rather than this random chance
{ //knock it aside and turn it off
G_PlayEffect ( " saber_cut " , saberHitLocation , saberHitNormal ) ;
if ( hitEnt )
{
vec3_t newDir ;
VectorSubtract ( g_entities [ ent - > client - > ps . saberEntityNum ] . currentOrigin , hitEnt - > currentOrigin , newDir ) ;
VectorNormalize ( newDir ) ;
G_ReflectMissile ( ent , & g_entities [ ent - > client - > ps . saberEntityNum ] , newDir ) ;
}
Jedi_PlayDeflectSound ( hitOwner ) ;
WP_SaberDrop ( ent , & g_entities [ ent - > client - > ps . saberEntityNum ] ) ;
}
else
{
if ( ! Q_irand ( 0 , 2 ) & & hitEnt )
{
vec3_t newDir ;
VectorSubtract ( g_entities [ ent - > client - > ps . saberEntityNum ] . currentOrigin , hitEnt - > currentOrigin , newDir ) ;
VectorNormalize ( newDir ) ;
G_ReflectMissile ( ent , & g_entities [ ent - > client - > ps . saberEntityNum ] , newDir ) ;
}
WP_SaberReturn ( ent , & g_entities [ ent - > client - > ps . saberEntityNum ] ) ;
}
}
}
}
if ( ent - > client - > ps . saberLockTime > level . time
& & ent - > s . number < ent - > client - > ps . saberLockEnemy
& & ! Q_irand ( 0 , 3 ) )
{ //need to make some kind of effect
vec3_t hitNorm = { 0 , 0 , 1 } ;
if ( WP_SabersIntersection ( ent , & g_entities [ ent - > client - > ps . saberLockEnemy ] , g_saberFlashPos ) )
{
if ( Q_irand ( 0 , 10 ) )
{
G_PlayEffect ( " saber_block " , g_saberFlashPos , hitNorm ) ;
}
else
{
g_saberFlashTime = level . time - 50 ;
G_PlayEffect ( " saber_cut " , g_saberFlashPos , hitNorm ) ;
}
G_Sound ( ent , G_SoundIndex ( va ( " sound/weapons/saber/saberblock%d.wav " , Q_irand ( 1 , 9 ) ) ) ) ;
}
}
if ( WP_SaberApplyDamage ( ent , baseDamage , baseDFlags , brokenParry ) )
{ //actually did damage to something
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
gi . Printf ( " base damage was %4.2f \n " , baseDamage ) ;
}
# endif
G_Sound ( ent , G_SoundIndex ( va ( " sound/weapons/saber/saberhit%d.wav " , Q_irand ( 1 , 3 ) ) ) ) ;
}
if ( hit_wall )
{
//just so Jedi knows that he hit a wall
ent - > client - > ps . saberEventFlags | = SEF_HITWALL ;
if ( ent - > s . number = = 0 )
{
AddSoundEvent ( ent , ent - > currentOrigin , 128 , AEL_DISCOVERED ) ;
AddSightEvent ( ent , ent - > currentOrigin , 256 , AEL_DISCOVERED , 50 ) ;
}
}
}
//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================
//SABER THROWING============================================================================
/*
= = = = = = = = = = = = = = = =
WP_SaberImpact
= = = = = = = = = = = = = = = =
*/
void WP_SaberImpact ( gentity_t * owner , gentity_t * saber , trace_t * trace )
{
gentity_t * other ;
other = & g_entities [ trace - > entityNum ] ;
if ( other - > takedamage & & ( other - > svFlags & SVF_BBRUSH ) )
{ //a breakable brush? break it!
vec3_t dir ;
VectorCopy ( saber - > s . pos . trDelta , dir ) ;
VectorNormalize ( dir ) ;
int dmg = other - > health * 2 ;
if ( other - > health > 50 & & dmg > 20 & & ! ( other - > svFlags & SVF_GLASS_BRUSH ) )
{
dmg = 20 ;
}
G_Damage ( other , owner , saber , dir , trace - > endpos , dmg , 0 , MOD_SABER ) ;
G_PlayEffect ( " saber_cut " , trace - > endpos , dir ) ;
if ( owner - > s . number = = 0 )
{
AddSoundEvent ( owner , trace - > endpos , 256 , AEL_DISCOVERED ) ;
AddSightEvent ( owner , trace - > endpos , 512 , AEL_DISCOVERED , 50 ) ;
}
return ;
}
if ( saber - > s . pos . trType = = TR_LINEAR )
{
//hit a wall? send it back
WP_SaberReturn ( saber - > owner , saber ) ;
}
if ( other & & ! other - > client & & ( other - > contents & CONTENTS_LIGHTSABER ) ) //&& other->s.weapon == WP_SABER )
{ //2 in-flight sabers collided!
//Big flash
//FIXME: bigger effect/sound?
//FIXME: STILL DOESNT WORK!!!
G_Sound ( saber , G_SoundIndex ( va ( " sound/weapons/saber/saberblock%d.wav " , Q_irand ( 1 , 9 ) ) ) ) ;
G_PlayEffect ( " saber_block " , trace - > endpos ) ;
g_saberFlashTime = level . time - 50 ;
VectorCopy ( trace - > endpos , g_saberFlashPos ) ;
}
if ( owner & & owner - > s . number = = 0 & & owner - > client )
{
//Add the event
if ( owner - > client - > ps . saberLength > 0 )
{ //saber is on, very suspicious
AddSoundEvent ( owner , saber - > currentOrigin , 128 , AEL_DISCOVERED ) ;
AddSightEvent ( owner , saber - > currentOrigin , 256 , AEL_DISCOVERED , 50 ) ;
}
else
{ //saber is off, not as suspicious
AddSoundEvent ( owner , saber - > currentOrigin , 128 , AEL_SUSPICIOUS ) ;
AddSightEvent ( owner , saber - > currentOrigin , 256 , AEL_SUSPICIOUS ) ;
}
}
// check for bounce
if ( ! other - > takedamage & & ( saber - > s . eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
{
// Check to see if there is a bounce count
if ( saber - > bounceCount ) {
// decrement number of bounces and then see if it should be done bouncing
if ( - - saber - > bounceCount < = 0 ) {
// He (or she) will bounce no more (after this current bounce, that is).
saber - > s . eFlags & = ~ ( EF_BOUNCE | EF_BOUNCE_HALF ) ;
if ( saber - > s . pos . trType = = TR_LINEAR & & owner & & owner - > client & & owner - > client - > ps . saberEntityState = = SES_RETURNING )
{
WP_SaberDrop ( saber - > owner , saber ) ;
}
return ;
}
else
{ //bounced and still have bounces left
if ( saber - > s . pos . trType = = TR_LINEAR & & owner & & owner - > client & & owner - > client - > ps . saberEntityState = = SES_RETURNING )
{ //under telekinetic control
if ( ! gi . inPVS ( saber - > currentOrigin , owner - > client - > renderInfo . handRPoint ) )
{ //not in the PVS of my master
saber - > bounceCount - = 25 ;
}
}
}
}
if ( saber - > s . pos . trType = = TR_LINEAR & & owner & & owner - > client & & owner - > client - > ps . saberEntityState = = SES_RETURNING )
{
//don't home for a few frames so we can get around this thing
trace_t bounceTr ;
vec3_t end ;
float owner_dist = Distance ( owner - > client - > renderInfo . handRPoint , saber - > currentOrigin ) ;
VectorMA ( saber - > currentOrigin , 10 , trace - > plane . normal , end ) ;
gi . trace ( & bounceTr , saber - > currentOrigin , saber - > mins , saber - > maxs , end , owner - > s . number , saber - > clipmask , G2_NOCOLLIDE , 0 ) ;
VectorCopy ( bounceTr . endpos , saber - > currentOrigin ) ;
if ( owner_dist > 0 )
{
if ( owner_dist > 50 )
{
owner - > client - > ps . saberEntityDist = owner_dist - 50 ;
}
else
{
owner - > client - > ps . saberEntityDist = 0 ;
}
}
return ;
}
G_BounceMissile ( saber , trace ) ;
if ( saber - > s . pos . trType = = TR_GRAVITY )
{ //bounced
//play a bounce sound
G_Sound ( saber , G_SoundIndex ( va ( " sound/weapons/saber/bounce%d.wav " , Q_irand ( 1 , 3 ) ) ) ) ;
//change rotation
VectorCopy ( saber - > currentAngles , saber - > s . apos . trBase ) ;
saber - > s . apos . trType = TR_LINEAR ;
saber - > s . apos . trTime = level . time ;
VectorSet ( saber - > s . apos . trDelta , Q_irand ( - 300 , 300 ) , Q_irand ( - 300 , 300 ) , Q_irand ( - 300 , 300 ) ) ;
}
//see if we stopped
else if ( saber - > s . pos . trType = = TR_STATIONARY )
{ //stopped
//play a bounce sound
G_Sound ( saber , G_SoundIndex ( va ( " sound/weapons/saber/bounce%d.wav " , Q_irand ( 1 , 3 ) ) ) ) ;
//stop rotation
VectorClear ( saber - > s . apos . trDelta ) ;
saber - > currentAngles [ 0 ] = SABER_PITCH_HACK ;
VectorCopy ( saber - > currentAngles , saber - > s . apos . trBase ) ;
//remember when it fell so it can return automagically
saber - > aimDebounceTime = level . time ;
}
}
else if ( other - > client & & other - > health > 0
& & ( other - > client - > NPC_class = = CLASS_DESANN | | other - > client - > NPC_class = = CLASS_TAVION | | other - > client - > NPC_class = = CLASS_LUKE | | ( other - > client - > NPC_class = = CLASS_GALAKMECH & & other - > client - > ps . powerups [ PW_GALAK_SHIELD ] > 0 ) ) )
{ //Luke, Desann and Tavion slap thrown sabers aside
WP_SaberDrop ( owner , saber ) ;
G_Sound ( saber , G_SoundIndex ( va ( " sound/weapons/saber/saberblock%d.wav " , Q_irand ( 1 , 9 ) ) ) ) ;
G_PlayEffect ( " saber_block " , trace - > endpos ) ;
g_saberFlashTime = level . time - 50 ;
VectorCopy ( trace - > endpos , g_saberFlashPos ) ;
//FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens
Jedi_PlayDeflectSound ( other ) ;
}
}
extern float G_PointDistFromLineSegment ( const vec3_t start , const vec3_t end , const vec3_t from ) ;
void WP_SaberInFlightReflectCheck ( gentity_t * self , usercmd_t * ucmd )
{
gentity_t * ent ;
gentity_t * entityList [ MAX_GENTITIES ] ;
gentity_t * missile_list [ MAX_GENTITIES ] ;
int numListedEntities ;
vec3_t mins , maxs ;
int i , e ;
int ent_count = 0 ;
int radius = 180 ;
vec3_t center , forward ;
vec3_t tip ;
vec3_t up = { 0 , 0 , 1 } ;
if ( self - > NPC & & ( self - > NPC - > scriptFlags & SCF_IGNORE_ALERTS ) )
{ //don't react to things flying at me...
return ;
}
//sanity checks: make sure we actually have a saberent
if ( self - > client - > ps . weapon ! = WP_SABER )
{
return ;
}
if ( ! self - > client - > ps . saberInFlight )
{
return ;
}
if ( ! self - > client - > ps . saberLength )
{
return ;
}
if ( self - > client - > ps . saberEntityNum = = ENTITYNUM_NONE )
{
return ;
}
gentity_t * saberent = & g_entities [ self - > client - > ps . saberEntityNum ] ;
if ( ! saberent )
{
return ;
}
//okay, enough damn sanity checks
VectorCopy ( saberent - > currentOrigin , center ) ;
for ( i = 0 ; i < 3 ; i + + )
{
mins [ i ] = center [ i ] - radius ;
maxs [ i ] = center [ i ] + radius ;
}
numListedEntities = gi . EntitiesInBox ( mins , maxs , entityList , MAX_GENTITIES ) ;
//FIXME: check visibility?
for ( e = 0 ; e < numListedEntities ; e + + )
{
ent = entityList [ e ] ;
if ( ent = = self )
continue ;
if ( ent - > owner = = self )
continue ;
if ( ! ( ent - > inuse ) )
continue ;
if ( ent - > s . eType ! = ET_MISSILE )
{
if ( ent - > client | | ent - > s . weapon ! = WP_SABER )
{ //FIXME: wake up bad guys?
continue ;
}
if ( ent - > s . eFlags & EF_NODRAW )
{
continue ;
}
if ( Q_stricmp ( " lightsaber " , ent - > classname ) ! = 0 )
{ //not a lightsaber
continue ;
}
}
else
{ //FIXME: make exploding missiles explode?
if ( ent - > s . pos . trType = = TR_STATIONARY )
{ //nothing you can do with a stationary missile
continue ;
}
if ( ent - > splashDamage | | ent - > splashRadius )
{ //can't deflect exploding missiles
if ( DistanceSquared ( ent - > currentOrigin , center ) < 256 ) //16 squared
{
G_MissileImpacted ( ent , saberent , ent - > currentOrigin , up ) ;
}
continue ;
}
}
//don't deflect it if it's not within 16 units of the blade
VectorMA ( self - > client - > renderInfo . muzzlePoint , self - > client - > ps . saberLength , self - > client - > renderInfo . muzzleDir , tip ) ;
if ( G_PointDistFromLineSegment ( self - > client - > renderInfo . muzzlePoint , tip , ent - > currentOrigin ) > 32 )
{
continue ;
}
// ok, we are within the radius, add us to the incoming list
missile_list [ ent_count ] = ent ;
ent_count + + ;
}
if ( ent_count )
{
vec3_t fx_dir ;
// we are done, do we have any to deflect?
if ( ent_count )
{
for ( int x = 0 ; x < ent_count ; x + + )
{
if ( missile_list [ x ] - > s . weapon = = WP_SABER )
{ //just send it back
if ( missile_list [ x ] - > owner & & missile_list [ x ] - > owner - > client & & missile_list [ x ] - > owner - > client - > ps . saberActive & & missile_list [ x ] - > s . pos . trType = = TR_LINEAR & & missile_list [ x ] - > owner - > client - > ps . saberEntityState ! = SES_RETURNING )
{ //it's on and being controlled
//FIXME: prevent it from damaging me?
WP_SaberReturn ( missile_list [ x ] - > owner , missile_list [ x ] ) ;
VectorNormalize2 ( missile_list [ x ] - > s . pos . trDelta , fx_dir ) ;
G_PlayEffect ( " saber_block " , missile_list [ x ] - > currentOrigin , fx_dir ) ;
if ( missile_list [ x ] - > owner - > client - > ps . saberInFlight & & self - > client - > ps . saberInFlight )
{
G_Sound ( missile_list [ x ] , G_SoundIndex ( va ( " sound/weapons/saber/saberblock%d.wav " , Q_irand ( 1 , 9 ) ) ) ) ;
g_saberFlashTime = level . time - 50 ;
gentity_t * saber = & g_entities [ self - > client - > ps . saberEntityNum ] ;
vec3_t org ;
VectorSubtract ( missile_list [ x ] - > currentOrigin , saber - > currentOrigin , org ) ;
VectorMA ( saber - > currentOrigin , 0.5 , org , org ) ;
VectorCopy ( org , g_saberFlashPos ) ;
}
}
}
else
{ //bounce it
if ( self - > client & & ! self - > s . number )
{
self - > client - > sess . missionStats . saberBlocksCnt + + ;
}
G_ReflectMissile ( self , missile_list [ x ] , forward ) ;
//do an effect
VectorNormalize2 ( missile_list [ x ] - > s . pos . trDelta , fx_dir ) ;
G_PlayEffect ( " blaster/deflect " , missile_list [ x ] - > currentOrigin , fx_dir ) ;
}
}
}
}
}
qboolean WP_SaberValidateEnemy ( gentity_t * self , gentity_t * enemy )
{
if ( ! enemy )
{
return qfalse ;
}
if ( ! enemy | | enemy = = self | | ! enemy - > inuse | | ! enemy - > client )
{ //not valid
return qfalse ;
}
if ( enemy - > health < = 0 )
{ //corpse
return qfalse ;
}
if ( DistanceSquared ( self - > client - > renderInfo . handRPoint , enemy - > currentOrigin ) > saberThrowDistSquared [ self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] ] )
{ //too far
return qfalse ;
}
if ( ( ! InFront ( enemy - > currentOrigin , self - > currentOrigin , self - > client - > ps . viewangles , 0.0f ) | | ! G_ClearLOS ( self , self - > client - > renderInfo . eyePoint , enemy ) )
& & ( DistanceHorizontalSquared ( enemy - > currentOrigin , self - > currentOrigin ) > 65536 | | fabs ( enemy - > currentOrigin [ 2 ] - self - > currentOrigin [ 2 ] ) > 384 ) )
{ //(not in front or not clear LOS) & greater than 256 away
return qfalse ;
}
if ( enemy - > client - > playerTeam = = self - > client - > playerTeam )
{ //on same team
return qfalse ;
}
//LOS?
return qtrue ;
}
float WP_SaberRateEnemy ( gentity_t * enemy , vec3_t center , vec3_t forward , float radius )
{
float rating ;
vec3_t dir ;
VectorSubtract ( enemy - > currentOrigin , center , dir ) ;
rating = ( 1.0f - ( VectorNormalize ( dir ) / radius ) ) ;
rating * = DotProduct ( forward , dir ) ;
return rating ;
}
gentity_t * WP_SaberFindEnemy ( gentity_t * self , gentity_t * saber )
{
//FIXME: should be a more intelligent way of doing this, like auto aim?
//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
gentity_t * ent , * bestEnt = NULL ;
gentity_t * entityList [ MAX_GENTITIES ] ;
int numListedEntities ;
vec3_t center , mins , maxs , fwdangles , forward ;
int i , e ;
float radius = 400 ;
float rating , bestRating = 0.0f ;
//FIXME: no need to do this in 1st person?
fwdangles [ 1 ] = self - > client - > ps . viewangles [ 1 ] ;
AngleVectors ( fwdangles , forward , NULL , NULL ) ;
VectorCopy ( saber - > currentOrigin , center ) ;
for ( i = 0 ; i < 3 ; i + + )
{
mins [ i ] = center [ i ] - radius ;
maxs [ i ] = center [ i ] + radius ;
}
if ( WP_SaberValidateEnemy ( self , self - > enemy ) )
{
bestEnt = self - > enemy ;
bestRating = WP_SaberRateEnemy ( bestEnt , center , forward , radius ) ;
}
numListedEntities = gi . EntitiesInBox ( mins , maxs , entityList , MAX_GENTITIES ) ;
if ( ! numListedEntities )
{ //should we clear the enemy?
return bestEnt ;
}
for ( e = 0 ; e < numListedEntities ; e + + )
{
ent = entityList [ e ] ;
if ( ent = = self | | ent = = saber | | ent = = bestEnt )
{
continue ;
}
if ( ! WP_SaberValidateEnemy ( self , ent ) )
{ //doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
continue ;
}
if ( ! gi . inPVS ( self - > currentOrigin , ent - > currentOrigin ) )
{ //not even potentially visible
continue ;
}
if ( ! G_ClearLOS ( self , self - > client - > renderInfo . eyePoint , ent ) )
{ //can't see him
continue ;
}
//rate him based on how close & how in front he is
rating = WP_SaberRateEnemy ( ent , center , forward , radius ) ;
if ( rating > bestRating )
{
bestEnt = ent ;
bestRating = rating ;
}
}
return bestEnt ;
}
void WP_RunSaber ( gentity_t * self , gentity_t * saber )
{
vec3_t origin , oldOrg ;
trace_t tr ;
VectorCopy ( saber - > currentOrigin , oldOrg ) ;
// get current position
EvaluateTrajectory ( & saber - > s . pos , level . time , origin ) ;
// get current angles
EvaluateTrajectory ( & saber - > s . apos , level . time , saber - > currentAngles ) ;
// trace a line from the previous position to the current position,
// ignoring interactions with the missile owner
int clipmask = saber - > clipmask ;
if ( ! self | | ! self - > client | | self - > client - > ps . saberLength < = 0 )
{ //don't keep hitting other sabers when turned off
clipmask & = ~ CONTENTS_LIGHTSABER ;
}
gi . trace ( & tr , saber - > currentOrigin , saber - > mins , saber - > maxs , origin ,
saber - > owner ? saber - > owner - > s . number : ENTITYNUM_NONE , clipmask , G2_NOCOLLIDE , 0 ) ;
VectorCopy ( tr . endpos , saber - > currentOrigin ) ;
if ( self - > client - > ps . saberActive )
{
if ( self - > client - > ps . saberInFlight | | ( self - > client - > ps . weaponTime & & ! Q_irand ( 0 , 100 ) ) )
{ //make enemies run from a lit saber in flight or from me when I'm attacking
if ( ! Q_irand ( 0 , 10 ) )
{ //not so often...
AddSightEvent ( self , saber - > currentOrigin , self - > client - > ps . saberLength * 3 , AEL_DANGER , 100 ) ;
}
}
}
if ( tr . startsolid )
{
tr . fraction = 0 ;
}
gi . linkentity ( saber ) ;
//touch push triggers?
if ( tr . fraction ! = 1 )
{
WP_SaberImpact ( self , saber , & tr ) ;
}
if ( saber - > s . pos . trType = = TR_LINEAR )
{ //home
//figure out where saber should be
vec3_t forward , saberHome , saberDest , fwdangles = { 0 } ;
2022-11-08 21:43:12 +00:00
if ( self - > client - > ps . clientNum = = 0 & & ! cg . renderingThirdPerson )
2022-10-03 21:19:00 +00:00
{
BG_CalculateVRWeaponPosition ( saberHome , fwdangles ) ;
2022-11-08 21:43:12 +00:00
//Ignore roll
fwdangles [ ROLL ] = 0 ;
2022-10-03 21:19:00 +00:00
}
else {
VectorCopy ( self - > client - > ps . viewangles , fwdangles ) ;
}
2022-09-18 15:37:21 +00:00
if ( self - > s . number )
{
fwdangles [ 0 ] - = 8 ;
}
else if ( cg . renderingThirdPerson )
{
fwdangles [ 0 ] - = 5 ;
}
2022-11-03 22:38:34 +00:00
//For now make FORCE_LEVEL_1 saber throw do the same as FORCE_LEVEL_2, otherwise it is impossible to use
if ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] > = FORCE_LEVEL_1 | |
self - > client - > ps . saberEntityState = = SES_RETURNING
2022-09-18 15:37:21 +00:00
| | VectorCompare ( saber - > s . pos . trDelta , vec3_origin ) )
{ //control if it's returning or just starting
2022-11-03 22:38:34 +00:00
float saberSpeed = self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = = FORCE_LEVEL_1 ? 300 : 500 ;
2022-09-18 15:37:21 +00:00
float dist ;
gentity_t * enemy = NULL ;
AngleVectors ( fwdangles , forward , NULL , NULL ) ;
2022-11-03 22:38:34 +00:00
//Always use right hand as saber home
VectorCopy ( self - > client - > renderInfo . handRPoint , saberHome ) ;
2022-09-18 15:37:21 +00:00
VectorMA ( saberHome , self - > client - > ps . saberEntityDist , forward , saberDest ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] > FORCE_LEVEL_2 & & self - > client - > ps . saberEntityState = = SES_LEAVING )
{ //max level
//pick an enemy
enemy = WP_SaberFindEnemy ( self , saber ) ;
if ( enemy )
{ //home in on enemy
float enemyDist = Distance ( self - > client - > renderInfo . handRPoint , enemy - > currentOrigin ) ;
VectorCopy ( enemy - > currentOrigin , saberDest ) ;
saberDest [ 2 ] + = enemy - > maxs [ 2 ] / 2.0f ; //FIXME: when in a knockdown anim, the saber float above them... do we care?
self - > client - > ps . saberEntityDist = enemyDist ;
}
}
//Make the saber head there
VectorSubtract ( saberDest , saber - > currentOrigin , saber - > s . pos . trDelta ) ;
dist = VectorNormalize ( saber - > s . pos . trDelta ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] > FORCE_LEVEL_2 & & self - > client - > ps . saberEntityState = = SES_LEAVING & & ! enemy )
{
if ( dist < 200 )
{
saberSpeed = 400 - ( dist * 2 ) ;
}
}
else if ( self - > client - > ps . saberEntityState = = SES_LEAVING & & dist < 50 )
{
saberSpeed = dist * 2 + 30 ;
if ( ( enemy & & dist > enemy - > maxs [ 0 ] ) | | ( ! enemy & & dist > 24 ) )
{ //auto-tracking an enemy and we can't hit him
if ( saberSpeed < 120 )
{ //clamp to a minimum speed
saberSpeed = 120 ;
}
}
}
/*
if ( self - > client - > ps . saberEntityState = = SES_RETURNING )
{ //FIXME: if returning, move faster?
saberSpeed = 800 ;
if ( dist < 200 )
{
saberSpeed - = 400 - ( dist * 2 ) ;
}
}
*/
VectorScale ( saber - > s . pos . trDelta , saberSpeed , saber - > s . pos . trDelta ) ;
//SnapVector( saber->s.pos.trDelta ); // save net bandwidth
VectorCopy ( saber - > currentOrigin , saber - > s . pos . trBase ) ;
saber - > s . pos . trTime = level . time ;
saber - > s . pos . trType = TR_LINEAR ;
}
else
{
VectorCopy ( saber - > currentOrigin , saber - > s . pos . trBase ) ;
saber - > s . pos . trTime = level . time ;
saber - > s . pos . trType = TR_LINEAR ;
}
//if it's heading back, point it's base at us
if ( self - > client - > ps . saberEntityState = = SES_RETURNING )
{
fwdangles [ 0 ] + = SABER_PITCH_HACK ;
VectorCopy ( fwdangles , saber - > s . apos . trBase ) ;
saber - > s . apos . trTime = level . time ;
saber - > s . apos . trType = TR_INTERPOLATE ;
VectorClear ( saber - > s . apos . trDelta ) ;
}
}
}
qboolean WP_SaberLaunch ( gentity_t * self , gentity_t * saber , qboolean thrown )
{ //FIXME: probably need a debounce time
vec3_t saberMins = { - 3.0f , - 3.0f , - 3.0f } ;
vec3_t saberMaxs = { 3.0f , 3.0f , 3.0f } ;
trace_t trace ;
if ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] > FORCE_LEVEL_2 )
{
if ( ! WP_ForcePowerUsable ( self , FP_SABERTHROW , 20 ) )
{
return qfalse ;
}
}
else
{
if ( ! WP_ForcePowerUsable ( self , FP_SABERTHROW , 0 ) )
{
return qfalse ;
}
}
if ( ! self - > s . number & & ( cg . zoomMode | | in_camera ) )
{ //can't saber throw when zoomed in or in cinematic
return qfalse ;
}
//make sure it won't start in solid
gi . trace ( & trace , self - > client - > renderInfo . handRPoint , saberMins , saberMaxs , self - > client - > renderInfo . handRPoint , saber - > s . number , MASK_SOLID , G2_NOCOLLIDE , 0 ) ;
if ( trace . startsolid | | trace . allsolid )
{
return qfalse ;
}
//make sure I'm not throwing it on the other side of a door or wall or whatever
gi . trace ( & trace , self - > currentOrigin , vec3_origin , vec3_origin , self - > client - > renderInfo . handRPoint , self - > s . number , MASK_SOLID , G2_NOCOLLIDE , 0 ) ;
if ( trace . startsolid | | trace . allsolid | | trace . fraction < 1.0f )
{
return qfalse ;
}
if ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] > FORCE_LEVEL_2 )
{ //at max skill, the cost increases as keep it out
WP_ForcePowerStart ( self , FP_SABERTHROW , 10 ) ;
}
else
{
WP_ForcePowerStart ( self , FP_SABERTHROW , 0 ) ;
}
//draw it
saber - > s . eFlags & = ~ EF_NODRAW ;
saber - > svFlags | = SVF_BROADCAST ;
saber - > svFlags & = ~ SVF_NOCLIENT ;
//place it
VectorCopy ( self - > client - > renderInfo . handRPoint , saber - > currentOrigin ) ; //muzzlePoint
VectorCopy ( saber - > currentOrigin , saber - > s . pos . trBase ) ;
saber - > s . pos . trTime = level . time ;
saber - > s . pos . trType = TR_LINEAR ;
VectorClear ( saber - > s . pos . trDelta ) ;
gi . linkentity ( saber ) ;
//spin it
VectorClear ( saber - > s . apos . trBase ) ;
saber - > s . apos . trTime = level . time ;
saber - > s . apos . trType = TR_LINEAR ;
if ( self - > health > 0 & & thrown )
{ //throwing it
saber - > s . apos . trBase [ 1 ] = self - > client - > ps . viewangles [ 1 ] ;
saber - > s . apos . trBase [ 0 ] = SABER_PITCH_HACK ;
}
else
{ //dropping it
vectoangles ( self - > client - > renderInfo . muzzleDir , saber - > s . apos . trBase ) ;
}
VectorClear ( saber - > s . apos . trDelta ) ;
switch ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] )
{ //FIXME: make a table?
default :
case FORCE_LEVEL_1 :
saber - > s . apos . trDelta [ 1 ] = 600 ;
break ;
case FORCE_LEVEL_2 :
saber - > s . apos . trDelta [ 1 ] = 800 ;
break ;
case FORCE_LEVEL_3 :
saber - > s . apos . trDelta [ 1 ] = 1200 ;
break ;
}
//Take it out of my hand
self - > client - > ps . saberInFlight = qtrue ;
self - > client - > ps . saberEntityState = SES_LEAVING ;
self - > client - > ps . saberEntityDist = saberThrowDist [ self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] ] ;
self - > client - > ps . saberThrowTime = level . time ;
//if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
{
self - > client - > ps . forcePowerDebounce [ FP_SABERTHROW ] = level . time + 1000 ; //so we can keep it out for a minimum amount of time
}
//if it's not active, turn it on
self - > client - > ps . saberActive = qtrue ;
//turn on the saber trail
self - > client - > saberTrail . inAction = qtrue ;
self - > client - > saberTrail . duration = 150 ;
//reset the mins
VectorCopy ( saberMins , saber - > mins ) ;
VectorCopy ( saberMaxs , saber - > maxs ) ;
saber - > contents = 0 ; //CONTENTS_LIGHTSABER;
saber - > clipmask = MASK_SOLID | CONTENTS_LIGHTSABER ;
// remove the ghoul2 sabre model on the player
if ( self - > weaponModel > = 0 )
{
gi . G2API_RemoveGhoul2Model ( self - > ghoul2 , self - > weaponModel ) ;
self - > weaponModel = - 1 ;
}
return qtrue ;
}
qboolean WP_SaberLose ( gentity_t * self , vec3_t throwDir )
{
if ( ! self | | ! self - > client | | self - > client - > ps . saberEntityNum < = 0 )
{ //WTF?!! We lost it already?
return qfalse ;
}
gentity_t * dropped = & g_entities [ self - > client - > ps . saberEntityNum ] ;
if ( ! self - > client - > ps . saberInFlight )
{ //not alreay in air
//make it so we can throw it
self - > client - > ps . forcePowersKnown | = ( 1 < < FP_SABERTHROW ) ;
self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_1 ;
//throw it
if ( ! WP_SaberLaunch ( self , dropped , qfalse ) )
{ //couldn't throw it
return qfalse ;
}
}
if ( self - > client - > ps . saberActive )
{ //on
//drop it instantly
WP_SaberDrop ( self , dropped ) ;
}
//optionally give it some thrown velocity
if ( throwDir & & ! VectorCompare ( throwDir , vec3_origin ) )
{
VectorCopy ( throwDir , dropped - > s . pos . trDelta ) ;
}
//don't pull it back on the next frame
if ( self - > NPC )
{
self - > NPC - > last_ucmd . buttons & = ~ BUTTON_ATTACK ;
}
return qtrue ;
}
void WP_SaberCatch ( gentity_t * self , gentity_t * saber , qboolean switchToSaber )
{ //FIXME: probably need a debounce time
if ( self - > health > 0 & & ! PM_SaberInBrokenParry ( self - > client - > ps . saberMove ) & & self - > client - > ps . saberBlocked ! = BLOCKED_PARRY_BROKEN )
{
//don't draw it
saber - > s . eFlags | = EF_NODRAW ;
saber - > svFlags & = SVF_BROADCAST ;
saber - > svFlags | = SVF_NOCLIENT ;
//take off any gravity stuff if we'd dropped it
saber - > s . pos . trType = TR_LINEAR ;
saber - > s . eFlags & = ~ EF_BOUNCE_HALF ;
//Put it in my hand
self - > client - > ps . saberInFlight = qfalse ;
self - > client - > ps . saberEntityState = SES_LEAVING ;
//turn off the saber trail
self - > client - > saberTrail . inAction = qfalse ;
self - > client - > saberTrail . duration = 75 ;
//reset its contents/clipmask
saber - > contents = CONTENTS_LIGHTSABER ; // | CONTENTS_SHOTCLIP;
saber - > clipmask = MASK_SHOT | CONTENTS_LIGHTSABER ;
//play catch sound
G_Sound ( saber , G_SoundIndex ( " sound/weapons/saber/saber_catch.wav " ) ) ;
//FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead...
//if it's not our current weapon, make it our current weapon
if ( self - > client - > ps . weapon = = WP_SABER )
{
G_CreateG2AttachedWeaponModel ( self , self - > client - > ps . saberModel ) ;
}
if ( switchToSaber )
{
if ( self - > client - > ps . weapon ! = WP_SABER )
{
CG_ChangeWeapon ( WP_SABER ) ;
}
else
{ //if it's not active, turn it on
self - > client - > ps . saberActive = qtrue ;
}
}
}
}
void WP_SaberReturn ( gentity_t * self , gentity_t * saber )
{
if ( PM_SaberInBrokenParry ( self - > client - > ps . saberMove ) | | self - > client - > ps . saberBlocked = = BLOCKED_PARRY_BROKEN )
{
return ;
}
if ( self & & self - > client )
{ //still alive and stuff
//FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm...
self - > client - > ps . saberEntityState = SES_RETURNING ;
//turn down the saber trail
self - > client - > saberTrail . inAction = qfalse ;
self - > client - > saberTrail . duration = 75 ;
}
if ( ! ( saber - > s . eFlags & EF_BOUNCE ) )
{
saber - > s . eFlags | = EF_BOUNCE ;
saber - > bounceCount = 300 ;
}
}
void WP_SaberDrop ( gentity_t * self , gentity_t * saber )
{
saber - > s . eFlags & = ~ EF_BOUNCE ;
saber - > bounceCount = 0 ;
//make it fall
saber - > s . pos . trType = TR_GRAVITY ;
//make it bounce some
saber - > s . eFlags | = EF_BOUNCE_HALF ;
//make it spin
VectorCopy ( saber - > currentAngles , saber - > s . apos . trBase ) ;
saber - > s . apos . trType = TR_LINEAR ;
saber - > s . apos . trTime = level . time ;
VectorSet ( saber - > s . apos . trDelta , Q_irand ( - 300 , 300 ) , saber - > s . apos . trDelta [ 1 ] , Q_irand ( - 300 , 300 ) ) ;
if ( ! saber - > s . apos . trDelta [ 1 ] )
{
saber - > s . apos . trDelta [ 1 ] = Q_irand ( - 300 , 300 ) ;
}
//force it to be ready to return
self - > client - > ps . saberEntityDist = 0 ;
self - > client - > ps . saberEntityState = SES_RETURNING ;
//turn it off
self - > client - > ps . saberActive = qfalse ;
//turn off the saber trail
self - > client - > saberTrail . inAction = qfalse ;
self - > client - > saberTrail . duration = 75 ;
//play the saber turning off sound
if ( self - > client - > playerTeam = = TEAM_PLAYER )
{
G_SoundOnEnt ( saber , CHAN_AUTO , " sound/weapons/saber/saberoff.wav " ) ;
}
else
{
G_SoundOnEnt ( saber , CHAN_AUTO , " sound/weapons/saber/enemy_saber_off.wav " ) ;
}
if ( self - > health < = 0 )
{ //owner is dead!
saber - > s . time = level . time ; //will make us free ourselves after a time
}
}
void WP_SaberPull ( gentity_t * self , gentity_t * saber )
{
if ( PM_SaberInBrokenParry ( self - > client - > ps . saberMove ) | | self - > client - > ps . saberBlocked = = BLOCKED_PARRY_BROKEN )
{
return ;
}
if ( self - > health > 0 )
{
//take off gravity
saber - > s . pos . trType = TR_LINEAR ;
//take off bounce
saber - > s . eFlags & = EF_BOUNCE_HALF ;
//play sound
G_Sound ( self , G_SoundIndex ( " sound/weapons/force/pull.wav " ) ) ;
}
}
// Check if we are throwing it, launch it if needed, update position if needed.
void WP_SaberThrow ( gentity_t * self , usercmd_t * ucmd )
{
vec3_t saberDiff ;
trace_t tr ;
//static float SABER_SPEED = 10;
gentity_t * saberent ;
if ( self - > client - > ps . saberEntityNum < = 0 | | self - > client - > ps . saberEntityNum > = ENTITYNUM_WORLD )
{ //WTF?!! We lost it?
return ;
}
saberent = & g_entities [ self - > client - > ps . saberEntityNum ] ;
VectorSubtract ( self - > client - > renderInfo . handRPoint , saberent - > currentOrigin , saberDiff ) ;
//is our saber in flight?
if ( ! self - > client - > ps . saberInFlight )
{ //saber is not in flight right now
if ( self - > client - > ps . weapon ! = WP_SABER )
{ //don't even have it out
return ;
}
else if ( ucmd - > buttons & BUTTON_ALT_ATTACK & & ! ( self - > client - > ps . pm_flags & PMF_ALT_ATTACK_HELD ) )
{ //still holding it, not still holding attack from a previous throw, so throw it.
if ( ! ( self - > client - > ps . saberEventFlags & SEF_INWATER ) & & WP_SaberLaunch ( self , saberent , qtrue ) )
{
if ( self - > client & & ! self - > s . number )
{
self - > client - > sess . missionStats . saberThrownCnt + + ;
}
//need to recalc this because we just moved it
VectorSubtract ( self - > client - > renderInfo . handRPoint , saberent - > currentOrigin , saberDiff ) ;
}
else
{ //couldn't throw it
return ;
}
}
else
{ //holding it, don't want to throw it, go away.
return ;
}
}
else
{ //inflight
//is our saber currently on it's way back to us?
if ( self - > client - > ps . saberEntityState = = SES_RETURNING )
{ //see if we're close enough to pick it up
if ( VectorLengthSquared ( saberDiff ) < = 256 ) //16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )//
{ //caught it
vec3_t axisPoint ;
trace_t trace ;
VectorCopy ( self - > currentOrigin , axisPoint ) ;
axisPoint [ 2 ] = self - > client - > renderInfo . handRPoint [ 2 ] ;
gi . trace ( & trace , axisPoint , vec3_origin , vec3_origin , self - > client - > renderInfo . handRPoint , self - > s . number , MASK_SOLID , G2_NOCOLLIDE , 0 ) ;
if ( ! trace . startsolid & & trace . fraction > = 1.0f )
{ //our hand isn't through a wall
WP_SaberCatch ( self , saberent , qtrue ) ;
NPC_SetAnim ( self , SETANIM_TORSO , TORSO_HANDRETRACT1 , SETANIM_FLAG_OVERRIDE ) ;
}
return ;
}
}
if ( saberent - > s . pos . trType ! = TR_STATIONARY )
{ //saber is in flight, lerp it
WP_RunSaber ( self , saberent ) ;
}
else
{ //it fell on the ground
if ( self - > health < = 0 & & level . time > saberent - > s . time + 5000 )
{ //make us free ourselves after a time
G_FreeEntity ( saberent ) ;
self - > client - > ps . saberEntityNum = ENTITYNUM_NONE ;
return ;
}
if ( ( ! self - > s . number & & level . time - saberent - > aimDebounceTime > 15000 )
| | ( self - > s . number & & level . time - saberent - > aimDebounceTime > 5000 ) )
{ //(only for player) been missing for 15 seconds, automagicially return
WP_SaberCatch ( self , saberent , qfalse ) ;
return ;
}
}
}
//are we still trying to use the saber?
if ( self - > client - > ps . weapon ! = WP_SABER )
{ //switched away
if ( ! self - > client - > ps . saberInFlight )
{ //wasn't throwing saber
return ;
}
else if ( saberent - > s . pos . trType = = TR_LINEAR )
{ //switched away while controlling it, just drop the saber
WP_SaberDrop ( self , saberent ) ;
return ;
}
else
{ //it's on the ground, see if it's inside us (touching)
if ( G_PointInBounds ( saberent - > currentOrigin , self - > absmin , self - > absmax ) )
{ //it's in us, pick it up automatically
WP_SaberPull ( self , saberent ) ;
}
}
}
else if ( saberent - > s . pos . trType ! = TR_LINEAR )
{ //weapon is saber and not flying
if ( self - > client - > ps . saberInFlight )
{ //we dropped it
if ( ucmd - > buttons & BUTTON_ATTACK ) //|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK ||
{ //we actively want to pick it up or we just switched to it, so pull it back
gi . trace ( & tr , saberent - > currentOrigin , saberent - > mins , saberent - > maxs , self - > client - > renderInfo . handRPoint , self - > s . number , MASK_SOLID , G2_NOCOLLIDE , 0 ) ;
if ( tr . allsolid | | tr . startsolid | | tr . fraction < 1.0f )
{ //can't pick it up yet, no LOS
return ;
}
//clear LOS, pick it up
WP_SaberPull ( self , saberent ) ;
}
else
{ //see if it's inside us (touching)
if ( G_PointInBounds ( saberent - > currentOrigin , self - > absmin , self - > absmax ) )
{ //it's in us, pick it up automatically
WP_SaberPull ( self , saberent ) ;
}
}
}
}
else if ( self - > health < = 0 & & self - > client - > ps . saberInFlight )
{ //we died, drop it
WP_SaberDrop ( self , saberent ) ;
return ;
}
else if ( ! self - > client - > ps . saberActive & & self - > client - > ps . saberEntityState ! = SES_RETURNING )
{ //we turned it off, drop it
WP_SaberDrop ( self , saberent ) ;
return ;
}
//TODO: if deactivate saber in flight, should it drop?
if ( saberent - > s . pos . trType ! = TR_LINEAR )
{ //don't home
return ;
}
float saberDist = VectorLength ( saberDiff ) ;
if ( self - > client - > ps . saberEntityState = = SES_LEAVING )
{ //saber still flying forward
if ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] > FORCE_LEVEL_2 )
{ //still holding it out
if ( ! ( ucmd - > buttons & BUTTON_ALT_ATTACK ) & & self - > client - > ps . forcePowerDebounce [ FP_SABERTHROW ] < level . time )
{ //done throwing, return to me
if ( self - > client - > ps . saberActive )
{ //still on
WP_SaberReturn ( self , saberent ) ;
}
}
else if ( level . time - self - > client - > ps . saberThrowTime > = 100 )
{
if ( WP_ForcePowerAvailable ( self , FP_SABERTHROW , 1 ) )
{
WP_ForcePowerDrain ( self , FP_SABERTHROW , 1 ) ;
self - > client - > ps . saberThrowTime = level . time ;
}
else
{ //out of force power, return to me
WP_SaberReturn ( self , saberent ) ;
}
}
}
else
{
if ( ! ( ucmd - > buttons & BUTTON_ALT_ATTACK ) & & self - > client - > ps . forcePowerDebounce [ FP_SABERTHROW ] < level . time )
{ //not holding button and has been out at least 1 second, return to me
if ( self - > client - > ps . saberActive )
{ //still on
WP_SaberReturn ( self , saberent ) ;
}
}
else if ( level . time - self - > client - > ps . saberThrowTime > 3000
| | ( self - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = = FORCE_LEVEL_1 & & saberDist > = self - > client - > ps . saberEntityDist ) )
{ //been out too long, or saber throw 1 went too far, return to me
if ( self - > client - > ps . saberActive )
{ //still on
WP_SaberReturn ( self , saberent ) ;
}
}
}
}
if ( self - > client - > ps . saberEntityState = = SES_RETURNING )
{
if ( self - > client - > ps . saberEntityDist > 0 )
{
self - > client - > ps . saberEntityDist - = 25 ;
}
if ( self - > client - > ps . saberEntityDist < 0 )
{
self - > client - > ps . saberEntityDist = 0 ;
}
else if ( saberDist < self - > client - > ps . saberEntityDist )
{ //if it's coming back to me, never push it away
self - > client - > ps . saberEntityDist = saberDist ;
}
}
}
//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
//SABER BLOCKING============================================================================
int WP_MissileBlockForBlock ( int saberBlock )
{
switch ( saberBlock )
{
case BLOCKED_UPPER_RIGHT :
return BLOCKED_UPPER_RIGHT_PROJ ;
break ;
case BLOCKED_UPPER_LEFT :
return BLOCKED_UPPER_LEFT_PROJ ;
break ;
case BLOCKED_LOWER_RIGHT :
return BLOCKED_LOWER_RIGHT_PROJ ;
break ;
case BLOCKED_LOWER_LEFT :
return BLOCKED_LOWER_LEFT_PROJ ;
break ;
case BLOCKED_TOP :
return BLOCKED_TOP_PROJ ;
break ;
}
return saberBlock ;
}
void WP_SaberBlockNonRandom ( gentity_t * self , vec3_t hitloc , qboolean missileBlock )
{
vec3_t diff , fwdangles = { 0 , 0 , 0 } , right ;
float rightdot ;
float zdiff ;
if ( self - > client - > ps . weaponstate = = WEAPON_DROPPING | |
self - > client - > ps . weaponstate = = WEAPON_RAISING )
{ //don't block while changing weapons
return ;
}
//NPCs don't auto-block
if ( ! missileBlock & & self - > s . number ! = 0 & & self - > client - > ps . saberBlocked ! = BLOCKED_NONE )
{
return ;
}
2022-10-13 22:19:44 +00:00
if ( self - > client - > ps . clientNum = = 0 & & missileBlock & & ! cg_thirdPerson . integer & &
! g_saberAutoDeflect1stPerson - > integer )
{
return ;
}
2022-09-18 15:37:21 +00:00
VectorSubtract ( hitloc , self - > client - > renderInfo . eyePoint , diff ) ;
diff [ 2 ] = 0 ;
VectorNormalize ( diff ) ;
fwdangles [ 1 ] = self - > client - > ps . viewangles [ 1 ] ;
// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
AngleVectors ( fwdangles , NULL , right , NULL ) ;
rightdot = DotProduct ( right , diff ) ;
zdiff = hitloc [ 2 ] - self - > client - > renderInfo . eyePoint [ 2 ] ;
//FIXME: take torsoAngles into account?
if ( zdiff > - 5 ) //0 )//40 )
{
if ( rightdot > 0.3 )
{
self - > client - > ps . saberBlocked = BLOCKED_UPPER_RIGHT ;
}
else if ( rightdot < - 0.3 )
{
self - > client - > ps . saberBlocked = BLOCKED_UPPER_LEFT ;
}
else
{
self - > client - > ps . saberBlocked = BLOCKED_TOP ;
}
}
else if ( zdiff > - 22 ) //-20 )//20 )
{
if ( zdiff < - 10 ) //30 )
{ //hmm, pretty low, but not low enough to use the low block, so we need to duck
//NPC should duck, but NPC should never get here
}
if ( rightdot > 0.1 )
{
self - > client - > ps . saberBlocked = BLOCKED_UPPER_RIGHT ;
}
else if ( rightdot < - 0.1 )
{
self - > client - > ps . saberBlocked = BLOCKED_UPPER_LEFT ;
}
else
{ //FIXME: this looks really weird if the shot is too low!
self - > client - > ps . saberBlocked = BLOCKED_TOP ;
}
}
else
{
if ( rightdot > = 0 )
{
self - > client - > ps . saberBlocked = BLOCKED_LOWER_RIGHT ;
}
else
{
self - > client - > ps . saberBlocked = BLOCKED_LOWER_LEFT ;
}
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer )
{
if ( ! self - > s . number )
{
gi . Printf ( " EyeZ: %4.2f HitZ: %4.2f zdiff: %4.2f rdot: %4.2f \n " , self - > client - > renderInfo . eyePoint [ 2 ] , hitloc [ 2 ] , zdiff , rightdot ) ;
switch ( self - > client - > ps . saberBlocked )
{
case BLOCKED_TOP :
gi . Printf ( " BLOCKED_TOP \n " ) ;
break ;
case BLOCKED_UPPER_RIGHT :
gi . Printf ( " BLOCKED_UPPER_RIGHT \n " ) ;
break ;
case BLOCKED_UPPER_LEFT :
gi . Printf ( " BLOCKED_UPPER_LEFT \n " ) ;
break ;
case BLOCKED_LOWER_RIGHT :
gi . Printf ( " BLOCKED_LOWER_RIGHT \n " ) ;
break ;
case BLOCKED_LOWER_LEFT :
gi . Printf ( " BLOCKED_LOWER_LEFT \n " ) ;
break ;
default :
break ;
}
}
}
# endif
if ( missileBlock )
{
self - > client - > ps . saberBlocked = WP_MissileBlockForBlock ( self - > client - > ps . saberBlocked ) ;
}
if ( self - > client - > ps . saberBlocked ! = BLOCKED_NONE )
{
int parryReCalcTime = Jedi_ReCalcParryTime ( self , EVASION_PARRY ) ;
if ( self - > client - > ps . forcePowerDebounce [ FP_SABER_DEFENSE ] < level . time + parryReCalcTime )
{
self - > client - > ps . forcePowerDebounce [ FP_SABER_DEFENSE ] = level . time + parryReCalcTime ;
}
}
}
void WP_SaberBlock ( gentity_t * saber , vec3_t hitloc , qboolean missileBlock )
{
gentity_t * playerent ;
vec3_t diff , fwdangles = { 0 , 0 , 0 } , right ;
float rightdot ;
float zdiff ;
if ( saber & & saber - > owner )
{
playerent = saber - > owner ;
if ( ! playerent - > client )
{
return ;
}
if ( playerent - > client - > ps . weaponstate = = WEAPON_DROPPING | |
playerent - > client - > ps . weaponstate = = WEAPON_RAISING )
{ //don't block while changing weapons
return ;
}
}
else
{ // Bad entity passed.
return ;
}
//temporarily disabling auto-blocking for NPCs...
if ( ! missileBlock & & playerent - > s . number ! = 0 & & playerent - > client - > ps . saberBlocked ! = BLOCKED_NONE )
{
return ;
}
VectorSubtract ( hitloc , playerent - > currentOrigin , diff ) ;
VectorNormalize ( diff ) ;
fwdangles [ 1 ] = playerent - > client - > ps . viewangles [ 1 ] ;
// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
AngleVectors ( fwdangles , NULL , right , NULL ) ;
rightdot = DotProduct ( right , diff ) + Q_flrand ( - 0.2f , 0.2f ) ;
zdiff = hitloc [ 2 ] - playerent - > currentOrigin [ 2 ] + Q_irand ( - 8 , 8 ) ;
// Figure out what quadrant the block was in.
if ( zdiff > 24 )
{ // Attack from above
if ( Q_irand ( 0 , 1 ) )
{
playerent - > client - > ps . saberBlocked = BLOCKED_TOP ;
}
else
{
playerent - > client - > ps . saberBlocked = BLOCKED_UPPER_LEFT ;
}
}
else if ( zdiff > 13 )
{ // The upper half has three viable blocks...
if ( rightdot > 0.25 )
{ // In the right quadrant...
if ( Q_irand ( 0 , 1 ) )
{
playerent - > client - > ps . saberBlocked = BLOCKED_UPPER_LEFT ;
}
else
{
playerent - > client - > ps . saberBlocked = BLOCKED_LOWER_LEFT ;
}
}
else
{
switch ( Q_irand ( 0 , 3 ) )
{
case 0 :
playerent - > client - > ps . saberBlocked = BLOCKED_UPPER_RIGHT ;
break ;
case 1 :
case 2 :
playerent - > client - > ps . saberBlocked = BLOCKED_LOWER_RIGHT ;
break ;
case 3 :
playerent - > client - > ps . saberBlocked = BLOCKED_TOP ;
break ;
}
}
}
else
{ // The lower half is a bit iffy as far as block coverage. Pick one of the "low" ones at random.
if ( Q_irand ( 0 , 1 ) )
{
playerent - > client - > ps . saberBlocked = BLOCKED_LOWER_RIGHT ;
}
else
{
playerent - > client - > ps . saberBlocked = BLOCKED_LOWER_LEFT ;
}
}
if ( missileBlock )
{
playerent - > client - > ps . saberBlocked = WP_MissileBlockForBlock ( playerent - > client - > ps . saberBlocked ) ;
}
}
void WP_SaberStartMissileBlockCheck ( gentity_t * self , usercmd_t * ucmd )
{
float dist ;
gentity_t * ent , * incoming = NULL ;
gentity_t * entityList [ MAX_GENTITIES ] ;
int numListedEntities ;
vec3_t mins , maxs ;
int i , e ;
float closestDist , radius = 256 ;
vec3_t forward , dir , missile_dir , fwdangles = { 0 } ;
trace_t trace ;
vec3_t traceTo , entDir ;
if ( self - > client - > ps . weapon ! = WP_SABER )
{
return ;
}
if ( self - > client - > ps . saberInFlight )
{
return ;
}
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_LIGHTNING ) )
{ //can't block while zapping
return ;
}
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_PUSH ) )
{ //can't block while shoving
return ;
}
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_GRIP ) )
{ //can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
return ;
}
if ( self - > health < = 0 )
{ //dead don't try to block (NOTE: actual deflection happens in missile code)
return ;
}
if ( PM_InKnockDown ( & self - > client - > ps ) )
{ //can't block when knocked down
return ;
}
if ( ! self - > client - > ps . saberLength )
{
if ( self - > s . number = = 0 )
{ //player doesn't auto-activate
return ;
}
}
if ( ! self - > s . number )
{ //don't do this if already attacking!
if ( ucmd - > buttons & BUTTON_ATTACK
| | PM_SaberInAttack ( self - > client - > ps . saberMove )
| | PM_SaberInSpecialAttack ( self - > client - > ps . torsoAnim )
| | PM_SaberInTransitionAny ( self - > client - > ps . saberMove ) )
{
return ;
}
}
if ( self - > client - > ps . forcePowerDebounce [ FP_SABER_DEFENSE ] > level . time )
{ //can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
return ;
}
2022-10-13 22:19:44 +00:00
if ( cg_thirdPerson . integer ) {
if ( ! self - > s . number & & ! g_saberAutoBlocking - > integer & &
self - > client - > ps . saberBlockingTime < level . time ) {
return ;
}
} else // first person
{
if ( ! self - > s . number & & ! g_saberAutoDeflect1stPerson - > integer & &
self - > client - > ps . saberBlockingTime < level . time ) {
return ;
}
}
2022-09-18 15:37:21 +00:00
fwdangles [ 1 ] = self - > client - > ps . viewangles [ 1 ] ;
AngleVectors ( fwdangles , forward , NULL , NULL ) ;
for ( i = 0 ; i < 3 ; i + + )
{
mins [ i ] = self - > currentOrigin [ i ] - radius ;
maxs [ i ] = self - > currentOrigin [ i ] + radius ;
}
numListedEntities = gi . EntitiesInBox ( mins , maxs , entityList , MAX_GENTITIES ) ;
closestDist = radius ;
for ( e = 0 ; e < numListedEntities ; e + + )
{
ent = entityList [ e ] ;
if ( ent = = self )
continue ;
if ( ent - > owner = = self )
continue ;
if ( ! ( ent - > inuse ) )
continue ;
if ( ent - > s . eType ! = ET_MISSILE & & ! ( ent - > s . eFlags & EF_MISSILE_STICK ) )
{ //not a normal projectile
if ( ent - > client | | ent - > s . weapon ! = WP_SABER )
{ //FIXME: wake up bad guys?
continue ;
}
if ( ent - > s . eFlags & EF_NODRAW )
{
continue ;
}
if ( Q_stricmp ( " lightsaber " , ent - > classname ) ! = 0 )
{ //not a lightsaber
//FIXME: what about general objects that are small in size- like rocks, etc...
continue ;
}
//a lightsaber.. make sure it's on and inFlight
if ( ! ent - > owner | | ! ent - > owner - > client )
{
continue ;
}
if ( ! ent - > owner - > client - > ps . saberInFlight )
{ //not in flight
continue ;
}
if ( ent - > owner - > client - > ps . saberLength < = 0 )
{ //not on
continue ;
}
if ( ent - > owner - > health < = 0 & & ! g_saberRealisticCombat - > integer )
{ //it's not doing damage, so ignore it
continue ;
}
}
else
{
if ( ent - > s . pos . trType = = TR_STATIONARY & & ! self - > s . number )
{ //nothing you can do with a stationary missile if you're the player
continue ;
}
}
float dot1 , dot2 ;
//see if they're in front of me
VectorSubtract ( ent - > currentOrigin , self - > currentOrigin , dir ) ;
dist = VectorNormalize ( dir ) ;
//FIXME: handle detpacks, proximity mines and tripmines
if ( ent - > s . weapon = = WP_THERMAL )
{ //thermal detonator!
if ( self - > NPC & & dist < ent - > splashRadius )
{
if ( dist < ent - > splashRadius & &
ent - > nextthink < level . time + 600 & &
ent - > count & &
self - > client - > ps . groundEntityNum ! = ENTITYNUM_NONE & &
( ent - > s . pos . trType = = TR_STATIONARY | |
ent - > s . pos . trType = = TR_INTERPOLATE | |
( dot1 = DotProduct ( dir , forward ) ) < SABER_REFLECT_MISSILE_CONE | |
! WP_ForcePowerUsable ( self , FP_PUSH , 0 ) ) )
{ //TD is close enough to hurt me, I'm on the ground and the thing is at rest or behind me and about to blow up, or I don't have force-push so force-jump!
//FIXME: sometimes this might make me just jump into it...?
self - > client - > ps . forceJumpCharge = 480 ;
}
else
{ //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
ForceThrow ( self , qfalse ) ;
}
}
continue ;
}
else if ( ent - > splashDamage & & ent - > splashRadius )
{ //exploding missile
//FIXME: handle tripmines and detpacks somehow...
// maybe do a force-gesture that makes them explode?
// But what if we're within it's splashradius?
if ( ! self - > s . number )
{ //players don't auto-handle these at all
continue ;
}
else
{
if ( ent - > s . pos . trType = = TR_STATIONARY & & ( ent - > s . eFlags & EF_MISSILE_STICK ) )
{ //a placed explosive like a tripmine or detpack
if ( InFOV ( ent - > currentOrigin , self - > client - > renderInfo . eyePoint , self - > client - > ps . viewangles , 90 , 90 ) )
{ //in front of me
if ( G_ClearLOS ( self , ent ) )
{ //can see it
vec3_t throwDir ;
//make the gesture
ForceThrow ( self , qfalse ) ;
//take it off the wall and toss it
ent - > s . pos . trType = TR_GRAVITY ;
ent - > s . eType = ET_MISSILE ;
ent - > s . eFlags & = ~ EF_MISSILE_STICK ;
ent - > s . eFlags | = EF_BOUNCE_HALF ;
AngleVectors ( ent - > currentAngles , throwDir , NULL , NULL ) ;
VectorMA ( ent - > currentOrigin , ent - > maxs [ 0 ] + 4 , throwDir , ent - > currentOrigin ) ;
VectorCopy ( ent - > currentOrigin , ent - > s . pos . trBase ) ;
VectorScale ( throwDir , 300 , ent - > s . pos . trDelta ) ;
ent - > s . pos . trDelta [ 2 ] + = 150 ;
VectorMA ( ent - > s . pos . trDelta , 800 , dir , ent - > s . pos . trDelta ) ;
ent - > s . pos . trTime = level . time ; // move a bit on the very first frame
VectorCopy ( ent - > currentOrigin , ent - > s . pos . trBase ) ;
ent - > owner = self ;
// make it explode, but with less damage
ent - > splashDamage / = 3 ;
ent - > splashRadius / = 3 ;
ent - > e_ThinkFunc = thinkF_WP_Explode ;
ent - > nextthink = level . time + Q_irand ( 500 , 3000 ) ;
}
}
}
else if ( dist < ent - > splashRadius & &
self - > client - > ps . groundEntityNum ! = ENTITYNUM_NONE & &
( DotProduct ( dir , forward ) < SABER_REFLECT_MISSILE_CONE | |
! WP_ForcePowerUsable ( self , FP_PUSH , 0 ) ) )
{ //NPCs try to evade it
self - > client - > ps . forceJumpCharge = 480 ;
}
else
{ //else, try to force-throw it away
//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
ForceThrow ( self , qfalse ) ;
}
}
//otherwise, can't block it, so we're screwed
continue ;
}
if ( ent - > s . weapon ! = WP_SABER )
{ //only block shots coming from behind
if ( ( dot1 = DotProduct ( dir , forward ) ) < SABER_REFLECT_MISSILE_CONE )
continue ;
}
else if ( ! self - > s . number )
{ //player never auto-blocks thrown sabers
continue ;
} //NPCs always try to block sabers coming from behind!
//see if they're heading towards me
VectorCopy ( ent - > s . pos . trDelta , missile_dir ) ;
VectorNormalize ( missile_dir ) ;
if ( ( dot2 = DotProduct ( dir , missile_dir ) ) > 0 )
continue ;
//FIXME: must have a clear trace to me, too...
if ( dist < closestDist )
{
VectorCopy ( self - > currentOrigin , traceTo ) ;
traceTo [ 2 ] = self - > absmax [ 2 ] - 4 ;
gi . trace ( & trace , ent - > currentOrigin , ent - > mins , ent - > maxs , traceTo , ent - > s . number , ent - > clipmask , G2_NOCOLLIDE , 0 ) ;
if ( trace . allsolid | | trace . startsolid | | ( trace . fraction < 1.0f & & trace . entityNum ! = self - > s . number & & trace . entityNum ! = self - > client - > ps . saberEntityNum ) )
{ //okay, try one more check
VectorNormalize2 ( ent - > s . pos . trDelta , entDir ) ;
VectorMA ( ent - > currentOrigin , radius , entDir , traceTo ) ;
gi . trace ( & trace , ent - > currentOrigin , ent - > mins , ent - > maxs , traceTo , ent - > s . number , ent - > clipmask , G2_NOCOLLIDE , 0 ) ;
if ( trace . allsolid | | trace . startsolid | | ( trace . fraction < 1.0f & & trace . entityNum ! = self - > s . number & & trace . entityNum ! = self - > client - > ps . saberEntityNum ) )
{ //can't hit me, ignore it
continue ;
}
}
if ( self - > s . number ! = 0 )
{ //An NPC
if ( self - > NPC & & ! self - > enemy & & ent - > owner )
{
if ( ent - > owner - > health > = 0 & & ( ! ent - > owner - > client | | ent - > owner - > client - > playerTeam ! = self - > client - > playerTeam ) )
{
G_SetEnemy ( self , ent - > owner ) ;
}
}
}
//FIXME: if NPC, predict the intersection between my current velocity/path and the missile's, see if it intersects my bounding box (+/-saberLength?), don't try to deflect unless it does?
closestDist = dist ;
incoming = ent ;
}
}
if ( incoming )
{
if ( self - > NPC & & ! G_ControlledByPlayer ( self ) )
{
if ( Jedi_WaitingAmbush ( self ) )
{
Jedi_Ambush ( self ) ;
}
if ( Jedi_SaberBlockGo ( self , & self - > NPC - > last_ucmd , NULL , NULL , incoming ) ! = EVASION_NONE )
{ //make sure to turn on your saber if it's not on
self - > client - > ps . saberActive = qtrue ;
}
}
else //player
{
WP_SaberBlockNonRandom ( self , incoming - > currentOrigin , qtrue ) ;
if ( incoming - > owner & & incoming - > owner - > client & & ( ! self - > enemy | | self - > enemy - > s . weapon ! = WP_SABER ) ) //keep enemy jedi over shooters
{
self - > enemy = incoming - > owner ;
NPC_SetLookTarget ( self , incoming - > owner - > s . number , level . time + 1000 ) ;
}
}
}
}
//GENERAL SABER============================================================================
//GENERAL SABER============================================================================
//GENERAL SABER============================================================================
//GENERAL SABER============================================================================
//GENERAL SABER============================================================================
void WP_SetSaberMove ( gentity_t * self , short blocked )
{
self - > client - > ps . saberBlocked = blocked ;
}
extern void CG_CubeOutline ( vec3_t mins , vec3_t maxs , int time , unsigned int color , float alpha ) ;
void WP_SaberUpdate ( gentity_t * self , usercmd_t * ucmd )
{
//float swap;
float minsize = 16 ;
if ( 0 ) // if ( self->s.number != 0 )
{ //for now only the player can do this // not anymore
return ;
}
if ( ! self - > client )
{
return ;
}
if ( self - > client - > ps . saberEntityNum < 0 | | self - > client - > ps . saberEntityNum > = ENTITYNUM_WORLD )
{ //never got one
return ;
}
// Check if we are throwing it, launch it if needed, update position if needed.
WP_SaberThrow ( self , ucmd ) ;
//vec3_t saberloc;
//vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8};
gentity_t * saberent ;
if ( self - > client - > ps . saberEntityNum < = 0 )
{ //WTF?!! We lost it?
return ;
}
saberent = & g_entities [ self - > client - > ps . saberEntityNum ] ;
//FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller
if ( self - > client - > ps . saberBlocked ! = BLOCKED_NONE )
{ //we're blocking, increase min size
minsize = 32 ;
}
//is our saber in flight?
if ( ! self - > client - > ps . saberInFlight )
{ // It isn't, which means we can update its position as we will.
if ( ! self - > client - > ps . saberActive | | PM_InKnockDown ( & self - > client - > ps ) )
{ //can't block if saber isn't on
VectorClear ( saberent - > mins ) ;
VectorClear ( saberent - > maxs ) ;
G_SetOrigin ( saberent , self - > currentOrigin ) ;
}
else if ( self - > client - > ps . saberBlocking = = BLK_TIGHT | | self - > client - > ps . saberBlocking = = BLK_WIDE )
{ //FIXME: keep bbox in front of player, even when wide?
2022-10-13 22:19:44 +00:00
bool autoBlocking = ( cg_thirdPerson . integer & & g_saberAutoBlocking - > integer ) | |
( ! cg_thirdPerson . integer & & g_saberAutoDeflect1stPerson - > integer ) ;
2022-09-18 15:37:21 +00:00
vec3_t saberOrg ;
2022-10-13 22:19:44 +00:00
if ( ( ( self - > s . number & & ! Jedi_SaberBusy ( self ) & & ! g_saberRealisticCombat - > integer ) | | ( self - > s . number = = 0 & & self - > client - > ps . saberBlocking = = BLK_WIDE & &
( autoBlocking | | self - > client - > ps . saberBlockingTime > level . time ) ) )
2022-09-18 15:37:21 +00:00
& & self - > client - > ps . weaponTime < = 0 )
{ //full-size blocking for non-attacking player with g_saberAutoBlocking on
vec3_t saberang = { 0 , 0 , 0 } , fwd , sabermins = { - 8 , - 8 , - 8 } , sabermaxs = { 8 , 8 , 8 } ;
saberang [ YAW ] = self - > client - > ps . viewangles [ YAW ] ;
AngleVectors ( saberang , fwd , NULL , NULL ) ;
VectorMA ( self - > currentOrigin , 12 , fwd , saberOrg ) ;
VectorAdd ( self - > mins , sabermins , saberent - > mins ) ;
VectorAdd ( self - > maxs , sabermaxs , saberent - > maxs ) ;
saberent - > contents = CONTENTS_LIGHTSABER ;
G_SetOrigin ( saberent , saberOrg ) ;
}
else
{
vec3_t saberTip ;
VectorMA ( self - > client - > renderInfo . muzzlePoint , self - > client - > ps . saberLength , self - > client - > renderInfo . muzzleDir , saberTip ) ;
VectorMA ( self - > client - > renderInfo . muzzlePoint , self - > client - > ps . saberLength * 0.5 , self - > client - > renderInfo . muzzleDir , saberOrg ) ;
for ( int i = 0 ; i < 3 ; i + + )
{
if ( saberTip [ i ] > self - > client - > renderInfo . muzzlePoint [ i ] )
{
saberent - > maxs [ i ] = saberTip [ i ] - saberOrg [ i ] + 8 ; //self->client->renderInfo.muzzlePoint[i];
saberent - > mins [ i ] = self - > client - > renderInfo . muzzlePoint [ i ] - saberOrg [ i ] - 8 ;
}
else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] )
{
saberent - > maxs [ i ] = self - > client - > renderInfo . muzzlePoint [ i ] - saberOrg [ i ] + 8 ;
saberent - > mins [ i ] = saberTip [ i ] - saberOrg [ i ] - 8 ; //self->client->renderInfo.muzzlePoint[i];
}
if ( self - > client - > ps . weaponTime > 0 | | self - > s . number | | g_saberAutoBlocking - > integer | | self - > client - > ps . saberBlockingTime > level . time )
{ //if attacking or blocking (or an NPC), inflate to a minimum size
if ( saberent - > maxs [ i ] < minsize )
{
saberent - > maxs [ i ] = minsize ;
}
if ( saberent - > mins [ i ] > - minsize )
{
saberent - > mins [ i ] = - minsize ;
}
}
}
saberent - > contents = CONTENTS_LIGHTSABER ;
G_SetOrigin ( saberent , saberOrg ) ;
}
}
/*
else if ( self - > client - > ps . saberBlocking = = BLK_WIDE )
{ // Assuming that we are not swinging, the saber's bounding box should be around the player.
vec3_t saberang = { 0 , 0 , 0 } , fwd ;
saberang [ YAW ] = self - > client - > ps . viewangles [ YAW ] ;
AngleVectors ( saberang , fwd , NULL , NULL ) ;
VectorMA ( self - > currentOrigin , 12 , fwd , saberloc ) ;
VectorAdd ( self - > mins , sabermins , saberent - > mins ) ;
VectorAdd ( self - > maxs , sabermaxs , saberent - > maxs ) ;
saberent - > contents = CONTENTS_LIGHTSABER ;
G_SetOrigin ( saberent , saberloc ) ;
}
else if ( self - > client - > ps . saberBlocking = = BLK_TIGHT )
{ // If the player is swinging, the bbox is around just the saber
VectorCopy ( self - > client - > renderInfo . muzzlePoint , sabermins ) ;
// Put the limits of the bbox around the saber size.
VectorMA ( sabermins , self - > client - > ps . saberLength , self - > client - > renderInfo . muzzleDir , sabermaxs ) ;
// Now make the mins into mins and the maxs into maxs
if ( sabermins [ 0 ] > sabermaxs [ 0 ] )
{
swap = sabermins [ 0 ] ;
sabermins [ 0 ] = sabermaxs [ 0 ] ;
sabermaxs [ 0 ] = swap ;
}
if ( sabermins [ 1 ] > sabermaxs [ 1 ] )
{
swap = sabermins [ 1 ] ;
sabermins [ 1 ] = sabermaxs [ 1 ] ;
sabermaxs [ 1 ] = swap ;
}
if ( sabermins [ 2 ] > sabermaxs [ 2 ] )
{
swap = sabermins [ 2 ] ;
sabermins [ 2 ] = sabermaxs [ 2 ] ;
sabermaxs [ 2 ] = swap ;
}
// Now the loc is halfway between the (absolute) mins and maxs
VectorAdd ( sabermins , sabermaxs , saberloc ) ;
VectorScale ( saberloc , 0.5 , saberloc ) ;
// Finally, turn the mins and maxs, which are absolute, into relative mins and maxs.
VectorSubtract ( sabermins , saberloc , saberent - > mins ) ;
VectorSubtract ( sabermaxs , saberloc , saberent - > maxs ) ;
saberent - > contents = CONTENTS_LIGHTSABER ; // | CONTENTS_SHOTCLIP;
G_SetOrigin ( saberent , saberloc ) ;
}
*/
else
{ // Otherwise there is no blocking possible.
VectorClear ( saberent - > mins ) ;
VectorClear ( saberent - > maxs ) ;
G_SetOrigin ( saberent , self - > currentOrigin ) ;
}
saberent - > clipmask = MASK_SHOT | CONTENTS_LIGHTSABER ;
gi . linkentity ( saberent ) ;
}
else
{
WP_SaberInFlightReflectCheck ( self , ucmd ) ;
}
# ifndef FINAL_BUILD
if ( d_saberCombat - > integer > 2 )
{
CG_CubeOutline ( saberent - > absmin , saberent - > absmax , 50 , WPDEBUG_SaberColor ( self - > client - > ps . saberColor ) , 1 ) ;
}
# endif
}
//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
//OTHER JEDI POWERS=========================================================================
extern gentity_t * TossClientItems ( gentity_t * self ) ;
extern void ChangeWeapon ( gentity_t * ent , int newWeapon ) ;
void WP_DropWeapon ( gentity_t * dropper , vec3_t velocity )
{
if ( ! dropper | | ! dropper - > client )
{
return ;
}
int replaceWeap = WP_NONE ;
int oldWeap = dropper - > s . weapon ;
gentity_t * weapon = TossClientItems ( dropper ) ;
if ( oldWeap = = WP_THERMAL & & dropper - > NPC )
{ //Hmm, maybe all NPCs should go into melee? Not too many, though, or they mob you and look silly
replaceWeap = WP_MELEE ;
}
if ( dropper - > ghoul2 . IsValid ( ) & & dropper - > weaponModel > = 0 )
{
gi . G2API_RemoveGhoul2Model ( dropper - > ghoul2 , dropper - > weaponModel ) ;
dropper - > weaponModel = - 1 ;
}
//FIXME: does this work on the player?
dropper - > client - > ps . stats [ STAT_WEAPONS ] | = ( 1 < < replaceWeap ) ;
if ( ! dropper - > s . number )
{
if ( oldWeap = = WP_THERMAL )
{
dropper - > client - > ps . ammo [ weaponData [ oldWeap ] . ammoIndex ] - = weaponData [ oldWeap ] . energyPerShot ;
}
else
{
dropper - > client - > ps . stats [ STAT_WEAPONS ] & = ~ ( 1 < < oldWeap ) ;
}
CG_ChangeWeapon ( replaceWeap ) ;
}
else
{
dropper - > client - > ps . stats [ STAT_WEAPONS ] & = ~ ( 1 < < oldWeap ) ;
}
ChangeWeapon ( dropper , replaceWeap ) ;
dropper - > s . weapon = replaceWeap ;
if ( dropper - > NPC )
{
dropper - > NPC - > last_ucmd . weapon = replaceWeap ;
}
if ( weapon ! = NULL & & velocity & & ! VectorCompare ( velocity , vec3_origin ) )
{ //weapon should have a direction to it's throw
VectorScale ( velocity , 3 , weapon - > s . pos . trDelta ) ; //NOTE: Presumes it is moving already...?
if ( weapon - > s . pos . trDelta [ 2 ] < 150 )
{ //this is presuming you don't want them to drop the weapon down on you...
weapon - > s . pos . trDelta [ 2 ] = 150 ;
}
//FIXME: gets stuck inside it's former owner...
weapon - > forcePushTime = level . time + 600 ; // let the push effect last for 600 ms
}
}
void WP_KnockdownTurret ( gentity_t * self , gentity_t * pas )
{
//knock it over
VectorCopy ( pas - > currentOrigin , pas - > s . pos . trBase ) ;
pas - > s . pos . trType = TR_LINEAR_STOP ;
pas - > s . pos . trDuration = 250 ;
pas - > s . pos . trTime = level . time ;
pas - > s . pos . trDelta [ 2 ] = ( 12.0f / ( pas - > s . pos . trDuration * 0.001f ) ) ;
VectorCopy ( pas - > currentAngles , pas - > s . apos . trBase ) ;
pas - > s . apos . trType = TR_LINEAR_STOP ;
pas - > s . apos . trDuration = 250 ;
pas - > s . apos . trTime = level . time ;
//FIXME: pick pitch/roll that always tilts it directly away from pusher
pas - > s . apos . trDelta [ PITCH ] = ( 100.0f / ( pas - > s . apos . trDuration * 0.001f ) ) ;
//kill it
pas - > count = 0 ;
pas - > nextthink = - 1 ;
G_Sound ( pas , G_SoundIndex ( " sound/chars/turret/shutdown.wav " ) ) ;
//push effect?
pas - > forcePushTime = level . time + 600 ; // let the push effect last for 600 ms
}
void WP_ResistForcePush ( gentity_t * self , gentity_t * pusher , qboolean noPenalty )
{
int parts ;
qboolean runningResist = qfalse ;
if ( ! self | | self - > health < = 0 | | ! self - > client | | ! pusher | | ! pusher - > client )
{
return ;
}
if ( ( ! self - > s . number | | self - > client - > NPC_class = = CLASS_DESANN | | self - > client - > NPC_class = = CLASS_LUKE )
& & ( VectorLengthSquared ( self - > client - > ps . velocity ) > 10000 | | self - > client - > ps . forcePowerLevel [ FP_PUSH ] > = FORCE_LEVEL_3 | | self - > client - > ps . forcePowerLevel [ FP_PULL ] > = FORCE_LEVEL_3 ) )
{
runningResist = qtrue ;
}
if ( ! runningResist
& & self - > client - > ps . groundEntityNum ! = ENTITYNUM_NONE
& & ! PM_SpinningSaberAnim ( self - > client - > ps . legsAnim )
& & ! PM_FlippingAnim ( self - > client - > ps . legsAnim )
& & ! PM_RollingAnim ( self - > client - > ps . legsAnim )
& & ! PM_InKnockDown ( & self - > client - > ps )
& & ! PM_CrouchAnim ( self - > client - > ps . legsAnim ) )
{ //if on a surface and not in a spin or flip, play full body resist
parts = SETANIM_BOTH ;
}
else
{ //play resist just in torso
parts = SETANIM_TORSO ;
}
NPC_SetAnim ( self , parts , BOTH_RESISTPUSH , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
if ( ! noPenalty )
{
if ( ! runningResist )
{
VectorClear ( self - > client - > ps . velocity ) ;
//still stop them from attacking or moving for a bit, though
//FIXME: maybe push just a little (like, slide)?
self - > client - > ps . weaponTime = 1000 ;
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
self - > client - > ps . weaponTime = floor ( self - > client - > ps . weaponTime * g_timescale - > value ) ;
}
self - > client - > ps . pm_time = self - > client - > ps . weaponTime ;
self - > client - > ps . pm_flags | = PMF_TIME_KNOCKBACK ;
//play the full body push effect on me
self - > forcePushTime = level . time + 600 ; // let the push effect last for 600 ms
}
else
{
self - > client - > ps . weaponTime = 600 ;
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
self - > client - > ps . weaponTime = floor ( self - > client - > ps . weaponTime * g_timescale - > value ) ;
}
}
}
//play my force push effect on my hand
self - > client - > ps . powerups [ PW_FORCE_PUSH ] = level . time + self - > client - > ps . torsoAnimTimer + 500 ;
Jedi_PlayBlockedPushSound ( self ) ;
}
void WP_ForceKnockdown ( gentity_t * self , gentity_t * pusher , qboolean pull , qboolean strongKnockdown , qboolean breakSaberLock )
{
if ( ! self | | ! self - > client | | ! pusher | | ! pusher - > client )
{
return ;
}
//break out of a saberLock?
if ( breakSaberLock )
{
self - > client - > ps . saberLockTime = 0 ;
self - > client - > ps . saberLockEnemy = ENTITYNUM_NONE ;
}
if ( self - > health > 0 )
{
if ( ! self - > s . number )
{
NPC_SetPainEvent ( self ) ;
}
else
{
GEntity_PainFunc ( self , pusher , pusher , self - > currentOrigin , 0 , MOD_MELEE ) ;
}
vec3_t pushDir ;
if ( pull )
{
VectorSubtract ( pusher - > currentOrigin , self - > currentOrigin , pushDir ) ;
}
else
{
VectorSubtract ( self - > currentOrigin , pusher - > currentOrigin , pushDir ) ;
}
G_CheckLedgeDive ( self , 72 , pushDir , qfalse , qfalse ) ;
if ( ! PM_SpinningSaberAnim ( self - > client - > ps . legsAnim )
& & ! PM_FlippingAnim ( self - > client - > ps . legsAnim )
& & ! PM_RollingAnim ( self - > client - > ps . legsAnim )
& & ! PM_InKnockDown ( & self - > client - > ps ) )
{
int knockAnim = BOTH_KNOCKDOWN1 ; //default knockdown
if ( pusher - > client - > NPC_class = = CLASS_DESANN & & self - > client - > NPC_class ! = CLASS_LUKE )
{ //desann always knocks down, unless you're Luke
strongKnockdown = qtrue ;
}
if ( ! self - > s . number
& & ! strongKnockdown
& & ( ( ! pull & & ( self - > client - > ps . forcePowerLevel [ FP_PUSH ] > FORCE_LEVEL_1 | | ! g_spskill - > integer ) ) | | ( pull & & ( self - > client - > ps . forcePowerLevel [ FP_PULL ] > FORCE_LEVEL_1 | | ! g_spskill - > integer ) ) ) )
{ //player only knocked down if pushed *hard*
if ( self - > s . weapon = = WP_SABER )
{ //temp HACK: these are the only 2 pain anims that look good when holding a saber
knockAnim = PM_PickAnim ( self , BOTH_PAIN2 , BOTH_PAIN3 ) ;
}
else
{
knockAnim = PM_PickAnim ( self , BOTH_PAIN1 , BOTH_PAIN19 ) ;
}
}
else if ( PM_CrouchAnim ( self - > client - > ps . legsAnim ) )
{ //crouched knockdown
knockAnim = BOTH_KNOCKDOWN4 ;
}
else
{ //plain old knockdown
vec3_t pLFwd , pLAngles = { 0 , self - > client - > ps . viewangles [ YAW ] , 0 } ;
vec3_t sFwd , sAngles = { 0 , pusher - > client - > ps . viewangles [ YAW ] , 0 } ;
AngleVectors ( pLAngles , pLFwd , NULL , NULL ) ;
AngleVectors ( sAngles , sFwd , NULL , NULL ) ;
if ( DotProduct ( sFwd , pLFwd ) > 0.2f )
{ //pushing him from behind
//FIXME: check to see if we're aiming below or above the waist?
if ( pull )
{
knockAnim = BOTH_KNOCKDOWN1 ;
}
else
{
knockAnim = BOTH_KNOCKDOWN3 ;
}
}
else
{ //pushing him from front
if ( pull )
{
knockAnim = BOTH_KNOCKDOWN3 ;
}
else
{
knockAnim = BOTH_KNOCKDOWN1 ;
}
}
}
if ( knockAnim = = BOTH_KNOCKDOWN1 & & strongKnockdown )
{ //push *hard*
knockAnim = BOTH_KNOCKDOWN2 ;
}
NPC_SetAnim ( self , SETANIM_BOTH , knockAnim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
if ( self - > s . number )
{ //randomize getup times
int addTime = Q_irand ( - 300 , 1000 ) ;
self - > client - > ps . legsAnimTimer + = addTime ;
self - > client - > ps . torsoAnimTimer + = addTime ;
}
//
if ( pusher - > NPC & & pusher - > enemy = = self )
{ //pushed pushed down his enemy
G_AddVoiceEvent ( pusher , Q_irand ( EV_GLOAT1 , EV_GLOAT3 ) , 3000 ) ;
pusher - > NPC - > blockedSpeechDebounceTime = level . time + 3000 ;
}
}
}
self - > forcePushTime = level . time + 600 ; // let the push effect last for 600 ms
}
2022-11-07 23:57:10 +00:00
void ForceThrowEx ( gentity_t * self , qboolean pull , qboolean aimByViewAngles ) ;
2022-09-18 15:37:21 +00:00
void ForceThrow ( gentity_t * self , qboolean pull )
2022-11-07 23:57:10 +00:00
{
ForceThrowEx ( self , pull , qfalse ) ;
}
void ForceThrowEx ( gentity_t * self , qboolean pull , qboolean aimByViewAngles )
2022-09-18 15:37:21 +00:00
{ //FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent.
//shove things in front of you away
float dist ;
gentity_t * ent , * forwardEnt = NULL ;
gentity_t * entityList [ MAX_GENTITIES ] ;
gentity_t * push_list [ MAX_GENTITIES ] ;
int numListedEntities ;
vec3_t mins , maxs ;
vec3_t v ;
int i , e ;
int ent_count = 0 ;
int radius ;
vec3_t center , ent_org , size , forward , right , end , dir , fwdangles = { 0 } ;
float dot1 , cone ;
trace_t tr ;
int anim , hold , soundIndex , cost , actualCost ;
if ( self - > health < = 0 )
{
return ;
}
if ( self - > client - > ps . leanofs )
{ //can't force-throw while leaning
return ;
}
if ( self - > client - > ps . forcePowerDebounce [ FP_PUSH ] > level . time ) //self->client->ps.powerups[PW_FORCE_PUSH] > level.time )
{ //already pushing- now you can't haul someone across the room, sorry
return ;
}
if ( ! self - > s . number & & ( cg . zoomMode | | in_camera ) )
{ //can't force throw/pull when zoomed in or in cinematic
return ;
}
if ( self - > client - > ps . saberLockTime > level . time )
{
if ( pull | | self - > client - > ps . forcePowerLevel [ FP_PUSH ] < FORCE_LEVEL_3 )
{ //this can be a way to break out
return ;
}
//else, I'm breaking my half of the saberlock
self - > client - > ps . saberLockTime = 0 ;
self - > client - > ps . saberLockEnemy = ENTITYNUM_NONE ;
}
if ( self - > client - > ps . legsAnim = = BOTH_KNOCKDOWN3
| | ( self - > client - > ps . torsoAnim = = BOTH_FORCE_GETUP_F1 & & self - > client - > ps . torsoAnimTimer > 400 )
| | ( self - > client - > ps . torsoAnim = = BOTH_FORCE_GETUP_F2 & & self - > client - > ps . torsoAnimTimer > 900 )
| | ( self - > client - > ps . torsoAnim = = BOTH_GETUP3 & & self - > client - > ps . torsoAnimTimer > 500 )
| | ( self - > client - > ps . torsoAnim = = BOTH_GETUP4 & & self - > client - > ps . torsoAnimTimer > 300 )
| | ( self - > client - > ps . torsoAnim = = BOTH_GETUP5 & & self - > client - > ps . torsoAnimTimer > 500 ) )
{ //we're face-down, so we'd only be force-push/pulling the floor
return ;
}
if ( pull )
{
radius = forcePushPullRadius [ self - > client - > ps . forcePowerLevel [ FP_PULL ] ] ;
}
else
{
radius = forcePushPullRadius [ self - > client - > ps . forcePowerLevel [ FP_PUSH ] ] ;
}
if ( ! radius )
{ //no ability to do this yet
return ;
}
if ( pull )
{
cost = forcePowerNeeded [ FP_PULL ] ;
if ( ! WP_ForcePowerUsable ( self , FP_PULL , cost ) )
{
return ;
}
//make sure this plays and that you cannot press fire for about 200ms after this
anim = BOTH_FORCEPULL ;
soundIndex = G_SoundIndex ( " sound/weapons/force/pull.wav " ) ;
hold = 200 ;
}
else
{
cost = forcePowerNeeded [ FP_PUSH ] ;
if ( ! WP_ForcePowerUsable ( self , FP_PUSH , cost ) )
{
return ;
}
//make sure this plays and that you cannot press fire for about 1 second after this
anim = BOTH_FORCEPUSH ;
soundIndex = G_SoundIndex ( " sound/weapons/force/push.wav " ) ;
hold = 650 ;
}
int parts = SETANIM_TORSO ;
if ( ! PM_InKnockDown ( & self - > client - > ps ) )
{
if ( self - > client - > ps . saberLockTime > level . time )
{
self - > client - > ps . saberLockTime = 0 ;
self - > painDebounceTime = level . time + 2000 ;
hold + = 1000 ;
parts = SETANIM_BOTH ;
}
else if ( ! VectorLengthSquared ( self - > client - > ps . velocity ) & & ! ( self - > client - > ps . pm_flags & PMF_DUCKED ) )
{
parts = SETANIM_BOTH ;
}
}
NPC_SetAnim ( self , parts , anim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_RESTART ) ;
self - > client - > ps . saberMove = self - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
self - > client - > ps . saberBlocked = BLOCKED_NONE ;
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
hold = floor ( hold * g_timescale - > value ) ;
}
self - > client - > ps . weaponTime = hold ; //was 1000, but want to swing sooner
//do effect... FIXME: build-up or delay this until in proper part of anim
self - > client - > ps . powerups [ PW_FORCE_PUSH ] = level . time + self - > client - > ps . torsoAnimTimer + 500 ;
G_Sound ( self , soundIndex ) ;
2022-10-15 13:53:53 +00:00
vec3_t origin , angles ;
2022-11-07 23:57:10 +00:00
if ( self - > client - > ps . clientNum = = 0 & & ! cg . renderingThirdPerson & & ! aimByViewAngles )
2022-10-12 17:00:26 +00:00
{
BG_CalculateVROffHandPosition ( origin , fwdangles ) ;
2022-11-07 23:57:10 +00:00
AngleVectors ( fwdangles , forward , right , NULL ) ;
VectorCopy ( origin , center ) ;
2022-10-12 17:00:26 +00:00
}
else
{
VectorCopy ( self - > client - > ps . viewangles , fwdangles ) ;
2022-10-15 13:53:53 +00:00
VectorCopy ( self - > client - > renderInfo . eyePoint , origin ) ;
2022-10-28 21:51:32 +00:00
AngleVectors ( fwdangles , forward , right , NULL ) ;
VectorCopy ( self - > currentOrigin , center ) ;
2022-10-12 17:00:26 +00:00
}
2022-10-15 13:53:53 +00:00
2022-09-18 15:37:21 +00:00
//fwdangles[1] = self->client->ps.viewangles[1];
for ( i = 0 ; i < 3 ; i + + )
{
mins [ i ] = center [ i ] - radius ;
maxs [ i ] = center [ i ] + radius ;
}
numListedEntities = gi . EntitiesInBox ( mins , maxs , entityList , MAX_GENTITIES ) ;
if ( pull )
{
cone = forcePullCone [ self - > client - > ps . forcePowerLevel [ FP_PULL ] ] ;
}
else
{
cone = forcePushCone [ self - > client - > ps . forcePowerLevel [ FP_PUSH ] ] ;
}
if ( cone > = 1.0f )
{ //must be pointing right at them
2022-10-15 13:53:53 +00:00
VectorMA ( origin , radius , forward , end ) ;
gi . trace ( & tr , origin , vec3_origin , vec3_origin , end , self - > s . number , MASK_OPAQUE | CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_ITEM | CONTENTS_CORPSE , G2_NOCOLLIDE , 0 ) ; //was MASK_SHOT, changed to match crosshair trace
2022-09-18 15:37:21 +00:00
/*
//FIXME: can't just return, need to be able to push missiles
if ( tr . entityNum > = ENTITYNUM_WORLD )
{ //no-one right in front of self, so short out
return ;
}
*/
forwardEnt = & g_entities [ tr . entityNum ] ;
}
for ( e = 0 ; e < numListedEntities ; e + + )
{
ent = entityList [ e ] ;
if ( ent = = self )
continue ;
if ( ent - > owner = = self & & ent - > s . weapon ! = WP_THERMAL ) //can push your own thermals
continue ;
if ( ! ( ent - > inuse ) )
continue ;
if ( ent - > NPC & & ent - > NPC - > scriptFlags & SCF_NO_FORCE )
{
if ( ent - > s . weapon = = WP_SABER )
{ //Hmm, should jedi do the resist behavior? If this is on, perhaps it's because of a cinematic?
WP_ResistForcePush ( ent , self , qtrue ) ;
}
continue ;
}
if ( ( ent - > flags & FL_FORCE_PULLABLE_ONLY ) & & ! pull )
{ //simple HACK: cannot force-push ammo rack items (because they may start in solid)
continue ;
}
//FIXME: don't push it if I already pushed it a little while ago
if ( ent - > s . eType ! = ET_MISSILE )
{
if ( cone > = 1.0f )
{ //must be pointing right at them
if ( ent ! = forwardEnt )
{ //must be the person I'm looking right at
if ( ent - > client & & ! pull
& & ent - > client - > ps . forceGripEntityNum = = self - > s . number
& & ( self - > s . eFlags & EF_FORCE_GRIPPED ) )
{ //this is the guy that's force-gripping me, use a wider cone regardless of force power level
}
else
{
continue ;
}
}
}
if ( ent - > s . eType ! = ET_ITEM & & ent - > e_ThinkFunc ! = thinkF_G_RunObject ) //|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items
{
//FIXME: need pushable objects
if ( ent - > s . eFlags & EF_NODRAW )
{
continue ;
}
if ( ! ent - > client )
{
if ( Q_stricmp ( " lightsaber " , ent - > classname ) ! = 0 )
{ //not a lightsaber
if ( ! ( ent - > svFlags & SVF_GLASS_BRUSH ) )
{ //and not glass
if ( Q_stricmp ( " func_door " , ent - > classname ) ! = 0 | | ! ( ent - > spawnflags & 2 /*MOVER_FORCE_ACTIVATE*/ ) )
{ //not a force-usable door
if ( Q_stricmp ( " func_static " , ent - > classname ) ! = 0 | | ( ! ( ent - > spawnflags & 1 /*F_PUSH*/ ) & & ! ( ent - > spawnflags & 2 /*F_PULL*/ ) ) )
{ //not a force-usable func_static
if ( Q_stricmp ( " limb " , ent - > classname ) )
{ //not a limb
if ( ent - > s . weapon = = WP_TURRET & & ! Q_stricmp ( " PAS " , ent - > classname ) & & ent - > s . apos . trType = = TR_STATIONARY )
{ //can knock over placed turrets
if ( ! self - > s . number | | self - > enemy ! = ent )
{ //only NPCs who are actively mad at this turret can push it over
continue ;
}
}
else
{
continue ;
}
}
}
}
else if ( ent - > moverState ! = MOVER_POS1 & & ent - > moverState ! = MOVER_POS2 )
{ //not at rest
continue ;
}
}
}
//continue;
}
else if ( ent - > client - > NPC_class = = CLASS_MARK1 )
{ //can't push Mark1 unless push 3
if ( pull | | self - > client - > ps . forcePowerLevel [ FP_PUSH ] < FORCE_LEVEL_3 )
{
continue ;
}
}
else if ( ent - > client - > NPC_class = = CLASS_GALAKMECH | | ent - > client - > NPC_class = = CLASS_ATST )
{ //can't push ATST or Galak
continue ;
}
else if ( ent - > s . weapon = = WP_EMPLACED_GUN )
{ //FIXME: maybe can pull them out?
continue ;
}
else if ( ent - > client - > playerTeam = = self - > client - > playerTeam & & self - > enemy & & self - > enemy ! = ent )
{ //can't accidently push a teammate while in combat
continue ;
}
}
else if ( ent - > s . eType = = ET_ITEM
& & ent - > item
& & ent - > item - > giType = = IT_HOLDABLE
& & ent - > item - > giTag = = INV_SECURITY_KEY )
//&& (ent->flags&FL_DROPPED_ITEM) ???
{ //dropped security keys can't be pushed? But placed ones can...? does this make any sense?
if ( ! pull | | self - > s . number )
{ //can't push, NPC's can't do anything to it
continue ;
}
else
{
if ( g_crosshairEntNum ! = ent - > s . number )
{ //player can pull it if looking *right* at it
if ( cone > = 1.0f )
{ //we did a forwardEnt trace
if ( forwardEnt ! = ent )
{ //must be pointing right at them
continue ;
}
}
else
{ //do a forwardEnt trace
2022-10-15 13:53:53 +00:00
VectorMA ( origin , radius , forward , end ) ;
gi . trace ( & tr , origin , vec3_origin , vec3_origin , end , self - > s . number , MASK_OPAQUE | CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_ITEM | CONTENTS_CORPSE , G2_NOCOLLIDE , 0 ) ; //was MASK_SHOT, changed to match crosshair trace
2022-09-18 15:37:21 +00:00
if ( tr . entityNum ! = ent - > s . number )
{ //last chance
continue ;
}
}
}
}
}
}
else
{
if ( ent - > s . pos . trType = = TR_STATIONARY & & ( ent - > s . eFlags & EF_MISSILE_STICK ) )
{ //can't force-push/pull stuck missiles (detpacks, tripmines)
continue ;
}
if ( ent - > s . pos . trType = = TR_STATIONARY & & ent - > s . weapon ! = WP_THERMAL )
{ //only thermal detonators can be pushed once stopped
continue ;
}
}
//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
// find the distance from the edge of the bounding box
for ( i = 0 ; i < 3 ; i + + )
{
if ( center [ i ] < ent - > absmin [ i ] )
{
v [ i ] = ent - > absmin [ i ] - center [ i ] ;
} else if ( center [ i ] > ent - > absmax [ i ] )
{
v [ i ] = center [ i ] - ent - > absmax [ i ] ;
} else
{
v [ i ] = 0 ;
}
}
VectorSubtract ( ent - > absmax , ent - > absmin , size ) ;
VectorMA ( ent - > absmin , 0.5 , size , ent_org ) ;
//see if they're in front of me
VectorSubtract ( ent_org , center , dir ) ;
VectorNormalize ( dir ) ;
if ( cone < 1.0f )
{ //must be within the forward cone
if ( ent - > client & & ! pull
& & ent - > client - > ps . forceGripEntityNum = = self - > s . number
& & self - > s . eFlags & EF_FORCE_GRIPPED )
{ //this is the guy that's force-gripping me, use a wider cone regardless of force power level
if ( ( dot1 = DotProduct ( dir , forward ) ) < cone - 0.3f )
continue ;
}
else if ( ent - > s . eType = = ET_MISSILE ) //&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )
{ //missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects)
if ( ( dot1 = DotProduct ( dir , forward ) ) < cone - 0.3f )
continue ;
}
else if ( ( dot1 = DotProduct ( dir , forward ) ) < cone )
{
continue ;
}
}
else if ( ent - > s . eType = = ET_MISSILE )
{ //a missile and we're at force level 1... just use a small cone, but not ridiculously small
if ( ( dot1 = DotProduct ( dir , forward ) ) < 0.75f )
{
continue ;
}
} //else is an NPC or brush entity that our forward trace would have to hit
dist = VectorLength ( v ) ;
//Now check and see if we can actually deflect it
//method1
//if within a certain range, deflect it
if ( ent - > s . eType = = ET_MISSILE & & cone > = 1.0f )
{ //smaller radius on missile checks at force push 1
if ( dist > = 192 )
{
continue ;
}
}
else if ( dist > = radius )
{
continue ;
}
//in PVS?
2022-10-15 13:53:53 +00:00
if ( ! ent - > bmodel & & ! gi . inPVS ( ent_org , origin ) )
2022-09-18 15:37:21 +00:00
{ //must be in PVS
continue ;
}
if ( ent ! = forwardEnt )
{ //don't need to trace against forwardEnt again
//really should have a clear LOS to this thing...
2022-10-15 13:53:53 +00:00
gi . trace ( & tr , origin , vec3_origin , vec3_origin , ent_org , self - > s . number , MASK_OPAQUE | CONTENTS_SOLID , G2_NOCOLLIDE , 0 ) ; //was MASK_SHOT, but changed to match above trace and crosshair trace
2022-09-18 15:37:21 +00:00
if ( tr . fraction < 1.0f & & tr . entityNum ! = ent - > s . number )
{ //must have clear LOS
continue ;
}
}
// ok, we are within the radius, add us to the incoming list
push_list [ ent_count ] = ent ;
ent_count + + ;
}
if ( ent_count )
{
for ( int x = 0 ; x < ent_count ; x + + )
{
if ( push_list [ x ] - > client )
{
vec3_t pushDir ;
float knockback = pull ? 0 : 200 ;
//FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!!
//First, if this is the player we're push/pulling, see if he can counter it
if ( ! push_list [ x ] - > s . number )
{ //player
if ( push_list [ x ] - > health > 0 //alive
& & push_list [ x ] - > client //client
& & push_list [ x ] - > client - > ps . torsoAnim ! = BOTH_FORCEGRIP_HOLD // BOTH_FORCEGRIP1//wasn't trying to grip anyone
& & ( self - > client - > NPC_class ! = CLASS_DESANN | | ! Q_irand ( 0 , 2 ) ) //only 30% chance of resisting a Desann push
& & push_list [ x ] - > client - > ps . groundEntityNum ! = ENTITYNUM_NONE //on the ground
& & ! PM_InKnockDown ( & push_list [ x ] - > client - > ps ) //not knocked down already
& & push_list [ x ] - > client - > ps . saberLockTime < level . time //not involved in a saberLock
& & push_list [ x ] - > client - > ps . weaponTime < level . time //not attacking or otherwise busy
& & ( push_list [ x ] - > client - > ps . weapon = = WP_SABER | | push_list [ x ] - > client - > ps . weapon = = WP_MELEE ) ) //using saber or fists
{ //trying to push or pull the player!
if ( push_list [ x ] - > client - > ps . powerups [ PW_FORCE_PUSH ] > level . time //player was pushing/pulling too
| | ( pull & & Q_irand ( 0 , ( push_list [ x ] - > client - > ps . forcePowerLevel [ FP_PULL ] - self - > client - > ps . forcePowerLevel [ FP_PULL ] ) * 2 + 1 ) > 0 ) //player's pull is high enough
| | ( ! pull & & Q_irand ( 0 , ( push_list [ x ] - > client - > ps . forcePowerLevel [ FP_PUSH ] - self - > client - > ps . forcePowerLevel [ FP_PUSH ] ) * 2 + 1 ) > 0 ) ) //player's push is high enough
{ //player's force push/pull is high enough to try to stop me
if ( InFront ( self - > currentOrigin , push_list [ x ] - > client - > renderInfo . eyePoint , push_list [ x ] - > client - > ps . viewangles , 0.3f ) )
{ //I'm in front of player
WP_ResistForcePush ( push_list [ x ] , self , qfalse ) ;
push_list [ x ] - > client - > ps . saberMove = push_list [ x ] - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
push_list [ x ] - > client - > ps . saberBlocked = BLOCKED_NONE ;
continue ;
}
}
}
}
else if ( push_list [ x ] - > client & & Jedi_WaitingAmbush ( push_list [ x ] ) )
{
WP_ForceKnockdown ( push_list [ x ] , self , pull , qtrue , qfalse ) ;
continue ;
}
//okay, everyone else (or player who couldn't resist it)...
if ( ( ( self - > s . number = = 0 & & Q_irand ( 0 , 2 ) ) | | Q_irand ( 0 , 2 ) ) & & push_list [ x ] - > client & & push_list [ x ] - > health > 0 //a living client
& & push_list [ x ] - > client - > ps . weapon = = WP_SABER //Jedi
& & push_list [ x ] - > health > 0 //alive
& & ( self - > client - > NPC_class ! = CLASS_DESANN | | ! Q_irand ( 0 , 2 ) ) //only 30% chance of resisting a Desann push
& & push_list [ x ] - > client - > ps . groundEntityNum ! = ENTITYNUM_NONE //on the ground
& & InFront ( self - > currentOrigin , push_list [ x ] - > currentOrigin , push_list [ x ] - > client - > ps . viewangles , 0.3f ) //I'm in front of him
& & ( push_list [ x ] - > client - > ps . powerups [ PW_FORCE_PUSH ] > level . time | | //he's pushing too
( push_list [ x ] - > s . number ! = 0 & & push_list [ x ] - > client - > ps . weaponTime < level . time ) //not the player and not attacking (NPC jedi auto-defend against pushes)
)
)
{ //Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground
if ( push_list [ x ] - > client - > ps . saberLockTime > level . time )
{ //they're in a lock
if ( push_list [ x ] - > client - > ps . saberLockEnemy ! = self - > s . number )
{ //they're not in a lock with me
continue ;
}
else if ( pull | | self - > client - > ps . forcePowerLevel [ FP_PUSH ] < FORCE_LEVEL_3 | |
push_list [ x ] - > client - > ps . forcePowerLevel [ FP_PUSH ] > FORCE_LEVEL_2 )
{ //they're in a lock with me, but my push is too weak
continue ;
}
else
{ //we will knock them down
self - > painDebounceTime = 0 ;
self - > client - > ps . weaponTime = 500 ;
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
self - > client - > ps . weaponTime = floor ( self - > client - > ps . weaponTime * g_timescale - > value ) ;
}
}
}
if ( ! pull & & self - > client - > ps . forcePowerLevel [ FP_PUSH ] > FORCE_LEVEL_2 & & ! Q_irand ( 0 , 2 ) & &
push_list [ x ] - > client - > ps . forcePowerLevel [ FP_PUSH ] < FORCE_LEVEL_3 )
{ //a level 3 push can even knock down a jedi
if ( PM_InKnockDown ( & push_list [ x ] - > client - > ps ) )
{ //can't knock them down again
continue ;
}
WP_ForceKnockdown ( push_list [ x ] , self , pull , qfalse , qtrue ) ;
}
else
{
WP_ResistForcePush ( push_list [ x ] , self , qfalse ) ;
}
}
else
{
//UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)!
//shove them
if ( push_list [ x ] - > s . number & & push_list [ x ] - > message )
{ //an NPC who has a key
//don't push me... FIXME: maybe can pull the key off me?
WP_ForceKnockdown ( push_list [ x ] , self , pull , qfalse , qfalse ) ;
continue ;
}
if ( pull )
{
VectorSubtract ( self - > currentOrigin , push_list [ x ] - > currentOrigin , pushDir ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_PULL ] > FORCE_LEVEL_1
& & push_list [ x ] - > s . weapon ! = WP_SABER
& & push_list [ x ] - > s . weapon ! = WP_MELEE
& & push_list [ x ] - > s . weapon ! = WP_THERMAL )
{ //yank the weapon - NOTE: level 1 just knocks them down, not take weapon
//FIXME: weapon yank anim if not a knockdown?
if ( InFront ( self - > currentOrigin , push_list [ x ] - > currentOrigin , push_list [ x ] - > client - > ps . viewangles , 0.0f ) )
{ //enemy has to be facing me, too...
WP_DropWeapon ( push_list [ x ] , pushDir ) ;
}
}
knockback + = VectorNormalize ( pushDir ) ;
if ( knockback > 200 )
{
knockback = 200 ;
}
if ( self - > client - > ps . forcePowerLevel [ FP_PULL ] < FORCE_LEVEL_3 )
{ //maybe just knock them down
knockback / = 3 ;
}
}
else
{
VectorSubtract ( push_list [ x ] - > currentOrigin , self - > currentOrigin , pushDir ) ;
knockback - = VectorNormalize ( pushDir ) ;
if ( knockback < 100 )
{
knockback = 100 ;
}
//scale for push level
if ( self - > client - > ps . forcePowerLevel [ FP_PUSH ] < FORCE_LEVEL_2 )
{ //maybe just knock them down
knockback / = 3 ;
}
else if ( self - > client - > ps . forcePowerLevel [ FP_PUSH ] > FORCE_LEVEL_2 )
{ //super-hard push
//Hmm, maybe in this case can even nudge/knockdown a jedi? Especially if close?
//knockback *= 5;
}
}
//actually push/pull the enemy
G_Throw ( push_list [ x ] , pushDir , knockback ) ;
//make it so they don't actually hurt me when pulled at me...
push_list [ x ] - > forcePuller = self - > s . number ;
if ( push_list [ x ] - > client - > ps . velocity [ 2 ] < knockback )
{
push_list [ x ] - > client - > ps . velocity [ 2 ] = knockback ;
}
if ( push_list [ x ] - > health > 0 )
{ //target is still alive
if ( ( push_list [ x ] - > s . number | | ( cg . renderingThirdPerson & & ! cg . zoomMode ) ) //NPC or 3rd person player
& & ( ( ! pull & & self - > client - > ps . forcePowerLevel [ FP_PUSH ] < FORCE_LEVEL_2 & & push_list [ x ] - > client - > ps . forcePowerLevel [ FP_PUSH ] < FORCE_LEVEL_1 ) //level 1 push
| | ( pull & & self - > client - > ps . forcePowerLevel [ FP_PULL ] < FORCE_LEVEL_2 & & push_list [ x ] - > client - > ps . forcePowerLevel [ FP_PULL ] < FORCE_LEVEL_1 ) ) ) //level 1 pull
{ //NPC or third person player (without force push/pull skill), and force push/pull level is at 1
WP_ForceKnockdown (
push_list [ x ] , self , pull ,
( qboolean ) ( ! pull & & knockback > 150 ) , qfalse ) ;
}
else if ( ! push_list [ x ] - > s . number )
{ //player, have to force an anim on him
WP_ForceKnockdown (
push_list [ x ] , self , pull ,
( qboolean ) ( ! pull & & knockback > 150 ) , qfalse ) ;
}
else
{ //NPC and force-push/pull at level 2 or higher
WP_ForceKnockdown (
push_list [ x ] , self , pull ,
( qboolean ) ( ! pull & & knockback > 100 ) , qfalse ) ;
}
}
push_list [ x ] - > forcePushTime = level . time + 600 ; // let the push effect last for 600 ms
}
}
else if ( push_list [ x ] - > s . weapon = = WP_SABER & & ( push_list [ x ] - > contents & CONTENTS_LIGHTSABER ) )
{ //a thrown saber, just send it back
/*
if ( pull )
{ //steal it?
}
else */ if ( push_list [ x ] - > owner & & push_list [ x ] - > owner - > client & & push_list [ x ] - > owner - > client - > ps . saberActive & & push_list [ x ] - > s . pos . trType = = TR_LINEAR & & push_list [ x ] - > owner - > client - > ps . saberEntityState ! = SES_RETURNING )
{ //it's on and being controlled
//FIXME: prevent it from damaging me?
if ( self - > s . number = = 0 | | Q_irand ( 0 , 2 ) )
{ //certain chance of throwing it aside and turning it off?
//give it some velocity away from me
//FIXME: maybe actually push or pull it?
if ( Q_irand ( 0 , 1 ) )
{
VectorScale ( right , - 1 , right ) ;
}
G_ReflectMissile ( self , push_list [ x ] , right ) ;
//FIXME: isn't turning off!!!
WP_SaberDrop ( push_list [ x ] - > owner , push_list [ x ] ) ;
}
else
{
WP_SaberReturn ( push_list [ x ] - > owner , push_list [ x ] ) ;
}
//different effect?
}
}
else if ( push_list [ x ] - > s . eType = = ET_MISSILE
& & push_list [ x ] - > s . pos . trType ! = TR_STATIONARY
& & ( push_list [ x ] - > s . pos . trType ! = TR_INTERPOLATE | | push_list [ x ] - > s . weapon ! = WP_THERMAL ) ) //rolling and stationary thermal detonators are dealt with below
{
vec3_t dir2Me ;
VectorSubtract ( self - > currentOrigin , push_list [ x ] - > currentOrigin , dir2Me ) ;
float dot = DotProduct ( push_list [ x ] - > s . pos . trDelta , dir2Me ) ;
if ( pull )
{ //deflect rather than reflect?
}
else
{
if ( push_list [ x ] - > s . eFlags & EF_MISSILE_STICK )
{ //caught a sticky in-air
push_list [ x ] - > s . eType = ET_MISSILE ;
push_list [ x ] - > s . eFlags & = ~ EF_MISSILE_STICK ;
push_list [ x ] - > s . eFlags | = EF_BOUNCE_HALF ;
push_list [ x ] - > splashDamage / = 3 ;
push_list [ x ] - > splashRadius / = 3 ;
push_list [ x ] - > e_ThinkFunc = thinkF_WP_Explode ;
push_list [ x ] - > nextthink = level . time + Q_irand ( 500 , 3000 ) ;
}
if ( dot > = 0 )
{ //it's heading towards me
G_ReflectMissile ( self , push_list [ x ] , forward ) ;
}
else
{
VectorScale ( push_list [ x ] - > s . pos . trDelta , 1.25f , push_list [ x ] - > s . pos . trDelta ) ;
}
//deflect sound
//G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) );
//push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
}
}
else if ( push_list [ x ] - > svFlags & SVF_GLASS_BRUSH )
{ //break the glass
trace_t tr ;
vec3_t pushDir ;
float damage = 800 ;
VectorNormalize ( forward ) ;
2022-10-15 13:53:53 +00:00
VectorMA ( origin , radius , forward , end ) ;
gi . trace ( & tr , origin , vec3_origin , vec3_origin , end , self - > s . number , MASK_SHOT , G2_NOCOLLIDE , 0 ) ;
2022-09-18 15:37:21 +00:00
if ( tr . entityNum ! = push_list [ x ] - > s . number | | tr . fraction = = 1.0 | | tr . allsolid | | tr . startsolid )
{ //must be pointing right at it
continue ;
}
if ( pull )
{
2022-10-15 13:53:53 +00:00
VectorSubtract ( origin , tr . endpos , pushDir ) ;
2022-09-18 15:37:21 +00:00
}
else
{
2022-10-15 13:53:53 +00:00
VectorSubtract ( tr . endpos , origin , pushDir ) ;
2022-09-18 15:37:21 +00:00
}
/*
VectorSubtract ( push_list [ x ] - > absmax , push_list [ x ] - > absmin , size ) ;
VectorMA ( push_list [ x ] - > absmin , 0.5 , size , center ) ;
if ( pull )
{
2022-10-15 13:53:53 +00:00
VectorSubtract ( origin , center , pushDir ) ;
2022-09-18 15:37:21 +00:00
}
else
{
2022-10-15 13:53:53 +00:00
VectorSubtract ( center , origin , pushDir ) ;
2022-09-18 15:37:21 +00:00
}
*/
damage - = VectorNormalize ( pushDir ) ;
if ( damage < 200 )
{
damage = 200 ;
}
VectorScale ( pushDir , damage , pushDir ) ;
G_Damage ( push_list [ x ] , self , self , pushDir , tr . endpos , damage , 0 , MOD_UNKNOWN ) ;
}
else if ( ! Q_stricmp ( " func_static " , push_list [ x ] - > classname ) )
{ //force-usable func_static
if ( ! pull & & ( push_list [ x ] - > spawnflags & 1 /*F_PUSH*/ ) )
{
GEntity_UseFunc ( push_list [ x ] , self , self ) ;
}
else if ( pull & & ( push_list [ x ] - > spawnflags & 2 /*F_PULL*/ ) )
{
GEntity_UseFunc ( push_list [ x ] , self , self ) ;
}
}
else if ( ! Q_stricmp ( " func_door " , push_list [ x ] - > classname ) & & ( push_list [ x ] - > spawnflags & 2 /*MOVER_FORCE_ACTIVATE*/ ) )
{ //push/pull the door
vec3_t pos1 , pos2 ;
VectorNormalize ( forward ) ;
2022-10-15 13:53:53 +00:00
VectorMA ( origin , radius , forward , end ) ;
gi . trace ( & tr , origin , vec3_origin , vec3_origin , end , self - > s . number , MASK_SHOT , G2_NOCOLLIDE , 0 ) ;
2022-09-18 15:37:21 +00:00
if ( tr . entityNum ! = push_list [ x ] - > s . number | | tr . fraction = = 1.0 | | tr . allsolid | | tr . startsolid )
{ //must be pointing right at it
continue ;
}
if ( VectorCompare ( vec3_origin , push_list [ x ] - > s . origin ) )
{ //does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
VectorSubtract ( push_list [ x ] - > absmax , push_list [ x ] - > absmin , size ) ;
VectorMA ( push_list [ x ] - > absmin , 0.5 , size , center ) ;
if ( ( push_list [ x ] - > spawnflags & 1 ) & & push_list [ x ] - > moverState = = MOVER_POS1 )
{ //if at pos1 and started open, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
VectorSubtract ( center , push_list [ x ] - > pos1 , center ) ;
}
else if ( ! ( push_list [ x ] - > spawnflags & 1 ) & & push_list [ x ] - > moverState = = MOVER_POS2 )
{ //if at pos2, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
VectorSubtract ( center , push_list [ x ] - > pos2 , center ) ;
}
VectorAdd ( center , push_list [ x ] - > pos1 , pos1 ) ;
VectorAdd ( center , push_list [ x ] - > pos2 , pos2 ) ;
}
else
{ //actually has an origin, pos1 and pos2 are absolute
VectorCopy ( push_list [ x ] - > currentOrigin , center ) ;
VectorCopy ( push_list [ x ] - > pos1 , pos1 ) ;
VectorCopy ( push_list [ x ] - > pos2 , pos2 ) ;
}
2022-10-15 13:53:53 +00:00
if ( Distance ( pos1 , origin ) < Distance ( pos2 , origin ) )
2022-09-18 15:37:21 +00:00
{ //pos1 is closer
if ( push_list [ x ] - > moverState = = MOVER_POS1 )
{ //at the closest pos
if ( pull )
{ //trying to pull, but already at closest point, so screw it
continue ;
}
}
else if ( push_list [ x ] - > moverState = = MOVER_POS2 )
{ //at farthest pos
if ( ! pull )
{ //trying to push, but already at farthest point, so screw it
continue ;
}
}
}
else
{ //pos2 is closer
if ( push_list [ x ] - > moverState = = MOVER_POS1 )
{ //at the farthest pos
if ( ! pull )
{ //trying to push, but already at farthest point, so screw it
continue ;
}
}
else if ( push_list [ x ] - > moverState = = MOVER_POS2 )
{ //at closest pos
if ( pull )
{ //trying to pull, but already at closest point, so screw it
continue ;
}
}
}
GEntity_UseFunc ( push_list [ x ] , self , self ) ;
}
else if ( push_list [ x ] - > s . eType = = ET_MISSILE /*thermal resting on ground*/
| | push_list [ x ] - > s . eType = = ET_ITEM
| | push_list [ x ] - > e_ThinkFunc = = thinkF_G_RunObject | | Q_stricmp ( " limb " , push_list [ x ] - > classname ) = = 0 )
{ //general object, toss it
vec3_t pushDir , kvel ;
float knockback = pull ? 0 : 200 ;
float mass = 200 ;
if ( pull )
{
if ( push_list [ x ] - > s . eType = = ET_ITEM )
{ //pull it to a little higher point
vec3_t adjustedOrg ;
VectorCopy ( self - > currentOrigin , adjustedOrg ) ;
adjustedOrg [ 2 ] + = self - > maxs [ 2 ] / 3 ;
VectorSubtract ( adjustedOrg , push_list [ x ] - > currentOrigin , pushDir ) ;
}
else
{
VectorSubtract ( self - > currentOrigin , push_list [ x ] - > currentOrigin , pushDir ) ;
}
knockback + = VectorNormalize ( pushDir ) ;
if ( knockback > 200 )
{
knockback = 200 ;
}
if ( push_list [ x ] - > s . eType = = ET_ITEM
& & push_list [ x ] - > item
& & push_list [ x ] - > item - > giType = = IT_HOLDABLE
& & push_list [ x ] - > item - > giTag = = INV_SECURITY_KEY )
{ //security keys are pulled with less enthusiasm
if ( knockback > 100 )
{
knockback = 100 ;
}
}
else if ( knockback > 200 )
{
knockback = 200 ;
}
}
else
{
//HMM, if I have an auto-enemy & he's in front of me, push it toward him?
VectorSubtract ( push_list [ x ] - > currentOrigin , self - > currentOrigin , pushDir ) ;
knockback - = VectorNormalize ( pushDir ) ;
if ( knockback < 100 )
{
knockback = 100 ;
}
}
//FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid? or check?
VectorCopy ( push_list [ x ] - > currentOrigin , push_list [ x ] - > s . pos . trBase ) ;
push_list [ x ] - > s . pos . trTime = level . time ; // move a bit on the very first frame
if ( push_list [ x ] - > s . pos . trType ! = TR_INTERPOLATE )
{ //don't do this to rolling missiles
push_list [ x ] - > s . pos . trType = TR_GRAVITY ;
}
if ( push_list [ x ] - > e_ThinkFunc = = thinkF_G_RunObject & & push_list [ x ] - > physicsBounce )
{
mass = push_list [ x ] - > physicsBounce ;
}
if ( mass < 50 )
{ //???
mass = 50 ;
}
if ( g_gravity - > value > 0 )
{
VectorScale ( pushDir , g_knockback - > value * knockback / mass * 0.8 , kvel ) ;
kvel [ 2 ] = pushDir [ 2 ] * g_knockback - > value * knockback / mass * 1.5 ;
}
else
{
VectorScale ( pushDir , g_knockback - > value * knockback / mass , kvel ) ;
}
VectorAdd ( push_list [ x ] - > s . pos . trDelta , kvel , push_list [ x ] - > s . pos . trDelta ) ;
if ( g_gravity - > value > 0 )
{
if ( push_list [ x ] - > s . pos . trDelta [ 2 ] < knockback )
{
push_list [ x ] - > s . pos . trDelta [ 2 ] = knockback ;
}
}
//no trDuration?
if ( push_list [ x ] - > e_ThinkFunc ! = thinkF_G_RunObject )
{ //objects spin themselves?
//spin it
//FIXME: messing with roll ruins the rotational center???
push_list [ x ] - > s . apos . trTime = level . time ;
push_list [ x ] - > s . apos . trType = TR_LINEAR ;
VectorClear ( push_list [ x ] - > s . apos . trDelta ) ;
push_list [ x ] - > s . apos . trDelta [ 1 ] = Q_irand ( - 800 , 800 ) ;
}
if ( Q_stricmp ( " limb " , push_list [ x ] - > classname ) = = 0 )
{ //make sure it runs it's physics
push_list [ x ] - > e_ThinkFunc = thinkF_LimbThink ;
push_list [ x ] - > nextthink = level . time + FRAMETIME ;
}
push_list [ x ] - > forcePushTime = level . time + 600 ; // let the push effect last for 600 ms
if ( push_list [ x ] - > item & & push_list [ x ] - > item - > giTag = = INV_SECURITY_KEY )
{
AddSightEvent ( player , push_list [ x ] - > currentOrigin , 128 , AEL_DISCOVERED ) ; //security keys are more important
}
else
{
AddSightEvent ( player , push_list [ x ] - > currentOrigin , 128 , AEL_SUSPICIOUS ) ; //hmm... or should this always be discovered?
}
}
else if ( push_list [ x ] - > s . weapon = = WP_TURRET
& & ! Q_stricmp ( " PAS " , push_list [ x ] - > classname )
& & push_list [ x ] - > s . apos . trType = = TR_STATIONARY )
{ //a portable turret
WP_KnockdownTurret ( self , push_list [ x ] ) ;
}
}
if ( pull )
{
if ( self - > client - > ps . forcePowerLevel [ FP_PUSH ] > FORCE_LEVEL_2 )
{ //at level 3, can pull multiple, so it costs more
actualCost = forcePowerNeeded [ FP_PUSH ] * ent_count ;
if ( actualCost > 50 )
{
actualCost = 50 ;
}
else if ( actualCost < cost )
{
actualCost = cost ;
}
}
else
{
actualCost = cost ;
}
WP_ForcePowerStart ( self , FP_PULL , actualCost ) ;
}
else
{
if ( self - > client - > ps . forcePowerLevel [ FP_PUSH ] > FORCE_LEVEL_2 )
{ //at level 3, can push multiple, so costs more
actualCost = forcePowerNeeded [ FP_PUSH ] * ent_count ;
if ( actualCost > 50 )
{
actualCost = 50 ;
}
else if ( actualCost < cost )
{
actualCost = cost ;
}
}
else if ( self - > client - > ps . forcePowerLevel [ FP_PUSH ] > FORCE_LEVEL_1 )
{ //at level 3, can push multiple, so costs more
actualCost = floor ( forcePowerNeeded [ FP_PUSH ] * ent_count / 1.5f ) ;
if ( actualCost > 50 )
{
actualCost = 50 ;
}
else if ( actualCost < cost )
{
actualCost = cost ;
}
}
else
{
actualCost = cost ;
}
WP_ForcePowerStart ( self , FP_PUSH , actualCost ) ;
}
}
else
{ //didn't push or pull anything? don't penalize them too much
if ( pull )
{
WP_ForcePowerStart ( self , FP_PULL , 5 ) ;
}
else
{
WP_ForcePowerStart ( self , FP_PUSH , 5 ) ;
}
}
if ( self - > NPC )
{ //NPCs can push more often
//FIXME: vary by rank and game skill?
self - > client - > ps . forcePowerDebounce [ FP_PUSH ] = level . time + 200 ;
}
else
{
self - > client - > ps . forcePowerDebounce [ FP_PUSH ] = level . time + self - > client - > ps . torsoAnimTimer + 500 ;
}
}
void ForceSpeed ( gentity_t * self , int duration )
{
if ( self - > health < = 0 )
{
return ;
}
if ( ! WP_ForcePowerUsable ( self , FP_SPEED , 0 ) )
{
return ;
}
if ( self - > client - > ps . saberLockTime > level . time )
{ //FIXME: can this be a way to break out?
return ;
}
if ( ! self - > s . number & & in_camera )
{ //player can't use force powers in cinematic
return ;
}
WP_ForcePowerStart ( self , FP_SPEED , 0 ) ;
if ( duration )
{
self - > client - > ps . forcePowerDuration [ FP_SPEED ] = level . time + duration ;
}
G_Sound ( self , G_SoundIndex ( " sound/weapons/force/speed.wav " ) ) ;
}
void ForceHeal ( gentity_t * self )
{
if ( self - > health < = 0 | | self - > client - > ps . stats [ STAT_MAX_HEALTH ] < = self - > health )
{
return ;
}
if ( ! WP_ForcePowerUsable ( self , FP_HEAL , 20 ) )
{ //must have enough force power for at least 5 points of health
return ;
}
if ( self - > painDebounceTime > level . time | | ( self - > client - > ps . weaponTime & & self - > client - > ps . weapon ! = WP_NONE ) )
{ //can't initiate a heal while taking pain or attacking
return ;
}
if ( self - > client - > ps . saberLockTime > level . time )
{ //FIXME: can this be a way to break out?
return ;
}
if ( ! self - > s . number & & in_camera )
{ //player can't use force powers in cinematic
return ;
}
/*
if ( self - > client - > ps . forcePowerLevel [ FP_HEAL ] > FORCE_LEVEL_2 )
{ //instant heal
//no more than available force power
int max = self - > client - > ps . forcePower ;
if ( max > MAX_FORCE_HEAL )
{ //no more than max allowed
max = MAX_FORCE_HEAL ;
}
if ( max > self - > client - > ps . stats [ STAT_MAX_HEALTH ] - self - > health )
{ //no more than what's missing
max = self - > client - > ps . stats [ STAT_MAX_HEALTH ] - self - > health ;
}
self - > health + = max ;
WP_ForcePowerDrain ( self , FP_HEAL , max ) ;
G_SoundOnEnt ( self , CHAN_VOICE , va ( " sound/weapons/force/heal%d.mp3 " , Q_irand ( 1 , 4 ) ) ) ;
}
else
*/
{
//start health going up
//NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE );
WP_ForcePowerStart ( self , FP_HEAL , 0 ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_HEAL ] < FORCE_LEVEL_2 )
{ //must meditate
//FIXME: holster weapon (select WP_NONE?)
//FIXME: BOTH_FORCEHEAL_START
NPC_SetAnim ( self , SETANIM_BOTH , BOTH_FORCEHEAL_START , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
self - > client - > ps . saberMove = self - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
self - > client - > ps . saberBlocked = BLOCKED_NONE ;
self - > client - > ps . torsoAnimTimer = self - > client - > ps . legsAnimTimer = FORCE_HEAL_INTERVAL * MAX_FORCE_HEAL + 2000 ; //???
if ( self - > client - > ps . saberActive )
{
self - > client - > ps . saberActive = qfalse ; //turn off saber when meditating
if ( self - > client - > playerTeam = = TEAM_PLAYER )
{
G_SoundOnEnt ( self , CHAN_WEAPON , " sound/weapons/saber/saberoff.wav " ) ;
}
else
{
G_SoundOnEnt ( self , CHAN_WEAPON , " sound/weapons/saber/enemy_saber_off.wav " ) ;
}
}
}
else
{ //just a quick gesture
/*
//Can't get an anim that looks good...
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCEHEAL_QUICK , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
self - > client - > ps . saberMove = self - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
self - > client - > ps . saberBlocked = BLOCKED_NONE ;
*/
}
}
//FIXME: always play healing effect
G_SoundOnEnt ( self , CHAN_ITEM , " sound/weapons/force/heal.mp3 " ) ;
}
extern void NPC_PlayConfusionSound ( gentity_t * self ) ;
extern void NPC_Jedi_PlayConfusionSound ( gentity_t * self ) ;
qboolean WP_CheckBreakControl ( gentity_t * self )
{
if ( ! self )
{
return qfalse ;
}
if ( ! self - > s . number )
{ //player
if ( self - > client & & self - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] > FORCE_LEVEL_3 )
{ //control-level
if ( self - > client - > ps . viewEntity > 0 & & self - > client - > ps . viewEntity < ENTITYNUM_WORLD )
{ //we are in a viewentity
gentity_t * controlled = & g_entities [ self - > client - > ps . viewEntity ] ;
if ( controlled - > NPC & & controlled - > NPC - > controlledTime > level . time )
{ //it is an NPC we controlled
//clear it and return
G_ClearViewEntity ( self ) ;
return qtrue ;
}
}
}
}
else
{ //NPC
if ( self - > NPC & & self - > NPC - > controlledTime > level . time )
{ //being controlled
gentity_t * controller = & g_entities [ 0 ] ;
if ( controller - > client & & controller - > client - > ps . viewEntity = = self - > s . number )
{ //we are being controlled by player
if ( controller - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] > FORCE_LEVEL_3 )
{ //control-level mind trick
//clear the control and return
G_ClearViewEntity ( controller ) ;
return qtrue ;
}
}
}
}
return qfalse ;
}
void ForceTelepathy ( gentity_t * self )
{
trace_t tr ;
vec3_t end , forward ;
gentity_t * traceEnt ;
qboolean targetLive = qfalse ;
if ( WP_CheckBreakControl ( self ) )
{
return ;
}
if ( self - > health < = 0 )
{
return ;
}
//FIXME: if mind trick 3 and aiming at an enemy need more force power
if ( ! WP_ForcePowerUsable ( self , FP_TELEPATHY , 0 ) )
{
return ;
}
if ( self - > client - > ps . weaponTime > = 800 )
{ //just did one!
return ;
}
if ( self - > client - > ps . saberLockTime > level . time )
{ //FIXME: can this be a way to break out?
return ;
}
if ( ! self - > s . number & & in_camera )
{ //player can't use force powers in cinematic
return ;
}
2022-10-12 21:35:27 +00:00
vec3_t origin , angles ;
2022-10-17 19:29:58 +00:00
if ( self - > client - > ps . clientNum = = 0 & & ! cg . renderingThirdPerson )
2022-10-12 21:35:27 +00:00
{
BG_CalculateVROffHandPosition ( origin , angles ) ;
AngleVectors ( angles , forward , NULL , NULL ) ;
}
else
{
AngleVectors ( self - > client - > ps . viewangles , forward , NULL , NULL ) ;
VectorCopy ( self - > client - > ps . viewangles , angles ) ;
2022-10-15 13:53:53 +00:00
VectorCopy ( self - > client - > renderInfo . eyePoint , origin ) ;
2022-10-12 21:35:27 +00:00
}
2022-09-18 15:37:21 +00:00
VectorNormalize ( forward ) ;
2022-10-15 13:53:53 +00:00
VectorMA ( origin , 2048 , forward , end ) ;
2022-09-18 15:37:21 +00:00
//Cause a distraction if enemy is not fighting
2022-10-15 13:53:53 +00:00
gi . trace ( & tr , origin , vec3_origin , vec3_origin , end , self - > s . number , MASK_OPAQUE | CONTENTS_BODY , G2_NOCOLLIDE , 0 ) ;
2022-09-18 15:37:21 +00:00
if ( tr . entityNum = = ENTITYNUM_NONE | | tr . fraction = = 1.0 | | tr . allsolid | | tr . startsolid )
{
return ;
}
traceEnt = & g_entities [ tr . entityNum ] ;
if ( traceEnt - > NPC & & traceEnt - > NPC - > scriptFlags & SCF_NO_FORCE )
{
return ;
}
if ( traceEnt & & traceEnt - > client )
{
switch ( traceEnt - > client - > NPC_class )
{
case CLASS_GALAKMECH : //cant grip him, he's in armor
case CLASS_ATST : //much too big to grip!
//no droids either
case CLASS_PROBE :
case CLASS_GONK :
case CLASS_R2D2 :
case CLASS_R5D2 :
case CLASS_MARK1 :
case CLASS_MARK2 :
case CLASS_MOUSE :
case CLASS_SEEKER :
case CLASS_REMOTE :
case CLASS_PROTOCOL :
break ;
default :
targetLive = qtrue ;
break ;
}
}
if ( targetLive & & traceEnt - > NPC )
{ //hit an organic non-player
if ( G_ActivateBehavior ( traceEnt , BSET_MINDTRICK ) )
{ //activated a script on him
//FIXME: do the visual sparkles effect on their heads, still?
WP_ForcePowerStart ( self , FP_TELEPATHY , 0 ) ;
}
else if ( traceEnt - > client - > playerTeam ! = self - > client - > playerTeam )
{ //an enemy
int override = 0 ;
if ( ( traceEnt - > NPC - > scriptFlags & SCF_NO_MIND_TRICK ) )
{
if ( traceEnt - > client - > NPC_class = = CLASS_GALAKMECH )
{
G_AddVoiceEvent ( NPC , Q_irand ( EV_CONFUSE1 , EV_CONFUSE3 ) , Q_irand ( 3000 , 5000 ) ) ;
}
}
else if ( self - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] > FORCE_LEVEL_3 )
{ //control them, even jedi
G_SetViewEntity ( self , traceEnt ) ;
traceEnt - > NPC - > controlledTime = level . time + 30000 ;
}
else if ( traceEnt - > s . weapon ! = WP_SABER )
{ //haha! Jedi aren't easily confused!
if ( self - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] > FORCE_LEVEL_2 )
{ //turn them to our side
//if mind trick 3 and aiming at an enemy need more force power
override = 50 ;
if ( self - > client - > ps . forcePower < 50 )
{
return ;
}
if ( traceEnt - > s . weapon ! = WP_NONE )
{ //don't charm people who aren't capable of fighting... like ugnaughts and droids
if ( traceEnt - > enemy )
{
G_ClearEnemy ( traceEnt ) ;
}
if ( traceEnt - > NPC )
{
//traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
traceEnt - > client - > leader = self ;
}
//FIXME: maybe pick an enemy right here?
team_t saveTeam = traceEnt - > client - > enemyTeam ;
traceEnt - > client - > enemyTeam = traceEnt - > client - > playerTeam ;
traceEnt - > client - > playerTeam = saveTeam ;
//FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
traceEnt - > NPC - > charmedTime = level . time + mindTrickTime [ self - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] ] ;
}
}
else
{ //just confuse them
//somehow confuse them? Set don't fire to true for a while? Drop their aggression? Maybe just take their enemy away and don't let them pick one up for a while unless shot?
traceEnt - > NPC - > confusionTime = level . time + mindTrickTime [ self - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] ] ; //confused for about 10 seconds
NPC_PlayConfusionSound ( traceEnt ) ;
if ( traceEnt - > enemy )
{
G_ClearEnemy ( traceEnt ) ;
}
}
}
else
{
NPC_Jedi_PlayConfusionSound ( traceEnt ) ;
}
WP_ForcePowerStart ( self , FP_TELEPATHY , override ) ;
}
else if ( traceEnt - > client - > playerTeam = = self - > client - > playerTeam )
{ //an ally
//maybe just have him look at you? Respond? Take your enemy?
if ( traceEnt - > client - > ps . pm_type < PM_DEAD & & traceEnt - > NPC ! = NULL & & ! ( traceEnt - > NPC - > scriptFlags & SCF_NO_RESPONSE ) )
{
NPC_UseResponse ( traceEnt , self , qfalse ) ;
WP_ForcePowerStart ( self , FP_TELEPATHY , 1 ) ;
}
} //NOTE: no effect on TEAM_NEUTRAL?
vec3_t eyeDir ;
AngleVectors ( traceEnt - > client - > renderInfo . eyeAngles , eyeDir , NULL , NULL ) ;
VectorNormalize ( eyeDir ) ;
G_PlayEffect ( " force_touch " , traceEnt - > client - > renderInfo . eyePoint , eyeDir ) ;
//make sure this plays and that you cannot press fire for about 1 second after this
//FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_MINDTRICK1 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD ) ;
//FIXME: build-up or delay this until in proper part of anim
}
else
{
if ( self - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] > FORCE_LEVEL_1 & & tr . fraction * 2048 > 64 )
{ //don't create a diversion less than 64 from you of if at power level 1
//use distraction anim instead
G_PlayEffect ( G_EffectIndex ( " force_touch " ) , tr . endpos , tr . plane . normal ) ;
//FIXME: these events don't seem to always be picked up...?
AddSoundEvent ( self , tr . endpos , 512 , AEL_SUSPICIOUS , qtrue ) ;
AddSightEvent ( self , tr . endpos , 512 , AEL_SUSPICIOUS , 50 ) ;
WP_ForcePowerStart ( self , FP_TELEPATHY , 0 ) ;
}
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_MINDTRICK2 , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD ) ;
}
self - > client - > ps . saberMove = self - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
self - > client - > ps . saberBlocked = BLOCKED_NONE ;
self - > client - > ps . weaponTime = 1000 ;
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
self - > client - > ps . weaponTime = floor ( self - > client - > ps . weaponTime * g_timescale - > value ) ;
}
}
void ForceGrip ( gentity_t * self )
{ //FIXME: make enemy Jedi able to use this
trace_t tr ;
vec3_t end , forward ;
gentity_t * traceEnt = NULL ;
if ( self - > health < = 0 )
{
return ;
}
if ( ! self - > s . number & & ( cg . zoomMode | | in_camera ) )
{ //can't force grip when zoomed in or in cinematic
return ;
}
if ( self - > client - > ps . leanofs )
{ //can't force-grip while leaning
return ;
}
if ( self - > client - > ps . forceGripEntityNum < = ENTITYNUM_WORLD )
{ //already gripping
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 )
{
self - > client - > ps . forcePowerDuration [ FP_GRIP ] = level . time + 100 ;
self - > client - > ps . weaponTime = 1000 ;
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
self - > client - > ps . weaponTime = floor ( self - > client - > ps . weaponTime * g_timescale - > value ) ;
}
}
return ;
}
if ( ! WP_ForcePowerUsable ( self , FP_GRIP , 0 ) )
{ //can't use it right now
return ;
}
if ( self - > client - > ps . forcePower < 26 )
{ //need 20 to start, 6 to hold it for any decent amount of time...
return ;
}
if ( self - > client - > ps . weaponTime )
{ //busy
return ;
}
if ( self - > client - > ps . saberLockTime > level . time )
{ //FIXME: can this be a way to break out?
return ;
}
//Cause choking anim + health drain in ent in front of me
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCEGRIP_HOLD , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
self - > client - > ps . saberMove = self - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
self - > client - > ps . saberBlocked = BLOCKED_NONE ;
self - > client - > ps . weaponTime = 1000 ;
if ( self - > client - > ps . forcePowersActive & ( 1 < < FP_SPEED ) )
{
self - > client - > ps . weaponTime = floor ( self - > client - > ps . weaponTime * g_timescale - > value ) ;
}
2022-10-12 21:35:27 +00:00
vec3_t origin , angles ;
2022-10-17 19:29:58 +00:00
if ( self - > client - > ps . clientNum = = 0 & & ! cg . renderingThirdPerson )
2022-10-12 17:00:26 +00:00
{
BG_CalculateVROffHandPosition ( origin , angles ) ;
AngleVectors ( angles , forward , NULL , NULL ) ;
}
else
{
AngleVectors ( self - > client - > ps . viewangles , forward , NULL , NULL ) ;
2022-10-12 21:35:27 +00:00
VectorCopy ( self - > client - > ps . viewangles , angles ) ;
2022-10-15 13:53:53 +00:00
VectorCopy ( self - > client - > renderInfo . eyePoint , origin ) ;
2022-10-12 17:00:26 +00:00
}
2022-10-15 13:53:53 +00:00
2022-09-18 15:37:21 +00:00
VectorNormalize ( forward ) ;
VectorMA ( self - > client - > renderInfo . handLPoint , FORCE_GRIP_DIST , forward , end ) ;
2022-10-15 13:53:53 +00:00
if ( self - > enemy & & ( self - > s . number | | InFront ( self - > enemy - > currentOrigin , origin , angles , 0.2f ) ) )
2022-09-18 15:37:21 +00:00
{ //NPCs can always lift enemy since we assume they're looking at them, players need to be facing the enemy
2022-10-15 13:53:53 +00:00
if ( gi . inPVS ( self - > enemy - > currentOrigin , origin ) )
2022-09-18 15:37:21 +00:00
{ //must be in PVS
2022-10-15 13:53:53 +00:00
gi . trace ( & tr , origin , vec3_origin , vec3_origin , self - > enemy - > currentOrigin , self - > s . number , MASK_SHOT , G2_NOCOLLIDE , 0 ) ;
2022-09-18 15:37:21 +00:00
if ( tr . fraction = = 1.0f | | tr . entityNum = = self - > enemy - > s . number )
{ //must have clear LOS
traceEnt = self - > enemy ;
}
}
}
if ( ! traceEnt )
{ //okay, trace straight ahead and see what's there
gi . trace ( & tr , self - > client - > renderInfo . handLPoint , vec3_origin , vec3_origin , end , self - > s . number , MASK_SHOT , G2_NOCOLLIDE , 0 ) ;
if ( tr . entityNum > = ENTITYNUM_WORLD | | tr . fraction = = 1.0 | | tr . allsolid | | tr . startsolid )
{
return ;
}
traceEnt = & g_entities [ tr . entityNum ] ;
}
if ( ! traceEnt | | traceEnt = = self /*???*/ | | traceEnt - > bmodel | | ( traceEnt - > health < = 0 & & traceEnt - > takedamage ) | | ( traceEnt - > NPC & & traceEnt - > NPC - > scriptFlags & SCF_NO_FORCE ) )
{
return ;
}
if ( traceEnt - > client )
{
if ( traceEnt - > client - > ps . forceJumpZStart )
{ //can't catch them in mid force jump - FIXME: maybe base it on velocity?
return ;
}
switch ( traceEnt - > client - > NPC_class )
{
case CLASS_GALAKMECH : //cant grip him, he's in armor
G_AddVoiceEvent ( traceEnt , Q_irand ( EV_PUSHED1 , EV_PUSHED3 ) , Q_irand ( 3000 , 5000 ) ) ;
return ;
break ;
case CLASS_ATST : //much too big to grip!
return ;
//no droids either...?
case CLASS_GONK :
case CLASS_R2D2 :
case CLASS_R5D2 :
case CLASS_MARK1 :
case CLASS_MARK2 :
case CLASS_MOUSE : //?
case CLASS_PROTOCOL :
//*sigh*... in JK3, you'll be able to grab and move *anything*...
return ;
break ;
case CLASS_PROBE :
case CLASS_SEEKER :
case CLASS_REMOTE :
case CLASS_SENTRY :
case CLASS_INTERROGATOR :
//*sigh*... in JK3, you'll be able to grab and move *anything*...
return ;
break ;
case CLASS_DESANN : //Desann cannot be gripped, he just pushes you back instantly
Jedi_PlayDeflectSound ( traceEnt ) ;
ForceThrow ( traceEnt , qfalse ) ;
return ;
break ;
case CLASS_REBORN :
case CLASS_SHADOWTROOPER :
case CLASS_TAVION :
case CLASS_JEDI :
case CLASS_LUKE :
if ( traceEnt - > NPC & & traceEnt - > NPC - > rank > RANK_CIVILIAN & & self - > client - > ps . forcePowerLevel [ FP_GRIP ] < FORCE_LEVEL_2 )
{
Jedi_PlayDeflectSound ( traceEnt ) ;
ForceThrow ( traceEnt , qfalse ) ;
return ;
}
break ;
default :
break ;
}
if ( traceEnt - > s . weapon = = WP_EMPLACED_GUN )
{ //FIXME: maybe can pull them out?
return ;
}
if ( self - > enemy & & traceEnt ! = self - > enemy & & traceEnt - > client - > playerTeam = = self - > client - > playerTeam )
{ //can't accidently grip your teammate in combat
return ;
}
}
else
{ //can't grip non-clients... right?
return ;
}
WP_ForcePowerStart ( self , FP_GRIP , 20 ) ;
//FIXME: rule out other things?
//FIXME: Jedi resist, like the push and pull?
self - > client - > ps . forceGripEntityNum = traceEnt - > s . number ;
//if ( traceEnt->client )
{
G_AddVoiceEvent ( traceEnt , Q_irand ( EV_PUSHED1 , EV_PUSHED3 ) , 2000 ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_2 | | traceEnt - > s . weapon = = WP_SABER )
{ //if we pick up & carry, drop their weap
if ( traceEnt - > s . weapon )
{
if ( traceEnt - > s . weapon ! = WP_SABER )
{
WP_DropWeapon ( traceEnt , NULL ) ;
}
else
{
//turn it off?
traceEnt - > client - > ps . saberActive = qfalse ;
G_SoundOnEnt ( traceEnt , CHAN_WEAPON , " sound/weapons/saber/saberoffquick.wav " ) ;
}
}
}
//else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun
VectorCopy ( traceEnt - > client - > renderInfo . headPoint , self - > client - > ps . forceGripOrg ) ;
}
/*
else
{
VectorCopy ( traceEnt - > currentOrigin , self - > client - > ps . forceGripOrg ) ;
}
*/
self - > client - > ps . forceGripOrg [ 2 ] + = 48 ; //FIXME: define?
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] < FORCE_LEVEL_2 )
{ //just a duration
self - > client - > ps . forcePowerDebounce [ FP_GRIP ] = level . time + 250 ;
self - > client - > ps . forcePowerDuration [ FP_GRIP ] = level . time + 5000 ;
traceEnt - > s . loopSound = G_SoundIndex ( " sound/weapons/force/grip.mp3 " ) ;
}
else
{
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] = = FORCE_LEVEL_2 )
{ //lifting sound? or always?
}
//if ( traceEnt->s.number )
{ //picks them up for a second first
self - > client - > ps . forcePowerDebounce [ FP_GRIP ] = level . time + 1000 ;
}
/*
else
{ //player should take damage right away
self - > client - > ps . forcePowerDebounce [ FP_GRIP ] = level . time + 250 ;
}
*/
self - > s . loopSound = G_SoundIndex ( " sound/weapons/force/grip.mp3 " ) ;
}
}
void ForceLightning ( gentity_t * self )
{
if ( self - > health < = 0 )
{
return ;
}
if ( ! self - > s . number & & ( cg . zoomMode | | in_camera ) )
{ //can't force lightning when zoomed in or in cinematic
return ;
}
if ( self - > client - > ps . leanofs )
{ //can't force-lightning while leaning
return ;
}
if ( self - > client - > ps . forcePower < 25 | | ! WP_ForcePowerUsable ( self , FP_LIGHTNING , 0 ) )
{
return ;
}
if ( self - > client - > ps . forcePowerDebounce [ FP_LIGHTNING ] > level . time )
{ //stops it while using it and also after using it, up to 3 second delay
return ;
}
if ( self - > client - > ps . saberLockTime > level . time )
{ //FIXME: can this be a way to break out?
return ;
}
//Shoot lightning from hand
//make sure this plays and that you cannot press fire for about 1 second after this
if ( self - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] < FORCE_LEVEL_2 )
{
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCELIGHTNING , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
else
{
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCELIGHTNING_START , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
self - > client - > ps . saberMove = self - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
self - > client - > ps . saberBlocked = BLOCKED_NONE ;
G_SoundOnEnt ( self , CHAN_BODY , " sound/weapons/force/lightning.wav " ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] < FORCE_LEVEL_2 )
{ //short burst
//G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
}
else
{ //holding it
self - > s . loopSound = G_SoundIndex ( " sound/weapons/force/lightning2.wav " ) ;
}
//FIXME: build-up or delay this until in proper part of anim
self - > client - > ps . weaponTime = self - > client - > ps . torsoAnimTimer ;
WP_ForcePowerStart ( self , FP_LIGHTNING , self - > client - > ps . torsoAnimTimer ) ;
}
void ForceLightningDamage ( gentity_t * self , gentity_t * traceEnt , vec3_t dir , float dist , float dot , vec3_t impactPoint )
{
if ( traceEnt - > NPC & & traceEnt - > NPC - > scriptFlags & SCF_NO_FORCE )
{
return ;
}
if ( traceEnt & & traceEnt - > takedamage )
{
if ( ! traceEnt - > client | | traceEnt - > client - > playerTeam ! = self - > client - > playerTeam | | self - > enemy = = traceEnt | | traceEnt - > enemy = = self )
{ //an enemy or object
int dmg ;
if ( self - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] > FORCE_LEVEL_2 )
{ //more damage if closer and more in front
dmg = 1 ;
if ( dist < 100 )
{
dmg + = 2 ;
}
else if ( dist < 200 )
{
dmg + = 1 ;
}
if ( dot > 0.9f )
{
dmg + = 2 ;
}
else if ( dot > 0.7f )
{
dmg + = 1 ;
}
}
else
{
dmg = Q_irand ( 1 , 3 ) ; //*self->client->ps.forcePowerLevel[FP_LIGHTNING];
}
if ( traceEnt - > client & & traceEnt - > health > 0 & & ( traceEnt - > client - > NPC_class = = CLASS_DESANN | | traceEnt - > client - > NPC_class = = CLASS_LUKE ) )
{ //Luke and Desann can shield themselves from the attack
//FIXME: shield effect or something?
int parts ;
if ( traceEnt - > client - > ps . groundEntityNum ! = ENTITYNUM_NONE & & ! PM_SpinningSaberAnim ( traceEnt - > client - > ps . legsAnim ) & & ! PM_FlippingAnim ( traceEnt - > client - > ps . legsAnim ) & & ! PM_RollingAnim ( traceEnt - > client - > ps . legsAnim ) )
{ //if on a surface and not in a spin or flip, play full body resist
parts = SETANIM_BOTH ;
}
else
{ //play resist just in torso
parts = SETANIM_TORSO ;
}
NPC_SetAnim ( traceEnt , parts , BOTH_RESISTPUSH , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
Jedi_PlayDeflectSound ( traceEnt ) ;
dmg = 0 ;
}
else if ( traceEnt - > s . weapon = = WP_SABER )
{
if ( Q_irand ( 0 , 1 ) )
{ //jedi less likely to be damaged
dmg = 0 ;
}
else
{
dmg = 1 ;
}
}
if ( traceEnt & & traceEnt - > client & & traceEnt - > client - > NPC_class = = CLASS_GALAK )
{
if ( traceEnt - > client - > ps . powerups [ PW_GALAK_SHIELD ] )
{ //has shield up
dmg = 0 ;
}
}
G_Damage ( traceEnt , self , self , dir , impactPoint , dmg , 0 , MOD_ELECTROCUTE ) ;
if ( traceEnt - > client )
{
if ( ! Q_irand ( 0 , 2 ) )
{
G_Sound ( traceEnt , G_SoundIndex ( va ( " sound/weapons/force/lightninghit%d.wav " , Q_irand ( 1 , 3 ) ) ) ) ;
}
traceEnt - > s . powerups | = ( 1 < < PW_SHOCKED ) ;
// If we are dead or we are a bot, we can do the full version
class_t npc_class = traceEnt - > client - > NPC_class ;
if ( traceEnt - > health < = 0 | | ( npc_class = = CLASS_SEEKER | | npc_class = = CLASS_PROBE | |
npc_class = = CLASS_MOUSE | | npc_class = = CLASS_GONK | | npc_class = = CLASS_R2D2 | | npc_class = = CLASS_REMOTE | |
npc_class = = CLASS_R5D2 | | npc_class = = CLASS_PROTOCOL | | npc_class = = CLASS_MARK1 | |
npc_class = = CLASS_MARK2 | | npc_class = = CLASS_INTERROGATOR | | npc_class = = CLASS_ATST ) | |
npc_class = = CLASS_SENTRY )
{
traceEnt - > client - > ps . powerups [ PW_SHOCKED ] = level . time + 4000 ;
}
else //short version
{
traceEnt - > client - > ps . powerups [ PW_SHOCKED ] = level . time + 500 ;
}
}
}
}
}
void ForceShootLightning ( gentity_t * self )
{
trace_t tr ;
vec3_t end , forward ;
gentity_t * traceEnt ;
if ( self - > health < = 0 )
{
return ;
}
if ( ! self - > s . number & & cg . zoomMode )
{ //can't force lightning when zoomed in
return ;
}
2022-10-28 21:51:32 +00:00
if ( self - > client - > ps . clientNum = = 0 & & ! cg . renderingThirdPerson )
2022-10-12 17:00:26 +00:00
{
vec3_t origin , angles ;
BG_CalculateVROffHandPosition ( origin , angles ) ;
AngleVectors ( angles , forward , NULL , NULL ) ;
}
else
{
AngleVectors ( self - > client - > ps . viewangles , forward , NULL , NULL ) ;
}
2022-09-18 15:37:21 +00:00
VectorNormalize ( forward ) ;
//FIXME: if lightning hits water, do water-only-flagged radius damage from that point
if ( self - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] > FORCE_LEVEL_2 )
{ //arc
vec3_t center , mins , maxs , dir , ent_org , size , v ;
float radius = 512 , dot , dist ;
gentity_t * entityList [ MAX_GENTITIES ] ;
int e , numListedEntities , i ;
VectorCopy ( self - > currentOrigin , center ) ;
for ( i = 0 ; i < 3 ; i + + )
{
mins [ i ] = center [ i ] - radius ;
maxs [ i ] = center [ i ] + radius ;
}
numListedEntities = gi . EntitiesInBox ( mins , maxs , entityList , MAX_GENTITIES ) ;
for ( e = 0 ; e < numListedEntities ; e + + )
{
traceEnt = entityList [ e ] ;
if ( ! traceEnt )
continue ;
if ( traceEnt = = self )
continue ;
if ( traceEnt - > owner = = self & & traceEnt - > s . weapon ! = WP_THERMAL ) //can push your own thermals
continue ;
if ( ! traceEnt - > inuse )
continue ;
if ( ! traceEnt - > takedamage )
continue ;
if ( traceEnt - > health < = 0 ) //no torturing corpses
continue ;
//this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
// find the distance from the edge of the bounding box
for ( i = 0 ; i < 3 ; i + + )
{
if ( center [ i ] < traceEnt - > absmin [ i ] )
{
v [ i ] = traceEnt - > absmin [ i ] - center [ i ] ;
} else if ( center [ i ] > traceEnt - > absmax [ i ] )
{
v [ i ] = center [ i ] - traceEnt - > absmax [ i ] ;
} else
{
v [ i ] = 0 ;
}
}
VectorSubtract ( traceEnt - > absmax , traceEnt - > absmin , size ) ;
VectorMA ( traceEnt - > absmin , 0.5 , size , ent_org ) ;
//see if they're in front of me
//must be within the forward cone
VectorSubtract ( ent_org , center , dir ) ;
VectorNormalize ( dir ) ;
if ( ( dot = DotProduct ( dir , forward ) ) < 0.5 )
continue ;
//must be close enough
dist = VectorLength ( v ) ;
if ( dist > = radius )
{
continue ;
}
//in PVS?
if ( ! traceEnt - > bmodel & & ! gi . inPVS ( ent_org , self - > client - > renderInfo . handLPoint ) )
{ //must be in PVS
continue ;
}
//Now check and see if we can actually hit it
gi . trace ( & tr , self - > client - > renderInfo . handLPoint , vec3_origin , vec3_origin , ent_org , self - > s . number , MASK_SHOT , G2_NOCOLLIDE , 0 ) ;
if ( tr . fraction < 1.0f & & tr . entityNum ! = traceEnt - > s . number )
{ //must have clear LOS
continue ;
}
// ok, we are within the radius, add us to the incoming list
//FIXME: maybe add up the ents and do more damage the less ents there are
// as if we're spreading out the damage?
ForceLightningDamage ( self , traceEnt , dir , dist , dot , ent_org ) ;
}
}
else
{ //trace-line
int ignore = self - > s . number ;
int traces = 0 ;
vec3_t start ;
VectorCopy ( self - > client - > renderInfo . handLPoint , start ) ;
VectorMA ( self - > client - > renderInfo . handLPoint , 2048 , forward , end ) ;
while ( traces < 10 )
{ //need to loop this in case we hit a Jedi who dodges the shot
gi . trace ( & tr , start , vec3_origin , vec3_origin , end , ignore , MASK_SHOT , G2_RETURNONHIT , 0 ) ;
if ( tr . entityNum = = ENTITYNUM_NONE | | tr . fraction = = 1.0 | | tr . allsolid | | tr . startsolid )
{
return ;
}
traceEnt = & g_entities [ tr . entityNum ] ;
//NOTE: only NPCs do this auto-dodge
if ( traceEnt & & traceEnt - > s . number & & traceEnt - > s . weapon = = WP_SABER ) //&& traceEnt->NPC
{ //FIXME: need a more reliable way to know we hit a jedi?
if ( ! Jedi_DodgeEvasion ( traceEnt , self , & 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 ;
}
traceEnt = & g_entities [ tr . entityNum ] ;
ForceLightningDamage ( self , traceEnt , forward , 0 , 0 , tr . endpos ) ;
}
}
void ForceJumpCharge ( gentity_t * self , usercmd_t * ucmd )
{
float forceJumpChargeInterval = forceJumpStrength [ 0 ] / ( FORCE_JUMP_CHARGE_TIME / FRAMETIME ) ;
if ( self - > health < = 0 )
{
return ;
}
if ( ! self - > s . number & & cg . zoomMode )
{ //can't force jump when zoomed in
return ;
}
//need to play sound
if ( ! self - > client - > ps . forceJumpCharge )
{ //FIXME: this should last only as long as the actual charge-up
G_SoundOnEnt ( self , CHAN_BODY , " sound/weapons/force/jumpbuild.wav " ) ;
}
//Increment
self - > client - > ps . forceJumpCharge + = forceJumpChargeInterval ;
//clamp to max strength for current level
if ( self - > client - > ps . forceJumpCharge > forceJumpStrength [ self - > client - > ps . forcePowerLevel [ FP_LEVITATION ] ] )
{
self - > client - > ps . forceJumpCharge = forceJumpStrength [ self - > client - > ps . forcePowerLevel [ FP_LEVITATION ] ] ;
}
//clamp to max available force power
if ( self - > client - > ps . forceJumpCharge / forceJumpChargeInterval / ( FORCE_JUMP_CHARGE_TIME / FRAMETIME ) * forcePowerNeeded [ FP_LEVITATION ] > self - > client - > ps . forcePower )
{ //can't use more than you have
self - > client - > ps . forceJumpCharge = self - > client - > ps . forcePower * forceJumpChargeInterval / ( FORCE_JUMP_CHARGE_TIME / FRAMETIME ) ;
}
//FIXME: a simple tap should always do at least a normal height's jump?
}
int WP_GetVelocityForForceJump ( gentity_t * self , vec3_t jumpVel , usercmd_t * ucmd )
{
float pushFwd = 0 , pushRt = 0 ;
vec3_t view , forward , right ;
VectorCopy ( self - > client - > ps . viewangles , view ) ;
view [ 0 ] = 0 ;
AngleVectors ( view , forward , right , NULL ) ;
if ( ucmd - > forwardmove & & ucmd - > rightmove )
{
if ( ucmd - > forwardmove > 0 )
{
pushFwd = 50 ;
}
else
{
pushFwd = - 50 ;
}
if ( ucmd - > rightmove > 0 )
{
pushRt = 50 ;
}
else
{
pushRt = - 50 ;
}
}
else if ( ucmd - > forwardmove | | ucmd - > rightmove )
{
if ( ucmd - > forwardmove > 0 )
{
pushFwd = 100 ;
}
else if ( ucmd - > forwardmove < 0 )
{
pushFwd = - 100 ;
}
else if ( ucmd - > rightmove > 0 )
{
pushRt = 100 ;
}
else if ( ucmd - > rightmove < 0 )
{
pushRt = - 100 ;
}
}
VectorMA ( self - > client - > ps . velocity , pushFwd , forward , jumpVel ) ;
VectorMA ( self - > client - > ps . velocity , pushRt , right , jumpVel ) ;
jumpVel [ 2 ] + = self - > client - > ps . forceJumpCharge ; //forceJumpStrength;
if ( pushFwd > 0 & & self - > client - > ps . forceJumpCharge > 200 )
{
return FJ_FORWARD ;
}
else if ( pushFwd < 0 & & self - > client - > ps . forceJumpCharge > 200 )
{
return FJ_BACKWARD ;
}
else if ( pushRt > 0 & & self - > client - > ps . forceJumpCharge > 200 )
{
return FJ_RIGHT ;
}
else if ( pushRt < 0 & & self - > client - > ps . forceJumpCharge > 200 )
{
return FJ_LEFT ;
}
else
{ //FIXME: jump straight up anim
return FJ_UP ;
}
}
void ForceJump ( gentity_t * self , usercmd_t * ucmd )
{
if ( self - > client - > ps . forcePowerDuration [ FP_LEVITATION ] > level . time )
{
return ;
}
if ( ! WP_ForcePowerUsable ( self , FP_LEVITATION , 0 ) )
{
return ;
}
if ( self - > s . groundEntityNum = = ENTITYNUM_NONE )
{
return ;
}
if ( self - > client - > ps . pm_flags & PMF_JUMP_HELD )
{
return ;
}
if ( self - > health < = 0 )
{
return ;
}
if ( ! self - > s . number & & ( cg . zoomMode | | in_camera ) )
{ //can't force jump when zoomed in or in cinematic
return ;
}
if ( self - > client - > ps . saberLockTime > level . time )
{ //FIXME: can this be a way to break out?
return ;
}
G_SoundOnEnt ( self , CHAN_BODY , " sound/weapons/force/jump.wav " ) ;
float forceJumpChargeInterval = forceJumpStrength [ self - > client - > ps . forcePowerLevel [ FP_LEVITATION ] ] / ( FORCE_JUMP_CHARGE_TIME / FRAMETIME ) ;
int anim ;
vec3_t jumpVel ;
switch ( WP_GetVelocityForForceJump ( self , jumpVel , ucmd ) )
{
case FJ_FORWARD :
if ( self - > NPC & & self - > NPC - > rank ! = RANK_CREWMAN & & self - > NPC - > rank < = RANK_LT_JG )
{ //can't do acrobatics
anim = BOTH_FORCEJUMP1 ;
}
else
{
anim = BOTH_FLIP_F ;
}
break ;
case FJ_BACKWARD :
if ( self - > NPC & & self - > NPC - > rank ! = RANK_CREWMAN & & self - > NPC - > rank < = RANK_LT_JG )
{ //can't do acrobatics
anim = BOTH_FORCEJUMPBACK1 ;
}
else
{
anim = BOTH_FLIP_B ;
}
break ;
case FJ_RIGHT :
if ( self - > NPC & & self - > NPC - > rank ! = RANK_CREWMAN & & self - > NPC - > rank < = RANK_LT_JG )
{ //can't do acrobatics
anim = BOTH_FORCEJUMPRIGHT1 ;
}
else
{
anim = BOTH_FLIP_R ;
}
break ;
case FJ_LEFT :
if ( self - > NPC & & self - > NPC - > rank ! = RANK_CREWMAN & & self - > NPC - > rank < = RANK_LT_JG )
{ //can't do acrobatics
anim = BOTH_FORCEJUMPLEFT1 ;
}
else
{
anim = BOTH_FLIP_L ;
}
break ;
default :
case FJ_UP :
anim = BOTH_JUMP1 ;
break ;
}
int parts = SETANIM_BOTH ;
if ( self - > client - > ps . weaponTime )
{ //FIXME: really only care if we're in a saber attack anim.. maybe trail length?
parts = SETANIM_LEGS ;
}
NPC_SetAnim ( self , parts , anim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
//FIXME: sound effect
self - > client - > ps . forceJumpZStart = self - > currentOrigin [ 2 ] ; //remember this for when we land
VectorCopy ( jumpVel , self - > client - > ps . velocity ) ;
//wasn't allowing them to attack when jumping, but that was annoying
//self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
WP_ForcePowerStart ( self , FP_LEVITATION , self - > client - > ps . forceJumpCharge / forceJumpChargeInterval / ( FORCE_JUMP_CHARGE_TIME / FRAMETIME ) * forcePowerNeeded [ FP_LEVITATION ] ) ;
//self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
self - > client - > ps . forceJumpCharge = 0 ;
}
void WP_ForcePowerRegenerate ( gentity_t * self , int overrideAmt )
{
if ( ! self - > client )
{
return ;
}
if ( self - > client - > ps . forcePower < self - > client - > ps . forcePowerMax )
{
if ( overrideAmt )
{
self - > client - > ps . forcePower + = overrideAmt ;
}
else
{
self - > client - > ps . forcePower + + ;
}
if ( self - > client - > ps . forcePower > self - > client - > ps . forcePowerMax )
{
self - > client - > ps . forcePower = self - > client - > ps . forcePowerMax ;
}
}
}
void WP_ForcePowerDrain ( gentity_t * self , forcePowers_t forcePower , int overrideAmt )
{
if ( self - > NPC )
{ //For now, NPCs have infinite force power
return ;
}
//take away the power
int drain = overrideAmt ;
if ( ! drain )
{
drain = forcePowerNeeded [ forcePower ] ;
}
if ( ! drain )
{
return ;
}
self - > client - > ps . forcePower - = drain ;
if ( self - > client - > ps . forcePower < 0 )
{
self - > client - > ps . forcePower = 0 ;
}
}
void WP_ForcePowerStart ( gentity_t * self , forcePowers_t forcePower , int overrideAmt )
{
int duration = 0 ;
//FIXME: debounce some of these
//and it in
//set up duration time
switch ( ( int ) forcePower )
{
case FP_HEAL :
self - > client - > ps . forcePowersActive | = ( 1 < < forcePower ) ;
self - > client - > ps . forceHealCount = 0 ;
break ;
case FP_LEVITATION :
self - > client - > ps . forcePowersActive | = ( 1 < < forcePower ) ;
break ;
case FP_SPEED :
//duration is always 5 seconds, player time
duration = ceil ( FORCE_SPEED_DURATION * forceSpeedValue [ self - > client - > ps . forcePowerLevel [ FP_SPEED ] ] ) ; //FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right...
self - > client - > ps . forcePowersActive | = ( 1 < < forcePower ) ;
self - > s . loopSound = G_SoundIndex ( " sound/weapons/force/speedloop.wav " ) ;
break ;
case FP_PUSH :
break ;
case FP_PULL :
break ;
case FP_TELEPATHY :
break ;
case FP_GRIP :
duration = 1000 ;
self - > client - > ps . forcePowersActive | = ( 1 < < forcePower ) ;
break ;
case FP_LIGHTNING :
duration = overrideAmt ;
overrideAmt = 0 ;
self - > client - > ps . forcePowersActive | = ( 1 < < forcePower ) ;
break ;
default :
break ;
}
if ( duration )
{
self - > client - > ps . forcePowerDuration [ forcePower ] = level . time + duration ;
}
else
{
self - > client - > ps . forcePowerDuration [ forcePower ] = 0 ;
}
self - > client - > ps . forcePowerDebounce [ forcePower ] = 0 ;
WP_ForcePowerDrain ( self , forcePower , overrideAmt ) ;
if ( ! self - > s . number )
{
self - > client - > sess . missionStats . forceUsed [ ( int ) forcePower ] + + ;
}
}
qboolean WP_ForcePowerAvailable ( gentity_t * self , forcePowers_t forcePower , int overrideAmt )
{
if ( forcePower = = FP_LEVITATION )
{
return qtrue ;
}
int drain = overrideAmt ? overrideAmt : forcePowerNeeded [ forcePower ] ;
if ( ! drain )
{
return qtrue ;
}
if ( self - > client - > ps . forcePower < drain )
{
//G_AddEvent( self, EV_NOAMMO, 0 );
return qfalse ;
}
return qtrue ;
}
extern void CG_PlayerLockedWeaponSpeech ( int jumping ) ;
qboolean WP_ForcePowerUsable ( gentity_t * self , forcePowers_t forcePower , int overrideAmt )
{
if ( ! ( self - > client - > ps . forcePowersKnown & ( 1 < < forcePower ) ) )
{ //don't know this power
return qfalse ;
}
if ( self - > client - > ps . forcePowerLevel [ forcePower ] < = 0 )
{ //can't use this power
return qfalse ;
}
if ( self - > flags & FL_LOCK_PLAYER_WEAPONS ) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one
{
CG_PlayerLockedWeaponSpeech ( qfalse ) ;
return qfalse ;
}
if ( ( self - > client - > ps . forcePowersActive & ( 1 < < forcePower ) ) )
{ //already using this power
return qfalse ;
}
/*
if ( ! self - > client - > ps . forcePowerLevel [ ( int ) ( forcePower ) ] )
{
return qfalse ;
}
*/
if ( self - > client - > NPC_class = = CLASS_ATST )
{ //Doh! No force powers in an AT-ST!
return qfalse ;
}
if ( self - > client - > ps . vehicleModel ! = 0 )
{ //Doh! No force powers when flying a vehicle!
return qfalse ;
}
if ( self - > client - > ps . viewEntity > 0 & & self - > client - > ps . viewEntity < ENTITYNUM_WORLD )
{ //Doh! No force powers when controlling an NPC
return qfalse ;
}
if ( self - > client - > ps . eFlags & EF_LOCKED_TO_WEAPON )
{ //Doh! No force powers when in an emplaced gun!
return qfalse ;
}
return WP_ForcePowerAvailable ( self , forcePower , overrideAmt ) ;
}
void WP_ForcePowerStop ( gentity_t * self , forcePowers_t forcePower )
{
gentity_t * gripEnt ;
self - > client - > ps . forcePowersActive & = ~ ( 1 < < forcePower ) ;
switch ( ( int ) forcePower )
{
case FP_HEAL :
//if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 )
{ //wasn't an instant heal and heal is now done
if ( self - > client - > ps . forcePowerLevel [ FP_HEAL ] < FORCE_LEVEL_2 )
{ //if in meditation pose, must come out of it
//FIXME: BOTH_FORCEHEAL_STOP
if ( self - > client - > ps . legsAnim = = BOTH_FORCEHEAL_START )
{
NPC_SetAnim ( self , SETANIM_LEGS , BOTH_FORCEHEAL_STOP , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
if ( self - > client - > ps . torsoAnim = = BOTH_FORCEHEAL_START )
{
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCEHEAL_STOP , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
self - > client - > ps . saberMove = self - > client - > ps . saberBounceMove = LS_READY ; //don't finish whatever saber anim you may have been in
self - > client - > ps . saberBlocked = BLOCKED_NONE ;
}
}
break ;
case FP_LEVITATION :
self - > client - > ps . forcePowerDebounce [ FP_LEVITATION ] = 0 ;
break ;
case FP_SPEED :
if ( ! self - > s . number )
{ //player using force speed
if ( g_timescale - > value ! = 1.0 )
{
gi . cvar_set ( " timescale " , " 1 " ) ;
}
}
//FIXME: reset my current anim, keeping current frame, but with proper anim speed
// otherwise, the anim will continue playing at high speed
self - > s . loopSound = 0 ;
break ;
case FP_PUSH :
break ;
case FP_PULL :
break ;
case FP_TELEPATHY :
break ;
case FP_GRIP :
if ( self - > client - > ps . forceGripEntityNum < ENTITYNUM_WORLD )
{
gripEnt = & g_entities [ self - > client - > ps . forceGripEntityNum ] ;
if ( gripEnt )
{
gripEnt - > s . loopSound = 0 ;
if ( gripEnt - > client )
{
gripEnt - > client - > ps . eFlags & = ~ EF_FORCE_GRIPPED ;
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 )
{ //sanity-cap the velocity
float gripVel = VectorNormalize ( gripEnt - > client - > ps . velocity ) ;
if ( gripVel > 500.0f )
{
gripVel = 500.0f ;
}
VectorScale ( gripEnt - > client - > ps . velocity , gripVel , gripEnt - > client - > ps . velocity ) ;
}
//FIXME: they probably dropped their weapon, should we make them flee? Or should AI handle no-weapon behavior?
if ( gripEnt - > health > 0 )
{
int holdTime = 500 ;
G_AddEvent ( gripEnt , EV_WATER_CLEAR , 0 ) ;
if ( gripEnt - > client - > ps . forcePowerDebounce [ FP_PUSH ] > level . time )
{ //they probably pushed out of it
holdTime = 0 ;
}
else if ( gripEnt - > s . weapon = = WP_SABER )
{ //jedi recover faster
holdTime = self - > client - > ps . forcePowerLevel [ FP_GRIP ] * 200 ;
}
else
{
holdTime = self - > client - > ps . forcePowerLevel [ FP_GRIP ] * 500 ;
}
//stop the anims soon, keep them locked in place for a bit
if ( gripEnt - > client - > ps . torsoAnim = = BOTH_CHOKE1 | | gripEnt - > client - > ps . torsoAnim = = BOTH_CHOKE3 )
{ //stop choking anim on torso
if ( gripEnt - > client - > ps . torsoAnimTimer > holdTime )
{
gripEnt - > client - > ps . torsoAnimTimer = holdTime ;
}
}
if ( gripEnt - > client - > ps . legsAnim = = BOTH_CHOKE1 | | gripEnt - > client - > ps . legsAnim = = BOTH_CHOKE3 )
{ //stop choking anim on legs
gripEnt - > client - > ps . legsAnimTimer = 0 ;
if ( holdTime )
{
//lock them in place for a bit
gripEnt - > client - > ps . pm_time = gripEnt - > client - > ps . torsoAnimTimer ;
gripEnt - > client - > ps . pm_flags | = PMF_TIME_KNOCKBACK ;
if ( gripEnt - > s . number )
{ //NPC
gripEnt - > painDebounceTime = level . time + gripEnt - > client - > ps . torsoAnimTimer ;
}
else
{ //player
gripEnt - > aimDebounceTime = level . time + gripEnt - > client - > ps . torsoAnimTimer ;
}
}
}
if ( gripEnt - > NPC )
{
if ( ! ( gripEnt - > NPC - > aiFlags & NPCAI_DIE_ON_IMPACT ) )
{ //not falling to their death
gripEnt - > NPC - > nextBStateThink = level . time + holdTime ;
}
//if still alive after stopped gripping, let them wake others up
G_AngerAlert ( gripEnt ) ;
}
}
}
else
{
gripEnt - > s . eFlags & = ~ EF_FORCE_GRIPPED ;
if ( gripEnt - > s . eType = = ET_MISSILE )
{ //continue normal movement
if ( gripEnt - > s . weapon = = WP_THERMAL )
{
gripEnt - > s . pos . trType = TR_INTERPOLATE ;
}
else
{
gripEnt - > s . pos . trType = TR_LINEAR ; //FIXME: what about gravity-effected projectiles?
}
VectorCopy ( gripEnt - > currentOrigin , gripEnt - > s . pos . trBase ) ;
gripEnt - > s . pos . trTime = level . time ;
}
else
{ //drop it
gripEnt - > e_ThinkFunc = thinkF_G_RunObject ;
gripEnt - > nextthink = level . time + FRAMETIME ;
gripEnt - > s . pos . trType = TR_GRAVITY ;
VectorCopy ( gripEnt - > currentOrigin , gripEnt - > s . pos . trBase ) ;
gripEnt - > s . pos . trTime = level . time ;
}
}
}
self - > s . loopSound = 0 ;
self - > client - > ps . forceGripEntityNum = ENTITYNUM_NONE ;
2022-11-03 22:38:34 +00:00
self - > client - > ps . forceGripEntityInitialDist = ENTITYNUM_NONE ;
2022-09-18 15:37:21 +00:00
}
if ( self - > client - > ps . torsoAnim = = BOTH_FORCEGRIP_HOLD )
{
NPC_SetAnim ( self , BOTH_FORCEGRIP_RELEASE , BOTH_FORCELIGHTNING_RELEASE , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
break ;
case FP_LIGHTNING :
if ( self - > client - > ps . torsoAnim = = BOTH_FORCELIGHTNING_HOLD
| | self - > client - > ps . torsoAnim = = BOTH_FORCELIGHTNING_START )
{
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCELIGHTNING_RELEASE , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
if ( self - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] < FORCE_LEVEL_2 )
{ //don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
self - > client - > ps . forcePowerDebounce [ FP_LIGHTNING ] = level . time + 3000 ; //FIXME: define?
}
else
{ //stop the looping sound
self - > client - > ps . forcePowerDebounce [ FP_LIGHTNING ] = level . time + 1000 ; //FIXME: define?
self - > s . loopSound = 0 ;
}
break ;
default :
break ;
}
}
extern qboolean PM_ForceJumpingUp ( gentity_t * gent ) ;
static void WP_ForcePowerRun ( gentity_t * self , forcePowers_t forcePower , usercmd_t * cmd )
{
float speed , newSpeed ;
gentity_t * gripEnt ;
vec3_t angles , dir , gripOrg , gripEntOrg ;
float dist ;
extern usercmd_t ucmd ;
switch ( ( int ) forcePower )
{
case FP_HEAL :
if ( self - > client - > ps . forceHealCount > = MAX_FORCE_HEAL | | self - > health > = self - > client - > ps . stats [ STAT_MAX_HEALTH ] )
{ //fully healed or used up all 25
if ( ! Q3_TaskIDPending ( self , TID_CHAN_VOICE ) )
{
G_SoundOnEnt ( self , CHAN_VOICE , va ( " sound/weapons/force/heal%d.mp3 " , Q_irand ( 1 , 4 ) ) ) ;
}
WP_ForcePowerStop ( self , forcePower ) ;
}
else if ( self - > client - > ps . forcePowerLevel [ FP_HEAL ] < FORCE_LEVEL_3 & & ( ( cmd - > buttons & BUTTON_ATTACK ) | | ( cmd - > buttons & BUTTON_ALT_ATTACK ) | | self - > painDebounceTime > level . time | | ( self - > client - > ps . weaponTime & & self - > client - > ps . weapon ! = WP_NONE ) ) )
{ //attacked or was hit while healing...
//stop healing
WP_ForcePowerStop ( self , forcePower ) ;
}
else if ( self - > client - > ps . forcePowerLevel [ FP_HEAL ] < FORCE_LEVEL_2 & & ( cmd - > rightmove | | cmd - > forwardmove | | cmd - > upmove > 0 ) )
{ //moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used
//stop healing
WP_ForcePowerStop ( self , forcePower ) ;
}
else if ( self - > client - > ps . forcePowerDebounce [ FP_HEAL ] < level . time )
{ //time to heal again
if ( WP_ForcePowerAvailable ( self , forcePower , 4 ) )
{ //have available power
self - > health + + ;
self - > client - > ps . forceHealCount + + ;
if ( self - > client - > ps . forcePowerLevel [ FP_HEAL ] > FORCE_LEVEL_2 )
{
self - > client - > ps . forcePowerDebounce [ FP_HEAL ] = level . time + 50 ;
}
else
{
self - > client - > ps . forcePowerDebounce [ FP_HEAL ] = level . time + FORCE_HEAL_INTERVAL ;
}
WP_ForcePowerDrain ( self , forcePower , 4 ) ;
}
else
{ //stop
WP_ForcePowerStop ( self , forcePower ) ;
}
}
break ;
case FP_LEVITATION :
if ( self - > client - > ps . groundEntityNum ! = ENTITYNUM_NONE & & ! self - > client - > ps . forceJumpZStart )
{ //done with jump
WP_ForcePowerStop ( self , forcePower ) ;
}
else
{
if ( PM_ForceJumpingUp ( self ) )
{ //holding jump in air
if ( cmd - > upmove > 10 )
{ //still trying to go up
if ( WP_ForcePowerAvailable ( self , FP_LEVITATION , 1 ) )
{
if ( self - > client - > ps . forcePowerDebounce [ FP_LEVITATION ] < level . time )
{
WP_ForcePowerDrain ( self , FP_LEVITATION , 5 ) ;
self - > client - > ps . forcePowerDebounce [ FP_LEVITATION ] = level . time + 100 ;
}
self - > client - > ps . forcePowersActive | = ( 1 < < FP_LEVITATION ) ;
self - > client - > ps . forceJumpCharge = 1 ; //just used as a flag for the player, cleared when he lands
}
else
{ //cut the jump short
WP_ForcePowerStop ( self , forcePower ) ;
}
}
else
{ //cut the jump short
WP_ForcePowerStop ( self , forcePower ) ;
}
}
else
{
WP_ForcePowerStop ( self , forcePower ) ;
}
}
break ;
case FP_SPEED :
speed = forceSpeedValue [ self - > client - > ps . forcePowerLevel [ FP_SPEED ] ] ;
if ( ! self - > s . number )
{ //player using force speed
gi . cvar_set ( " timescale " , va ( " %4.2f " , speed ) ) ;
if ( g_timescale - > value > speed )
{
newSpeed = g_timescale - > value - 0.05 ;
if ( newSpeed < speed )
{
newSpeed = speed ;
}
gi . cvar_set ( " timescale " , va ( " %4.2f " , newSpeed ) ) ;
}
}
break ;
case FP_PUSH :
break ;
case FP_PULL :
break ;
case FP_TELEPATHY :
break ;
case FP_GRIP :
if ( ! WP_ForcePowerAvailable ( self , FP_GRIP , 0 )
| | ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 & & ! self - > s . number & & ! ( cmd - > buttons & BUTTON_FORCEGRIP ) ) )
{
WP_ForcePowerStop ( self , FP_GRIP ) ;
return ;
}
else if ( self - > client - > ps . forceGripEntityNum > = 0 & & self - > client - > ps . forceGripEntityNum < ENTITYNUM_WORLD )
{
gripEnt = & g_entities [ self - > client - > ps . forceGripEntityNum ] ;
if ( ! gripEnt | | ( gripEnt - > health < = 0 & & gripEnt - > takedamage ) ) //FIXME: what about things that never had health or lose takedamage when they die?
{ //either invalid ent, or dead ent
WP_ForcePowerStop ( self , FP_GRIP ) ;
return ;
}
else if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] = = FORCE_LEVEL_1
& & gripEnt - > client
& & gripEnt - > client - > ps . groundEntityNum = = ENTITYNUM_NONE )
{
WP_ForcePowerStop ( self , FP_GRIP ) ;
return ;
}
else if ( gripEnt - > s . weapon = = WP_SABER & & gripEnt - > NPC & & gripEnt - > client & & gripEnt - > client - > ps . forcePowersKnown & ( 1 < < FP_PUSH ) & & ! Q_irand ( 0 , 100 - ( gripEnt - > NPC - > stats . evasion * 10 ) - ( g_spskill - > integer * 10 ) ) )
{ //a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
ForceThrow ( gripEnt , qfalse ) ;
//FIXME: I need to go into some pushed back anim...
WP_ForcePowerStop ( self , FP_GRIP ) ;
return ;
}
else
{
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 )
{ //holding it
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCEGRIP_HOLD , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
2022-11-03 22:38:34 +00:00
bool isFirstPersonPlayer = ( self - > client - > ps . clientNum = = 0 & & ! cg . renderingThirdPerson ) ;
2022-09-18 15:37:21 +00:00
//get their org
2022-11-03 22:38:34 +00:00
if ( isFirstPersonPlayer )
2022-10-12 21:35:27 +00:00
{
vec3_t origin ;
BG_CalculateVROffHandPosition ( origin , angles ) ;
}
else
{
VectorCopy ( self - > client - > ps . viewangles , angles ) ;
}
2022-09-18 15:37:21 +00:00
angles [ 0 ] - = 10 ;
AngleVectors ( angles , dir , NULL , NULL ) ;
if ( gripEnt - > client )
{ //move
VectorCopy ( gripEnt - > client - > renderInfo . headPoint , gripEntOrg ) ;
}
else
{
VectorCopy ( gripEnt - > currentOrigin , gripEntOrg ) ;
}
2022-11-03 22:38:34 +00:00
if ( isFirstPersonPlayer & &
self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_2 & &
self - > client - > ps . forceGripEntityInitialDist = = ENTITYNUM_NONE )
{
vec3_t diff ;
2022-11-07 23:57:10 +00:00
VectorSubtract ( vr - > offhandposition , vr - > hmdposition , diff ) ;
2022-11-03 22:38:34 +00:00
self - > client - > ps . forceGripEntityInitialDist = VectorLength ( diff ) ;
}
2022-09-18 15:37:21 +00:00
//how far are they
dist = Distance ( self - > client - > renderInfo . handLPoint , gripEntOrg ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] = = FORCE_LEVEL_2 & &
2022-10-12 21:35:27 +00:00
( ! InFront ( gripEntOrg , self - > client - > renderInfo . handLPoint , angles , 0.3f ) | |
2022-09-18 15:37:21 +00:00
DistanceSquared ( gripEntOrg , self - > client - > renderInfo . handLPoint ) > FORCE_GRIP_DIST_SQUARED ) )
{ //must face them
WP_ForcePowerStop ( self , FP_GRIP ) ;
return ;
}
//check for lift or carry
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_2 )
{ //carry
2022-11-03 22:38:34 +00:00
if ( isFirstPersonPlayer ) {
vec3_t diff ;
2022-11-07 23:57:10 +00:00
VectorSubtract ( vr - > offhandposition , vr - > hmdposition , diff ) ;
2022-11-03 22:38:34 +00:00
float length = VectorLength ( diff ) ;
2022-11-07 23:57:10 +00:00
float movedLength = ( length - self - > client - > ps . forceGripEntityInitialDist ) * cg_worldScale . value ;
if ( fabs ( movedLength ) > 1.0f ) {
dist + = movedLength * 5.0f ;
2022-11-03 22:38:34 +00:00
}
if ( dist > 384 ) {
dist = 384 ;
} else if ( dist < 32 ) {
dist = 32 ;
}
2022-09-18 15:37:21 +00:00
}
2022-11-03 22:38:34 +00:00
else
2022-09-18 15:37:21 +00:00
{
2022-11-03 22:38:34 +00:00
//cap dist
if ( dist > 256 ) {
dist = 256 ;
} else if ( dist < 128 ) {
dist = 128 ;
}
2022-09-18 15:37:21 +00:00
}
2022-11-03 22:38:34 +00:00
VectorMA ( self - > client - > renderInfo . handLPoint ,
dist , dir ,
gripOrg ) ;
2022-09-18 15:37:21 +00:00
}
else if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 )
{ //just lift
VectorCopy ( self - > client - > ps . forceGripOrg , gripOrg ) ;
}
else
{
VectorCopy ( gripEnt - > currentOrigin , gripOrg ) ;
}
//now move them
if ( gripEnt - > client )
{
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 )
{ //level 1 just holds them
VectorSubtract ( gripOrg , gripEntOrg , gripEnt - > client - > ps . velocity ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_2 )
{ //level 2 just lifts them
float gripDist = VectorNormalize ( gripEnt - > client - > ps . velocity ) / 3.0f ;
if ( gripDist < 5.0f )
{
gripDist = 5.0f ;
}
VectorScale ( gripEnt - > client - > ps . velocity , ( gripDist * gripDist ) , gripEnt - > client - > ps . velocity ) ;
}
}
//stop them from thinking
gripEnt - > client - > ps . pm_time = 2000 ;
gripEnt - > client - > ps . pm_flags | = PMF_TIME_KNOCKBACK ;
if ( gripEnt - > NPC )
{
if ( ! ( gripEnt - > NPC - > aiFlags & NPCAI_DIE_ON_IMPACT ) )
{ //not falling to their death
gripEnt - > NPC - > nextBStateThink = level . time + 2000 ;
}
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 )
{ //level 1 just holds them
vectoangles ( dir , angles ) ;
gripEnt - > NPC - > desiredYaw = AngleNormalize180 ( angles [ YAW ] + 180 ) ;
gripEnt - > NPC - > desiredPitch = - angles [ PITCH ] ;
SaveNPCGlobals ( ) ;
SetNPCGlobals ( gripEnt ) ;
NPC_UpdateAngles ( qtrue , qtrue ) ;
gripEnt - > NPC - > last_ucmd . angles [ 0 ] = ucmd . angles [ 0 ] ;
gripEnt - > NPC - > last_ucmd . angles [ 1 ] = ucmd . angles [ 1 ] ;
gripEnt - > NPC - > last_ucmd . angles [ 2 ] = ucmd . angles [ 2 ] ;
RestoreNPCGlobals ( ) ;
//FIXME: why does he turn back to his original angles once he dies or is let go?
}
}
else if ( ! gripEnt - > s . number )
{
//vectoangles( dir, angles );
//gripEnt->client->ps.viewangles[0] = -angles[0];
//gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180);
gripEnt - > enemy = self ;
NPC_SetLookTarget ( gripEnt , self - > s . number , level . time + 1000 ) ;
}
gripEnt - > client - > ps . eFlags | = EF_FORCE_GRIPPED ;
//dammit! Make sure that saber stays off!
gripEnt - > client - > ps . saberActive = qfalse ;
}
else
{ //move
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_1 )
{ //level 1 just holds them
VectorCopy ( gripEnt - > currentOrigin , gripEnt - > s . pos . trBase ) ;
VectorSubtract ( gripOrg , gripEntOrg , gripEnt - > s . pos . trDelta ) ;
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_2 )
{ //level 2 just lifts them
VectorScale ( gripEnt - > s . pos . trDelta , 10 , gripEnt - > s . pos . trDelta ) ;
}
gripEnt - > s . pos . trType = TR_LINEAR ;
gripEnt - > s . pos . trTime = level . time ;
}
gripEnt - > s . eFlags | = EF_FORCE_GRIPPED ;
}
//Shouldn't this be discovered?
//AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 );
AddSightEvent ( self , gripOrg , 128 , AEL_DISCOVERED , 20 ) ;
if ( self - > client - > ps . forcePowerDebounce [ FP_GRIP ] < level . time )
{
//GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH );
gripEnt - > painDebounceTime = 0 ;
G_Damage ( gripEnt , self , self , dir , gripOrg , forceGripDamage [ self - > client - > ps . forcePowerLevel [ FP_GRIP ] ] , DAMAGE_NO_ARMOR , MOD_CRUSH ) ; //MOD_???
if ( gripEnt - > s . number )
{
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] > FORCE_LEVEL_2 )
{ //do damage faster at level 3
self - > client - > ps . forcePowerDebounce [ FP_GRIP ] = level . time + Q_irand ( 150 , 750 ) ;
}
else
{
self - > client - > ps . forcePowerDebounce [ FP_GRIP ] = level . time + Q_irand ( 250 , 1000 ) ;
}
}
else
{ //player takes damage faster
self - > client - > ps . forcePowerDebounce [ FP_GRIP ] = level . time + Q_irand ( 100 , 600 ) ;
}
if ( forceGripDamage [ self - > client - > ps . forcePowerLevel [ FP_GRIP ] ] > 0 )
{ //no damage at level 1
WP_ForcePowerDrain ( self , FP_GRIP , 3 ) ;
}
}
else
{
//WP_ForcePowerDrain( self, FP_GRIP, 0 );
if ( ! gripEnt - > enemy )
{
G_SetEnemy ( gripEnt , self ) ;
}
}
if ( gripEnt - > client & & gripEnt - > health > 0 )
{
int anim = BOTH_CHOKE3 ; //left-handed choke
if ( gripEnt - > client - > ps . weapon = = WP_NONE | | gripEnt - > client - > ps . weapon = = WP_MELEE )
{
anim = BOTH_CHOKE1 ; //two-handed choke
}
if ( self - > client - > ps . forcePowerLevel [ FP_GRIP ] < FORCE_LEVEL_2 )
{ //still on ground, only set anim on torso
NPC_SetAnim ( gripEnt , SETANIM_TORSO , anim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
else
{ //in air, set on whole body
NPC_SetAnim ( gripEnt , SETANIM_BOTH , anim , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
gripEnt - > painDebounceTime = level . time + 2000 ;
}
}
}
break ;
case FP_LIGHTNING :
if ( self - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] > FORCE_LEVEL_1 )
{ //higher than level 1
if ( cmd - > buttons & BUTTON_FORCE_LIGHTNING )
{ //holding it keeps it going
self - > client - > ps . forcePowerDuration [ FP_LIGHTNING ] = level . time + 500 ;
if ( self - > client - > ps . torsoAnim = = BOTH_FORCELIGHTNING_START )
{
if ( ! self - > client - > ps . torsoAnimTimer )
{
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCELIGHTNING_HOLD , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
else
{
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCELIGHTNING_START , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
}
else
{
NPC_SetAnim ( self , SETANIM_TORSO , BOTH_FORCELIGHTNING_HOLD , SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ) ;
}
}
}
if ( ! WP_ForcePowerAvailable ( self , forcePower , 0 ) )
{
WP_ForcePowerStop ( self , forcePower ) ;
}
else
{
ForceShootLightning ( self ) ;
WP_ForcePowerDrain ( self , forcePower , 0 ) ;
}
break ;
default :
break ;
}
}
void WP_ForcePowersUpdate ( gentity_t * self , usercmd_t * ucmd )
{
qboolean usingForce = qfalse ;
int i ;
//see if any force powers are running
if ( ! self )
{
return ;
}
if ( ! self - > client )
{
return ;
}
if ( self - > health < = 0 )
{ //if dead, deactivate any active force powers
for ( i = 0 ; i < NUM_FORCE_POWERS ; i + + )
{
if ( self - > client - > ps . forcePowerDuration [ i ] | | ( self - > client - > ps . forcePowersActive & ( 1 < < i ) ) )
{
WP_ForcePowerStop ( self , ( forcePowers_t ) i ) ;
self - > client - > ps . forcePowerDuration [ i ] = 0 ;
}
}
return ;
}
if ( ! self - > s . number )
{ //player uses different kind of force-jump
}
else
{
/*
if ( ucmd - > buttons & BUTTON_FORCEJUMP )
{ //just charging up
ForceJumpCharge ( self , ucmd ) ;
}
else */
if ( self - > client - > ps . forceJumpCharge )
{ //let go of charge button, have charge
//if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
if ( self - > client - > ps . groundEntityNum = = ENTITYNUM_NONE
& & ! PM_SwimmingAnim ( self - > client - > ps . legsAnim ) )
{ //FIXME: stop sound?
//self->client->ps.forceJumpCharge = 0;
//FIXME: actually, we want this to still be cleared... don't clear it if the button isn't being pressed, but clear it if not holding button and not on ground.
}
else
{ //still on ground, so jump
ForceJump ( self , ucmd ) ;
return ;
}
}
}
if ( ucmd - > buttons & BUTTON_FORCEGRIP )
{
ForceGrip ( self ) ;
}
if ( ucmd - > buttons & BUTTON_FORCE_LIGHTNING )
{
ForceLightning ( self ) ;
}
for ( i = 0 ; i < NUM_FORCE_POWERS ; i + + )
{
if ( self - > client - > ps . forcePowerDuration [ i ] )
{
if ( self - > client - > ps . forcePowerDuration [ i ] < level . time )
{
if ( ( self - > client - > ps . forcePowersActive & ( 1 < < i ) ) )
{ //turn it off
WP_ForcePowerStop ( self , ( forcePowers_t ) i ) ;
}
self - > client - > ps . forcePowerDuration [ i ] = 0 ;
}
}
if ( ( self - > client - > ps . forcePowersActive & ( 1 < < i ) ) )
{
usingForce = qtrue ;
WP_ForcePowerRun ( self , ( forcePowers_t ) i , ucmd ) ;
}
}
if ( self - > client - > ps . saberInFlight )
{ //don't regen force power while throwing saber
if ( self - > client - > ps . saberEntityNum < ENTITYNUM_NONE & & self - > client - > ps . saberEntityNum > 0 ) //player is 0
{ //
if ( & g_entities [ self - > client - > ps . saberEntityNum ] ! = NULL & & g_entities [ self - > client - > ps . saberEntityNum ] . s . pos . trType = = TR_LINEAR )
{ //fell to the ground and we're trying to pull it back
usingForce = qtrue ;
}
}
}
if ( ! usingForce )
{ //when not using the force, regenerate at 10 points per second
if ( self - > client - > ps . forcePowerRegenDebounceTime < level . time )
{
WP_ForcePowerRegenerate ( self , 0 ) ;
self - > client - > ps . forcePowerRegenDebounceTime = level . time + 100 ;
}
}
}
void WP_InitForcePowers ( gentity_t * ent )
{
if ( ! ent | | ! ent - > client )
{
return ;
}
if ( ent - > client - > NPC_class = = CLASS_TAVION | |
ent - > client - > NPC_class = = CLASS_REBORN | |
ent - > client - > NPC_class = = CLASS_DESANN | |
ent - > client - > NPC_class = = CLASS_SHADOWTROOPER | |
ent - > client - > NPC_class = = CLASS_JEDI | |
ent - > client - > NPC_class = = CLASS_LUKE )
{ //an NPC jedi
ent - > client - > ps . forcePower = ent - > client - > ps . forcePowerMax = FORCE_POWER_MAX ;
ent - > client - > ps . forcePowerRegenDebounceTime = 0 ;
ent - > client - > ps . forceGripEntityNum = ENTITYNUM_NONE ;
2022-11-03 22:38:34 +00:00
ent - > client - > ps . forceGripEntityInitialDist = ENTITYNUM_NONE ;
2022-09-18 15:37:21 +00:00
if ( ent - > client - > NPC_class = = CLASS_DESANN )
{
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_LEVITATION ) | ( 1 < < FP_PUSH ) | ( 1 < < FP_PULL ) | ( 1 < < FP_GRIP ) | ( 1 < < FP_LIGHTNING ) | ( 1 < < FP_SABERTHROW ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PULL ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_GRIP ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_3 ;
}
else if ( ent - > client - > NPC_class = = CLASS_LUKE )
{
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_LEVITATION ) | ( 1 < < FP_PUSH ) | ( 1 < < FP_PULL ) | ( 1 < < FP_SABERTHROW ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PULL ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_3 ;
}
else if ( ent - > client - > NPC_class = = CLASS_TAVION | | ( ent - > client - > NPC_class = = CLASS_JEDI & & ent - > NPC - > rank = = RANK_COMMANDER ) )
{ //Tavia or trainer Jedi
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_LEVITATION ) | ( 1 < < FP_PUSH ) | ( 1 < < FP_PULL ) | ( 1 < < FP_SABERTHROW ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PULL ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_3 ;
if ( ent - > client - > NPC_class = = CLASS_TAVION )
{
ent - > client - > ps . forcePowersKnown | = ( 1 < < FP_LIGHTNING ) | ( 1 < < FP_GRIP ) ;
ent - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_GRIP ] = FORCE_LEVEL_2 ;
}
}
else if ( ent - > client - > NPC_class = = CLASS_SHADOWTROOPER )
{ //Shadow Trooper
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_LEVITATION ) | ( 1 < < FP_PUSH ) | ( 1 < < FP_PULL ) | ( 1 < < FP_SABERTHROW ) | ( 1 < < FP_GRIP ) | ( 1 < < FP_LIGHTNING ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_PULL ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_GRIP ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_3 ;
}
else if ( ent - > NPC - > rank = = RANK_LT | | ent - > client - > NPC_class = = CLASS_JEDI )
{ //Reborn Boss or ally Jedi
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_LEVITATION ) | ( 1 < < FP_PUSH ) | ( 1 < < FP_PULL ) | ( 1 < < FP_SABERTHROW ) | ( 1 < < FP_GRIP ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_PULL ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_3 ;
if ( ent - > client - > NPC_class ! = CLASS_JEDI )
{
ent - > client - > ps . forcePowersKnown | = ( 1 < < FP_GRIP ) ;
ent - > client - > ps . forcePowerLevel [ FP_GRIP ] = FORCE_LEVEL_2 ;
}
}
else if ( ent - > NPC - > rank = = RANK_LT_JG )
{ //Reborn Fencer
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_PUSH ) | ( 1 < < FP_SABERTHROW ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_2 ; //FIXME: maybe make him only use it in defense- to throw away grenades
ent - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_2 ;
}
else if ( ent - > NPC - > rank = = RANK_ENSIGN )
{ //Force User
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_LEVITATION ) | ( 1 < < FP_PUSH ) | ( 1 < < FP_PULL ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_PULL ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_1 ;
}
else if ( ent - > NPC - > rank = = RANK_CREWMAN )
{ //Acrobat
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_LEVITATION ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_2 ;
}
else if ( ent - > NPC - > rank = = RANK_CIVILIAN )
{ //Grunt (NOTE: grunt turns slower and has less health)
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_SPEED ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_1 ;
}
}
else
{ //player
ent - > client - > ps . forcePowersKnown = ( 1 < < FP_HEAL ) | ( 1 < < FP_LEVITATION ) | ( 1 < < FP_SPEED ) | ( 1 < < FP_PUSH ) | ( 1 < < FP_PULL ) | ( 1 < < FP_TELEPATHY ) | ( 1 < < FP_GRIP ) | ( 1 < < FP_LIGHTNING ) | ( 1 < < FP_SABERTHROW ) | ( 1 < < FP_SABER_DEFENSE ) | ( 1 < < FP_SABER_OFFENSE ) ;
ent - > client - > ps . forcePower = ent - > client - > ps . forcePowerMax = FORCE_POWER_MAX ;
ent - > client - > ps . forcePowerRegenDebounceTime = 0 ;
ent - > client - > ps . forcePowerLevel [ FP_HEAL ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_LEVITATION ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_PUSH ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_PULL ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_SABERTHROW ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_SPEED ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_LIGHTNING ] = FORCE_LEVEL_1 ;
ent - > client - > ps . forcePowerLevel [ FP_TELEPATHY ] = FORCE_LEVEL_2 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_DEFENSE ] = FORCE_LEVEL_3 ;
ent - > client - > ps . forcePowerLevel [ FP_SABER_OFFENSE ] = FORCE_LEVEL_3 ;
if ( ent - > NPC )
{ //???
ent - > client - > ps . forcePowerLevel [ FP_GRIP ] = FORCE_LEVEL_3 ;
}
else
{
ent - > client - > ps . forcePowerLevel [ FP_GRIP ] = FORCE_LEVEL_2 ;
}
ent - > client - > ps . forceGripEntityNum = ENTITYNUM_NONE ;
2022-11-03 22:38:34 +00:00
ent - > client - > ps . forceGripEntityInitialDist = ENTITYNUM_NONE ;
2022-09-18 15:37:21 +00:00
}
}