jedioutcast/CODE-mp/game/w_saber.c
2013-04-04 16:05:53 -05:00

4023 lines
121 KiB
C

#include "g_local.h"
#include "bg_local.h" //Only because we use PM_SetAnim here once.
#include "w_saber.h"
#include "ai_main.h"
#include "..\ghoul2\g2.h"
#define SABER_BOX_SIZE 16.0f
extern bot_state_t *botstates[MAX_CLIENTS];
extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold );
int saberSpinSound = 0;
int saberOffSound = 0;
int saberOnSound = 0;
int saberHumSound = 0;
qboolean PM_SaberInTransition( int move );
float RandFloat(float min, float max) {
return ((rand() * (max - min)) / 32768.0F) + min;
}
//#define DEBUG_SABER_BOX
#ifdef DEBUG_SABER_BOX
void G_DebugBoxLines(vec3_t mins, vec3_t maxs, int duration)
{
vec3_t start;
vec3_t end;
float x = maxs[0] - mins[0];
float y = maxs[1] - mins[1];
// top of box
VectorCopy(maxs, start);
VectorCopy(maxs, end);
start[0] -= x;
G_TestLine(start, end, 0x00000ff, duration);
end[0] = start[0];
end[1] -= y;
G_TestLine(start, end, 0x00000ff, duration);
start[1] = end[1];
start[0] += x;
G_TestLine(start, end, 0x00000ff, duration);
G_TestLine(start, maxs, 0x00000ff, duration);
// bottom of box
VectorCopy(mins, start);
VectorCopy(mins, end);
start[0] += x;
G_TestLine(start, end, 0x00000ff, duration);
end[0] = start[0];
end[1] += y;
G_TestLine(start, end, 0x00000ff, duration);
start[1] = end[1];
start[0] -= x;
G_TestLine(start, end, 0x00000ff, duration);
G_TestLine(start, mins, 0x00000ff, duration);
}
#endif
void SaberUpdateSelf(gentity_t *ent)
{
if (ent->r.ownerNum == ENTITYNUM_NONE)
{
ent->think = G_FreeEntity;
ent->nextthink = level.time;
return;
}
if (!g_entities[ent->r.ownerNum].inuse ||
!g_entities[ent->r.ownerNum].client ||
g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR)
{
ent->think = G_FreeEntity;
ent->nextthink = level.time;
return;
}
if (g_entities[ent->r.ownerNum].client->ps.saberInFlight && g_entities[ent->r.ownerNum].health > 0)
{ //let The Master take care of us now (we'll get treated like a missile until we return)
ent->nextthink = level.time;
return;
}
if (g_entities[ent->r.ownerNum].client->ps.usingATST)
{
ent->r.contents = 0;
ent->clipmask = 0;
}
else if (g_entities[ent->r.ownerNum].client->ps.weapon != WP_SABER ||
(g_entities[ent->r.ownerNum].client->ps.pm_flags & PMF_FOLLOW) ||
g_entities[ent->r.ownerNum].health < 1 ||
g_entities[ent->r.ownerNum].client->ps.saberHolstered ||
!g_entities[ent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABERATTACK]/* ||
!g_entities[ent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABERTHROW]*/)
{
ent->r.contents = 0;
ent->clipmask = 0;
}
else
{
#ifdef DEBUG_SABER_BOX
vec3_t dbgMins;
vec3_t dbgMaxs;
if (ent->r.ownerNum == 0)
{
VectorAdd( ent->r.currentOrigin, ent->r.mins, dbgMins );
VectorAdd( ent->r.currentOrigin, ent->r.maxs, dbgMaxs );
G_DebugBoxLines(dbgMins, dbgMaxs, 100);
}
#endif
ent->r.contents = CONTENTS_LIGHTSABER;
ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
}
trap_LinkEntity(ent);
ent->nextthink = level.time;
}
void SaberGotHit( gentity_t *self, gentity_t *other, trace_t *trace )
{
gentity_t *own = &g_entities[self->r.ownerNum];
if (!own || !own->client)
{
return;
}
//Do something here..? Was handling projectiles here, but instead they're now handled in their own functions.
}
void WP_SaberInitBladeData( gentity_t *ent )
{
gentity_t *saberent;
//We do not want the client to have any real knowledge of the entity whatsoever. It will only
//ever be used on the server.
saberent = G_Spawn();
ent->client->ps.saberEntityNum = saberent->s.number;
saberent->classname = "lightsaber";
saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
saberent->r.ownerNum = ent->s.number;
saberent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
saberent->r.contents = CONTENTS_LIGHTSABER;
VectorSet( saberent->r.mins, -SABER_BOX_SIZE, -SABER_BOX_SIZE, -SABER_BOX_SIZE );
VectorSet( saberent->r.maxs, SABER_BOX_SIZE, SABER_BOX_SIZE, SABER_BOX_SIZE );
saberent->mass = 10;
saberent->s.eFlags |= EF_NODRAW;
saberent->r.svFlags |= SVF_NOCLIENT;
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->nextthink = level.time + 50;
saberSpinSound = G_SoundIndex("sound/weapons/saber/saberspin.wav");
saberOffSound = G_SoundIndex("sound/weapons/saber/saberoffquick.wav");
saberOnSound = G_SoundIndex("sound/weapons/saber/saberon.wav");
saberHumSound = G_SoundIndex("sound/weapons/saber/saberhum1.wav");
}
#if 0
static void G_SwingAngles( float destination, float swingTolerance, float clampTolerance,
float speed, float *angle, qboolean *swinging ) {
float swing;
float move;
float scale;
if ( !*swinging ) {
// see if a swing should be started
swing = AngleSubtract( *angle, destination );
if ( swing > swingTolerance || swing < -swingTolerance ) {
*swinging = qtrue;
}
}
if ( !*swinging ) {
return;
}
// modify the speed depending on the delta
// so it doesn't seem so linear
swing = AngleSubtract( destination, *angle );
scale = fabs( swing );
if ( scale < swingTolerance * 0.5 ) {
scale = 0.5;
} else if ( scale < swingTolerance ) {
scale = 1.0;
} else {
scale = 2.0;
}
// swing towards the destination angle
if ( swing >= 0 ) {
move = FRAMETIME * scale * speed;
if ( move >= swing ) {
move = swing;
*swinging = qfalse;
}
*angle = AngleMod( *angle + move );
} else if ( swing < 0 ) {
move = FRAMETIME * scale * -speed;
if ( move <= swing ) {
move = swing;
*swinging = qfalse;
}
*angle = AngleMod( *angle + move );
}
// clamp to no more than tolerance
swing = AngleSubtract( destination, *angle );
if ( swing > clampTolerance ) {
*angle = AngleMod( destination - (clampTolerance - 1) );
} else if ( swing < -clampTolerance ) {
*angle = AngleMod( destination + (clampTolerance - 1) );
}
}
#endif
//NOTE: If C` is modified this function should be modified as well (and vice versa)
void G_G2ClientSpineAngles( gentity_t *ent, vec3_t viewAngles, const vec3_t angles, vec3_t thoracicAngles, vec3_t ulAngles, vec3_t llAngles )
{
viewAngles[YAW] = AngleDelta( ent->client->ps.viewangles[YAW], angles[YAW] );
if ( !BG_FlippingAnim( ent->client->ps.legsAnim )
&& !BG_SpinningSaberAnim( ent->client->ps.legsAnim )
&& !BG_SpinningSaberAnim( ent->client->ps.torsoAnim )
&& ent->client->ps.legsAnim != ent->client->ps.torsoAnim )//NOTE: presumes your legs & torso are on the same frame, though they *should* be because PM_SetAnimFinal tries to keep them in synch
{//FIXME: no need to do this if legs and torso on are same frame
//adjust for motion offset
mdxaBone_t boltMatrix;
vec3_t motionFwd, motionAngles;
vec3_t motionRt, tempAng;
int ang;
trap_G2API_GetBoltMatrix_NoRecNoRot( ent->client->ghoul2, 0, ent->bolt_Motion, &boltMatrix, vec3_origin, ent->client->ps.origin, level.time, /*cgs.gameModels*/0, vec3_origin);
//trap_G2API_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, motionFwd );
motionFwd[0] = -boltMatrix.matrix[0][1];
motionFwd[1] = -boltMatrix.matrix[1][1];
motionFwd[2] = -boltMatrix.matrix[2][1];
vectoangles( motionFwd, motionAngles );
//trap_G2API_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, motionRt );
motionRt[0] = -boltMatrix.matrix[0][0];
motionRt[1] = -boltMatrix.matrix[1][0];
motionRt[2] = -boltMatrix.matrix[2][0];
vectoangles( motionRt, tempAng );
motionAngles[ROLL] = -tempAng[PITCH];
for ( ang = 0; ang < 3; ang++ )
{
viewAngles[ang] = AngleNormalize180( viewAngles[ang] - AngleNormalize180( motionAngles[ang] ) );
}
}
//distribute the angles differently up the spine
//NOTE: each of these distributions must add up to 1.0f
thoracicAngles[PITCH] = viewAngles[PITCH]*0.20f;
llAngles[PITCH] = viewAngles[PITCH]*0.40f;
ulAngles[PITCH] = viewAngles[PITCH]*0.40f;
thoracicAngles[YAW] = viewAngles[YAW]*0.20f;
ulAngles[YAW] = viewAngles[YAW]*0.35f;
llAngles[YAW] = viewAngles[YAW]*0.45f;
thoracicAngles[ROLL] = viewAngles[ROLL]*0.20f;
ulAngles[ROLL] = viewAngles[ROLL]*0.35f;
llAngles[ROLL] = viewAngles[ROLL]*0.45f;
}
void G_G2PlayerAngles( gentity_t *ent, vec3_t legs[3], vec3_t legsAngles){
vec3_t torsoAngles, headAngles;
float dest;
static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 };
vec3_t velocity;
float speed;
int dir;
vec3_t velPos, velAng;
int adddir = 0;
float dif;
float degrees_negative = 0;
float degrees_positive = 0;
qboolean yawing = qfalse;
vec3_t ulAngles, llAngles, viewAngles, angles, thoracicAngles = {0,0,0};
//float pitchViewAng;
//float yawViewAng;
VectorCopy( ent->client->ps.viewangles, headAngles );
headAngles[YAW] = AngleMod( headAngles[YAW] );
VectorClear( legsAngles );
VectorClear( torsoAngles );
// --------- yaw -------------
// allow yaw to drift a bit
if ((( ent->s.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_STAND1) ||
( ent->s.torsoAnim & ~ANIM_TOGGLEBIT ) != WeaponReadyAnim[ent->s.weapon] )
{
// if not standing still, always point all in the same direction
/*cent->pe.torso.yawing = qtrue; // always center
cent->pe.torso.pitching = qtrue; // always center
cent->pe.legs.yawing = qtrue; // always center
*/
yawing = qtrue;
}
// adjust legs for movement dir
dir = ent->s.angles2[YAW];
if ( dir < 0 || dir > 7 ) {
return;
}
torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ];
// --------- pitch -------------
// only show a fraction of the pitch angle in the torso
if ( headAngles[PITCH] > 180 ) {
dest = (-360 + headAngles[PITCH]) * 0.75;
} else {
dest = headAngles[PITCH] * 0.75;
}
//G_SwingAngles call disabled, at least for now. It isn't necessary on the server.
// pitchViewAng = ent->client->ps.viewangles[PITCH];
// G_SwingAngles( dest, 15, 30, 0.1, &pitchViewAng, &yawing );
torsoAngles[PITCH] = ent->client->ps.viewangles[PITCH];
// --------- roll -------------
// lean towards the direction of travel
VectorCopy( ent->s.pos.trDelta, velocity );
speed = VectorNormalize( velocity );
/*
speed_desired = ent->client->ps.speed/4;
if (!speed)
{
speed_dif = 0;
}
else
{
speed_dif = (speed/speed_desired);
}
if (speed_dif > 1)
{
speed_dif = 1;
}
else if (speed_dif < 0)
{
speed_dif = 0;
}
*/
if ( speed ) {
vec3_t axis[3];
float side;
speed *= 0.05;
AnglesToAxis( legsAngles, axis );
side = speed * DotProduct( velocity, axis[1] );
legsAngles[ROLL] -= side;
side = speed * DotProduct( velocity, axis[0] );
legsAngles[PITCH] += side;
}
//rww - crazy velocity-based leg angle calculation
legsAngles[YAW] = headAngles[YAW];
velPos[0] = ent->client->ps.origin[0] + velocity[0];
velPos[1] = ent->client->ps.origin[1] + velocity[1];
velPos[2] = ent->client->ps.origin[2] + velocity[2];
if (ent->client->ps.groundEntityNum == ENTITYNUM_NONE)
{ //off the ground, no direction-based leg angles
VectorCopy(ent->client->ps.origin, velPos);
}
VectorSubtract(ent->client->ps.origin, velPos, velAng);
if (!VectorCompare(velAng, vec3_origin))
{
vectoangles(velAng, velAng);
if (velAng[YAW] <= legsAngles[YAW])
{
degrees_negative = (legsAngles[YAW] - velAng[YAW]);
degrees_positive = (360 - legsAngles[YAW]) + velAng[YAW];
}
else
{
degrees_negative = legsAngles[YAW] + (360 - velAng[YAW]);
degrees_positive = (velAng[YAW] - legsAngles[YAW]);
}
if (degrees_negative < degrees_positive)
{
dif = degrees_negative;
adddir = 0;
}
else
{
dif = degrees_positive;
adddir = 1;
}
if (dif > 90)
{
dif = (180 - dif);
}
if (dif > 60)
{
dif = 60;
}
//Slight hack for when playing is running backward
if (dir == 3 || dir == 5)
{
dif = -dif;
}
if (adddir)
{
legsAngles[YAW] -= dif;
}
else
{
legsAngles[YAW] += dif;
}
}
//G_SwingAngles call disabled, at least for now. It isn't necessary on the server.
// yawViewAng = ent->client->ps.viewangles[YAW];
// G_SwingAngles( legsAngles[YAW], 40, 90, /*cg_swingSpeed.value*/ 0.3, &yawViewAng, &yawing );
legsAngles[YAW] = ent->client->ps.viewangles[YAW];
legsAngles[ROLL] = 0;
torsoAngles[ROLL] = 0;
// pull the angles back out of the hierarchial chain
AnglesSubtract( headAngles, torsoAngles, headAngles );
AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
AnglesToAxis( legsAngles, legs );
// we assume that model 0 is the player model.
// trap_G2API_SetBoneAngles(ent->client->ghoul2, 0, "upper_lumbar", torsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time);
VectorCopy( ent->client->ps.viewangles, viewAngles );
if (viewAngles[PITCH] > 290)
{ //keep the same general range as lerpAngles on the client so we can use the same spine correction
viewAngles[PITCH] -= 360;
}
viewAngles[YAW] = viewAngles[ROLL] = 0;
viewAngles[PITCH] *= 0.5;
VectorCopy(legsAngles, angles);
G_G2ClientSpineAngles(ent, viewAngles, angles, thoracicAngles, ulAngles, llAngles);
trap_G2API_SetBoneAngles(ent->client->ghoul2, 0, "upper_lumbar", ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time);
trap_G2API_SetBoneAngles(ent->client->ghoul2, 0, "lower_lumbar", llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time);
trap_G2API_SetBoneAngles(ent->client->ghoul2, 0, "thoracic", thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time);
//trap_G2API_SetBoneAngles(ent->client->ghoul2, 0, "cranium", headAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, level.time);
}
qboolean PM_SaberInDeflect( int move );
qboolean PM_SaberInBrokenParry( int move );
qboolean PM_SaberInBounce( int move );
qboolean SaberAttacking(gentity_t *self)
{
if (PM_SaberInParry(self->client->ps.saberMove))
{
return qfalse;
}
if (PM_SaberInBrokenParry(self->client->ps.saberMove))
{
return qfalse;
}
if (PM_SaberInDeflect(self->client->ps.saberMove))
{
return qfalse;
}
if (PM_SaberInBounce(self->client->ps.saberMove))
{
return qfalse;
}
if (PM_SaberInKnockaway(self->client->ps.saberMove))
{
return qfalse;
}
if (BG_SaberInAttack(self->client->ps.saberMove))
{
if (self->client->ps.weaponstate == WEAPON_FIRING && self->client->ps.saberBlocked == BLOCKED_NONE)
{
return qtrue;
}
}
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
#define SABER_HITDAMAGE 35
void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
{
// animation_t *anim;
int attAnim, defAnim = 0;
float attStart = 0.5f;
float idealDist = 48.0f;
vec3_t attAngles, defAngles, defDir;
vec3_t newOrg;
vec3_t attDir;
float diff = 0;
trace_t trace;
pmove_t pmv;
//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_CCWCIRCLELOCK;
defAnim = BOTH_CWCIRCLELOCK;
attStart = 0.75f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
case LOCK_L:
attAnim = BOTH_CWCIRCLELOCK;
defAnim = BOTH_CCWCIRCLELOCK;
attStart = 0.75f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
default:
return qfalse;
break;
}
memset (&pmv, 0, sizeof(pmv));
pmv.ps = &attacker->client->ps;
pmv.animations = bgGlobalAnimations;
pmv.cmd = attacker->client->pers.cmd;
pmv.trace = trap_Trace;
pmv.pointcontents = trap_PointContents;
pmv.gametype = g_gametype.integer;
//This is a rare exception, you should never really call PM_ utility functions from game or cgame (despite the fact that it's technically possible)
pm = &pmv;
PM_SetAnim(SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
attacker->client->ps.saberLockFrame = bgGlobalAnimations[attAnim].firstFrame+(bgGlobalAnimations[attAnim].numFrames*0.5);
pmv.ps = &defender->client->ps;
pmv.animations = bgGlobalAnimations;
pmv.cmd = defender->client->pers.cmd;
pm = &pmv;
PM_SetAnim(SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
defender->client->ps.saberLockFrame = bgGlobalAnimations[defAnim].firstFrame+(bgGlobalAnimations[defAnim].numFrames*0.5);
attacker->client->ps.saberLockHits = 0;
defender->client->ps.saberLockHits = 0;
attacker->client->ps.saberLockAdvance = qfalse;
defender->client->ps.saberLockAdvance = qfalse;
VectorClear( attacker->client->ps.velocity );
VectorClear( defender->client->ps.velocity );
attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + 10000;
attacker->client->ps.saberLockEnemy = defender->s.number;
defender->client->ps.saberLockEnemy = attacker->s.number;
attacker->client->ps.weaponTime = defender->client->ps.weaponTime = Q_irand( 1000, 3000 );//delay 1 to 3 seconds before pushing
VectorSubtract( defender->r.currentOrigin, attacker->r.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
diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
//try to move attacker half the diff towards the defender
VectorMA( attacker->r.currentOrigin, diff*0.5f, defDir, newOrg );
trap_Trace( &trace, attacker->r.currentOrigin, attacker->r.mins, attacker->r.maxs, newOrg, attacker->s.number, attacker->clipmask );
if ( !trace.startsolid && !trace.allsolid )
{
G_SetOrigin( attacker, trace.endpos );
trap_LinkEntity( attacker );
}
//now get the defender's dist and do it for him too
VectorSubtract( attacker->r.currentOrigin, defender->r.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->r.currentOrigin, diff, attDir, newOrg );
trap_Trace( &trace, defender->r.currentOrigin, defender->r.mins, defender->r.maxs, newOrg, defender->s.number, defender->clipmask );
if ( !trace.startsolid && !trace.allsolid )
{
G_SetOrigin( defender, trace.endpos );
trap_LinkEntity( defender );
}
//DONE!
return qtrue;
}
qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
{
float dist;
qboolean ent1BlockingPlayer = qfalse;
qboolean ent2BlockingPlayer = qfalse;
if (!g_saberLocking.integer)
{
return qfalse;
}
if (!ent1->client || !ent2->client)
{
return qfalse;
}
if (!ent1->client->ps.duelInProgress ||
!ent2->client->ps.duelInProgress ||
ent1->client->ps.duelIndex != ent2->s.number ||
ent2->client->ps.duelIndex != ent1->s.number)
{ //only allow saber locking if two players are dueling with each other directly
if (g_gametype.integer != GT_TOURNAMENT)
{
return qfalse;
}
}
if ( fabs( ent1->r.currentOrigin[2]-ent2->r.currentOrigin[2] ) > 16 )
{
return qfalse;
}
if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
{
return qfalse;
}
dist = DistanceSquared(ent1->r.currentOrigin,ent2->r.currentOrigin);
if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 )
{//between 8 and 80 from each other//was 16 and 48
return qfalse;
}
if (BG_InSpecialJump(ent1->client->ps.legsAnim))
{
return qfalse;
}
if (BG_InSpecialJump(ent2->client->ps.legsAnim))
{
return qfalse;
}
if (BG_InRoll(&ent1->client->ps, ent1->client->ps.legsAnim))
{
return qfalse;
}
if (BG_InRoll(&ent2->client->ps, ent2->client->ps.legsAnim))
{
return qfalse;
}
if (ent1->client->ps.forceHandExtend != HANDEXTEND_NONE ||
ent2->client->ps.forceHandExtend != HANDEXTEND_NONE)
{
return qfalse;
}
if ((ent1->client->ps.pm_flags & PMF_DUCKED) ||
(ent2->client->ps.pm_flags & PMF_DUCKED))
{
return qfalse;
}
// if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) )
// {
// return qfalse;
// }
if (!InFront( ent1->client->ps.origin, ent2->client->ps.origin, ent2->client->ps.viewangles, 0.4f ))
{
return qfalse;
}
if (!InFront( ent2->client->ps.origin, ent1->client->ps.origin, ent1->client->ps.viewangles, 0.4f ))
{
return qfalse;
}
//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 );
}
//return qfalse;
}
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 );
}
//return qfalse;
}
if ( ent1->s.number == 0 &&
ent1->client->ps.saberBlocking == BLK_WIDE && ent1->client->ps.weaponTime <= 0 )
{
ent1BlockingPlayer = qtrue;
}
if ( ent2->s.number == 0 &&
ent2->client->ps.saberBlocking == BLK_WIDE && ent2->client->ps.weaponTime <= 0 )
{
ent2BlockingPlayer = qtrue;
}
//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
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 );
}
return qfalse;
}
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
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 );
}
return qfalse;
}
//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
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 );
}
return qfalse;
}
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
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 );
}
return qfalse;
}
//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
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 );
}
return qfalse;
}
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
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 );
}
return qfalse;
}
//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
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 );
}
return qfalse;
}
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
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 );
}
return qfalse;
}
if ( !Q_irand( 0, 10 ) )
{
return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
}
return qfalse;
}
int G_GetParryForBlock(int block)
{
switch (block)
{
case BLOCKED_UPPER_RIGHT:
return LS_PARRY_UR;
break;
case BLOCKED_UPPER_RIGHT_PROJ:
return LS_REFLECT_UR;
break;
case BLOCKED_UPPER_LEFT:
return LS_PARRY_UL;
break;
case BLOCKED_UPPER_LEFT_PROJ:
return LS_REFLECT_UL;
break;
case BLOCKED_LOWER_RIGHT:
return LS_PARRY_LR;
break;
case BLOCKED_LOWER_RIGHT_PROJ:
return LS_REFLECT_LR;
break;
case BLOCKED_LOWER_LEFT:
return LS_PARRY_LL;
break;
case BLOCKED_LOWER_LEFT_PROJ:
return LS_REFLECT_LL;
break;
case BLOCKED_TOP:
return LS_PARRY_UP;
break;
case BLOCKED_TOP_PROJ:
return LS_REFLECT_UP;
break;
default:
break;
}
return LS_NONE;
}
int PM_SaberBounceForAttack( int move );
int PM_SaberDeflectionForQuad( int quad );
extern stringID_table_t animTable[MAX_ANIMATIONS+1];
qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender, float saberHitFraction )
{
qboolean animBasedDeflection = qtrue;
if ( !attacker || !attacker->client || !attacker->client->ghoul2 )
{
return qfalse;
}
if ( !defender || !defender->client || !defender->client->ghoul2 )
{
return qfalse;
}
if ((level.time - attacker->client->lastSaberStorageTime) > 500)
{ //last update was too long ago, something is happening to this client to prevent his saber from updating
return qfalse;
}
if ((level.time - defender->client->lastSaberStorageTime) > 500)
{ //ditto
return qfalse;
}
if ( animBasedDeflection )
{
//Hmm, let's try just basing it off the anim
int attQuadStart = saberMoveData[attacker->client->ps.saberMove].startQuad;
int attQuadEnd = saberMoveData[attacker->client->ps.saberMove].endQuad;
int defQuad = saberMoveData[defender->client->ps.saberMove].endQuad;
int quadDiff = fabs(defQuad-attQuadStart);
if ( defender->client->ps.saberMove == LS_READY )
{
//FIXME: we should probably do SOMETHING here...
//I have this return qfalse here in the hopes that
//the defender will pick a parry and the attacker
//will hit the defender's saber again.
//But maybe this func call should come *after*
//it's decided whether or not the defender is
//going to parry.
return qfalse;
}
//reverse the left/right of the defQuad because of the mirrored nature of facing each other in combat
switch ( defQuad )
{
case Q_BR:
defQuad = Q_BL;
break;
case Q_R:
defQuad = Q_L;
break;
case Q_TR:
defQuad = Q_TL;
break;
case Q_TL:
defQuad = Q_TR;
break;
case Q_L:
defQuad = Q_R;
break;
case Q_BL:
defQuad = Q_BR;
break;
}
if ( quadDiff > 4 )
{//wrap around so diff is never greater than 180 (4 * 45)
quadDiff = 4 - (quadDiff - 4);
}
//have the quads, find a good anim to use
if ( (!quadDiff || (quadDiff == 1 && Q_irand(0,1))) //defender pretty much stopped the attack at a 90 degree angle
&& (defender->client->ps.fd.saberAnimLevel == attacker->client->ps.fd.saberAnimLevel || Q_irand( 0, defender->client->ps.fd.saberAnimLevel-attacker->client->ps.fd.saberAnimLevel ) >= 0) )//and the defender's style is stronger
{
//bounce straight back
int attMove = attacker->client->ps.saberMove;
attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
if (g_saberDebugPrint.integer)
{
Com_Printf( "attack %s vs. parry %s bounced to %s\n",
animTable[saberMoveData[attMove].animToUse].name,
animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
}
attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
return qfalse;
}
else
{//attack hit at an angle, figure out what angle it should bounce off att
int newQuad;
quadDiff = defQuad - attQuadEnd;
//add half the diff of between the defense and attack end to the attack end
if ( quadDiff > 4 )
{
quadDiff = 4 - (quadDiff - 4);
}
else if ( quadDiff < -4 )
{
quadDiff = -4 + (quadDiff + 4);
}
newQuad = attQuadEnd + ceil( ((float)quadDiff)/2.0f );
if ( newQuad < Q_BR )
{//less than zero wraps around
newQuad = Q_B + newQuad;
}
if ( newQuad == attQuadStart )
{//never come off at the same angle that we would have if the attack was not interrupted
if ( Q_irand(0, 1) )
{
newQuad--;
}
else
{
newQuad++;
}
if ( newQuad < Q_BR )
{
newQuad = Q_B;
}
else if ( newQuad > Q_B )
{
newQuad = Q_BR;
}
}
if ( newQuad == defQuad )
{//bounce straight back
int attMove = attacker->client->ps.saberMove;
attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
if (g_saberDebugPrint.integer)
{
Com_Printf( "attack %s vs. parry %s bounced to %s\n",
animTable[saberMoveData[attMove].animToUse].name,
animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
}
attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
return qfalse;
}
//else, pick a deflection
else
{
int attMove = attacker->client->ps.saberMove;
attacker->client->ps.saberMove = PM_SaberDeflectionForQuad( newQuad );
if (g_saberDebugPrint.integer)
{
Com_Printf( "attack %s vs. parry %s deflected to %s\n",
animTable[saberMoveData[attMove].animToUse].name,
animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
}
attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
return qtrue;
}
}
}
else
{
vec3_t att_HitDir, def_BladeDir, temp;
float hitDot;
//VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp );
VectorCopy(attacker->client->lastSaberBase_Always, temp);
//VectorCopy(attacker->client->lastSaberDir_Always, att_HitDir);
AngleVectors(attacker->client->lastSaberDir_Always, att_HitDir, 0, 0);
AngleVectors(defender->client->lastSaberDir_Always, def_BladeDir, 0, 0);
//VectorMA( def_BladeDir, saberHitFraction, temp, def_BladeDir );
// VectorScale( att_HitDir, -1.0f, att_HitDir );
// VectorScale( def_BladeDir, -1.0f, 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.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
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.saberMove = LS_D1_TR;
}
else if ( swingUDot < -0.25f )
{//deflect to bottom
attacker->client->ps.saberMove = LS_D1_BR;
}
else
{//deflect horizontally
attacker->client->ps.saberMove = LS_D1__R;
}
}
else if ( swingRDot < -0.25f )
{//deflect to left
if ( swingUDot > 0.25f )
{//deflect to top
attacker->client->ps.saberMove = LS_D1_TL;
}
else if ( swingUDot < -0.25f )
{//deflect to bottom
attacker->client->ps.saberMove = LS_D1_BL;
}
else
{//deflect horizontally
attacker->client->ps.saberMove = LS_D1__L;
}
}
else
{//deflect in middle
if ( swingUDot > 0.25f )
{//deflect to top
attacker->client->ps.saberMove = LS_D1_T_;
}
else if ( swingUDot < -0.25f )
{//deflect to bottom
attacker->client->ps.saberMove = 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.saberMove = LS_D1_TR;
}
else if ( swingRDot < 0 )
{
attacker->client->ps.saberMove = LS_D1_TL;
}
else
{
attacker->client->ps.saberMove = LS_D1_T_;
}
}
}
attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
return qtrue;
}
}
}
int G_KnockawayForParry( int move )
{
//FIXME: need actual anims for this
//FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center
switch ( move )
{
case LS_PARRY_UP:
return LS_K1_T_;//push up
break;
case LS_PARRY_UR:
default://case LS_READY:
return LS_K1_TR;//push up, slightly to right
break;
case LS_PARRY_UL:
return LS_K1_TL;//push up and to left
break;
case LS_PARRY_LR:
return LS_K1_BR;//push down and to left
break;
case LS_PARRY_LL:
return LS_K1_BL;//push down and to right
break;
}
//return LS_NONE;
}
#define SABER_NONATTACK_DAMAGE 1
//For strong attacks, we ramp damage based on the point in the attack animation
int G_GetAttackDamage(gentity_t *self, int minDmg, int maxDmg, float multPoint)
{
int peakDif = 0;
int speedDif = 0;
int totalDamage = maxDmg;
float peakPoint = 0;
float attackAnimLength = bgGlobalAnimations[self->client->ps.torsoAnim].numFrames * fabs(bgGlobalAnimations[self->client->ps.torsoAnim].frameLerp);
float currentPoint = 0;
float damageFactor = 0;
float animSpeedFactor = 1.0f;
//Be sure to scale by the proper anim speed just as if we were going to play the animation
BG_SaberStartTransAnim(self->client->ps.fd.saberAnimLevel, self->client->ps.torsoAnim&~ANIM_TOGGLEBIT, &animSpeedFactor);
speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor);
attackAnimLength += speedDif;
peakPoint = attackAnimLength;
peakPoint -= attackAnimLength*multPoint;
//we treat torsoTimer as the point in the animation (closer it is to attackAnimLength, closer it is to beginning)
currentPoint = self->client->ps.torsoTimer;
if (peakPoint > currentPoint)
{
peakDif = (peakPoint - currentPoint);
}
else
{
peakDif = (currentPoint - peakPoint);
}
damageFactor = (float)((currentPoint/peakPoint));
if (damageFactor > 1)
{
damageFactor = (2.0f - damageFactor);
}
totalDamage *= damageFactor;
if (totalDamage < minDmg)
{
totalDamage = minDmg;
}
if (totalDamage > maxDmg)
{
totalDamage = maxDmg;
}
//Com_Printf("%i\n", totalDamage);
return totalDamage;
}
//Get the point in the animation and return a percentage of the current point in the anim between 0 and the total anim length (0.0f - 1.0f)
float G_GetAnimPoint(gentity_t *self)
{
int speedDif = 0;
float attackAnimLength = bgGlobalAnimations[self->client->ps.torsoAnim].numFrames * fabs(bgGlobalAnimations[self->client->ps.torsoAnim].frameLerp);
float currentPoint = 0;
float animSpeedFactor = 1.0f;
float animPercentage = 0;
//Be sure to scale by the proper anim speed just as if we were going to play the animation
BG_SaberStartTransAnim(self->client->ps.fd.saberAnimLevel, self->client->ps.torsoAnim&~ANIM_TOGGLEBIT, &animSpeedFactor);
speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor);
attackAnimLength += speedDif;
currentPoint = self->client->ps.torsoTimer;
animPercentage = currentPoint/attackAnimLength;
//Com_Printf("%f\n", animPercentage);
return animPercentage;
}
qboolean G_ClientIdleInWorld(gentity_t *ent)
{
if (!ent->client->pers.cmd.upmove &&
!ent->client->pers.cmd.forwardmove &&
!ent->client->pers.cmd.rightmove &&
!(ent->client->pers.cmd.buttons & BUTTON_GESTURE) &&
!(ent->client->pers.cmd.buttons & BUTTON_FORCEGRIP) &&
!(ent->client->pers.cmd.buttons & BUTTON_ALT_ATTACK) &&
!(ent->client->pers.cmd.buttons & BUTTON_FORCEPOWER) &&
!(ent->client->pers.cmd.buttons & BUTTON_FORCE_LIGHTNING) &&
!(ent->client->pers.cmd.buttons & BUTTON_FORCE_DRAIN) &&
!(ent->client->pers.cmd.buttons & BUTTON_ATTACK))
{
return qtrue;
}
return qfalse;
}
#ifdef G2_COLLISION_ENABLED
qboolean G_G2TraceCollide(trace_t *tr, vec3_t lastValidStart, vec3_t lastValidEnd, vec3_t traceMins, vec3_t traceMaxs)
{
if (!g_saberGhoul2Collision.integer)
{
return qfalse;
}
if (tr->entityNum < MAX_CLIENTS)
{ //Hit a client with the normal trace, try the collision trace.
G2Trace_t G2Trace;
gentity_t *g2Hit;
vec3_t vIdentity = {1.0f, 1.0f, 1.0f};
vec3_t angles;
int tN = 0;
float fRadius = 0;
if (traceMins[0] ||
traceMins[1] ||
traceMins[2] ||
traceMaxs[0] ||
traceMaxs[1] ||
traceMaxs[2])
{
fRadius=(traceMaxs[0]-traceMins[0])/2.0f;
}
memset (&G2Trace, 0, sizeof(G2Trace));
while (tN < MAX_G2_COLLISIONS)
{
G2Trace[tN].mEntityNum = -1;
tN++;
}
g2Hit = &g_entities[tr->entityNum];
if (g2Hit && g2Hit->inuse && g2Hit->client && g2Hit->client->ghoul2)
{
angles[ROLL] = angles[PITCH] = 0;
angles[YAW] = g2Hit->client->ps.viewangles[YAW];
trap_G2API_CollisionDetect ( G2Trace, g2Hit->client->ghoul2, angles, g2Hit->client->ps.origin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, vIdentity, 0, 2, fRadius );
if (G2Trace[0].mEntityNum != g2Hit->s.number)
{
tr->fraction = 1.0f;
tr->entityNum = ENTITYNUM_NONE;
tr->startsolid = 0;
tr->allsolid = 0;
return qfalse;
}
else
{ //Yay!
VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos);
return qtrue;
}
}
}
return qfalse;
}
#endif
//rww - MP version of the saber damage function. This is where all the things like blocking, triggering a parry,
//triggering a broken parry, doing actual damage, etc. are done for the saber. It doesn't resemble the SP
//version very much, but functionality is (hopefully) about the same.
qboolean CheckSaberDamage(gentity_t *self, vec3_t saberStart, vec3_t saberEnd, qboolean doInterpolate, int trMask)
{
trace_t tr;
vec3_t dir;
vec3_t saberTrMins, saberTrMaxs;
#ifdef G2_COLLISION_ENABLED
vec3_t lastValidStart;
vec3_t lastValidEnd;
#endif
int dmg = 0;
int attackStr = 0;
float saberBoxSize = g_saberBoxTraceSize.value;
qboolean idleDamage = qfalse;
qboolean didHit = qfalse;
qboolean sabersClashed = qfalse;
qboolean unblockable = qfalse;
qboolean didDefense = qfalse;
qboolean didOffense = qfalse;
qboolean saberTraceDone = qfalse;
qboolean otherUnblockable = qfalse;
qboolean tryDeflectAgain = qfalse;
gentity_t *otherOwner;
if (self->client->ps.saberHolstered)
{
return qfalse;
}
memset (&tr, 0, sizeof(tr)); //make the compiler happy
#ifdef G2_COLLISION_ENABLED
if (g_saberGhoul2Collision.integer)
{
VectorSet(saberTrMins, -saberBoxSize*3, -saberBoxSize*3, -saberBoxSize*3);
VectorSet(saberTrMaxs, saberBoxSize*3, saberBoxSize*3, saberBoxSize*3);
}
else
#endif
if (self->client->ps.fd.saberAnimLevel < FORCE_LEVEL_2)
{ //box trace for fast, because it doesn't get updated so often
VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
}
else if (g_saberAlwaysBoxTrace.integer)
{
VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
}
else
{
VectorClear(saberTrMins);
VectorClear(saberTrMaxs);
}
while (!saberTraceDone)
{
if (doInterpolate)
{ //This didn't quite work out like I hoped. But it's better than nothing. Sort of.
vec3_t oldSaberStart, oldSaberEnd, saberDif, oldSaberDif;
int traceTests = 0;
float trDif = 8;
VectorCopy(self->client->lastSaberBase, oldSaberStart);
VectorCopy(self->client->lastSaberTip, oldSaberEnd);
VectorSubtract(saberStart, saberEnd, saberDif);
VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif);
VectorNormalize(saberDif);
VectorNormalize(oldSaberDif);
saberEnd[0] = saberStart[0] - (saberDif[0]*trDif);
saberEnd[1] = saberStart[1] - (saberDif[1]*trDif);
saberEnd[2] = saberStart[2] - (saberDif[2]*trDif);
oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif);
oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif);
oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif);
//G_TestLine(oldSaberEnd, saberEnd, 0x0000ff, 50);
trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask);
#ifdef G2_COLLISION_ENABLED
VectorCopy(saberEnd, lastValidStart);
VectorCopy(saberStart, lastValidEnd);
if (tr.entityNum < MAX_CLIENTS)
{
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
#endif
trDif++;
while (tr.fraction == 1.0 && traceTests < 4 && tr.entityNum >= ENTITYNUM_NONE)
{
VectorCopy(self->client->lastSaberBase, oldSaberStart);
VectorCopy(self->client->lastSaberTip, oldSaberEnd);
VectorSubtract(saberStart, saberEnd, saberDif);
VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif);
VectorNormalize(saberDif);
VectorNormalize(oldSaberDif);
saberEnd[0] = saberStart[0] - (saberDif[0]*trDif);
saberEnd[1] = saberStart[1] - (saberDif[1]*trDif);
saberEnd[2] = saberStart[2] - (saberDif[2]*trDif);
oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif);
oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif);
oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif);
//G_TestLine(oldSaberEnd, saberEnd, 0x0000ff, 50);
trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask);
#ifdef G2_COLLISION_ENABLED
VectorCopy(saberEnd, lastValidStart);
VectorCopy(saberStart, lastValidEnd);
if (tr.entityNum < MAX_CLIENTS)
{
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
#endif
traceTests++;
trDif += 8;
}
}
else
{
/*
if (self->s.number == 0)
{
G_TestLine(saberStart, saberEnd, 0x0000ff, 50);
}
*/
trap_Trace(&tr, saberStart, saberTrMins, saberTrMaxs, saberEnd, self->s.number, trMask);
#ifdef G2_COLLISION_ENABLED
VectorCopy(saberStart, lastValidStart);
VectorCopy(saberEnd, lastValidEnd);
if (tr.entityNum < MAX_CLIENTS)
{
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
#endif
}
/*
if (tr.entityNum == ENTITYNUM_NONE && trMask == CONTENTS_LIGHTSABER)
{ //didn't hit anything while checking with just the saber contents mask, do a normal trace.
trMask = (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT);
}
else
{
saberTraceDone = qtrue;
}
*/
//Doing this differently now
saberTraceDone = qtrue;
}
if (SaberAttacking(self) &&
self->client->ps.saberAttackWound < level.time)
{ //this animation is that of the last attack movement, and so it should do full damage
qboolean saberInSpecial = BG_SaberInSpecial(self->client->ps.saberMove);
dmg = SABER_HITDAMAGE;//*self->client->ps.fd.saberAnimLevel;
if (self->client->ps.fd.saberAnimLevel == 3)
{
//dmg = 100;
//new damage-ramping system
if (!saberInSpecial)
{
dmg = G_GetAttackDamage(self, 2, 120, 0.5f);
}
else if (saberInSpecial &&
(self->client->ps.saberMove == LS_A_JUMP_T__B_))
{
dmg = G_GetAttackDamage(self, 2, 180, 0.65f);
}
else
{
dmg = 100;
}
}
else if (self->client->ps.fd.saberAnimLevel == 2)
{
if (saberInSpecial &&
(self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH))
{ //a well-timed hit with this can do a full 105
dmg = G_GetAttackDamage(self, 2, 100, 0.5f);
}
else
{
dmg = 60;
}
}
else if (self->client->ps.fd.saberAnimLevel == 1)
{
if (saberInSpecial &&
(self->client->ps.saberMove == LS_A_LUNGE))
{
dmg = G_GetAttackDamage(self, 2, SABER_HITDAMAGE-5, 0.3f);
}
else
{
dmg = SABER_HITDAMAGE;
}
}
attackStr = self->client->ps.fd.saberAnimLevel;
}
else if (self->client->ps.saberIdleWound < level.time)
{ //just touching, do minimal damage and only check for it every 200ms (mainly to cut down on network traffic for hit events)
dmg = SABER_NONATTACK_DAMAGE;
//self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
idleDamage = qtrue;
}
/*
if (self->client->ps.saberMove == LS_A_BACK ||
self->client->ps.saberMove == LS_A_BACK_CR ||
self->client->ps.saberMove == LS_A_BACKSTAB ||
self->client->ps.saberMove == LS_A_JUMP_T__B_)
*/
if (BG_SaberInSpecial(self->client->ps.saberMove))
{
unblockable = qtrue;
self->client->ps.saberBlocked = 0;
if (self->client->ps.saberMove == LS_A_JUMP_T__B_)
{ //do extra damage for special unblockables
dmg += 5; //This is very tiny, because this move has a huge damage ramp
}
else if (self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH)
{
dmg += 5; //ditto
if (dmg <= 40 || G_GetAnimPoint(self) <= 0.4f)
{ //sort of a hack, don't want it doing big damage in the off points of the anim
dmg = 2;
}
}
else if (self->client->ps.saberMove == LS_A_LUNGE)
{
dmg += 2; //and ditto again
if (G_GetAnimPoint(self) <= 0.4f)
{ //same as above
dmg = 2;
}
}
else
{
dmg += 20;
}
}
if (!dmg)
{
if (tr.entityNum < MAX_CLIENTS ||
(g_entities[tr.entityNum].inuse && (g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER)))
{
return qtrue;
}
return qfalse;
}
if (dmg > SABER_NONATTACK_DAMAGE && self->client->ps.isJediMaster)
{ //give the Jedi Master more saber attack power
dmg *= 2;
}
VectorSubtract(saberEnd, saberStart, dir);
VectorNormalize(dir);
//rww - I'm saying || tr.startsolid here, because otherwise your saber tends to skip positions and go through
//people, and the compensation traces start in their bbox too. Which results in the saber passing through people
//when you visually cut right through them. Which sucks.
if ((tr.fraction != 1 || tr.startsolid) &&
/*(!g_entities[tr.entityNum].client || !g_entities[tr.entityNum].client->ps.usingATST) &&*/
//g_entities[tr.entityNum].client &&
g_entities[tr.entityNum].takedamage &&
tr.entityNum != self->s.number)
{
gentity_t *te;
if (idleDamage &&
g_entities[tr.entityNum].client &&
OnSameTeam(self, &g_entities[tr.entityNum]) &&
!g_friendlySaber.integer)
{
return qfalse;
}
if (g_entities[tr.entityNum].inuse && g_entities[tr.entityNum].client &&
g_entities[tr.entityNum].client->ps.duelInProgress &&
g_entities[tr.entityNum].client->ps.duelIndex != self->s.number)
{
return qfalse;
}
if (g_entities[tr.entityNum].inuse && g_entities[tr.entityNum].client &&
self->client->ps.duelInProgress &&
self->client->ps.duelIndex != g_entities[tr.entityNum].s.number)
{
return qfalse;
}
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
didHit = qtrue;
if (g_entities[tr.entityNum].client && !unblockable && WP_SaberCanBlock(&g_entities[tr.entityNum], tr.endpos, 0, MOD_SABER, qfalse, attackStr))
{
te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
if (dmg <= SABER_NONATTACK_DAMAGE)
{
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
}
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
te->s.eventParm = 1;
if (dmg > SABER_NONATTACK_DAMAGE)
{
int lockFactor = g_saberLockFactor.integer;
if ((g_entities[tr.entityNum].client->ps.fd.forcePowerLevel[FP_SABERATTACK] - self->client->ps.fd.forcePowerLevel[FP_SABERATTACK]) > 1 &&
Q_irand(1, 10) < lockFactor*2) //used to be < 7
{ //Just got blocked by someone with a decently higher attack level, so enter into a lock (where they have the advantage due to a higher attack lev)
if (!G_ClientIdleInWorld(&g_entities[tr.entityNum]))
{
if (WP_SabersCheckLock(self, &g_entities[tr.entityNum]))
{
self->client->ps.saberBlocked = BLOCKED_NONE;
g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE;
return didHit;
}
}
}
else if (Q_irand(1, 20) < lockFactor)
{
if (!G_ClientIdleInWorld(&g_entities[tr.entityNum]))
{
if (WP_SabersCheckLock(self, &g_entities[tr.entityNum]))
{
self->client->ps.saberBlocked = BLOCKED_NONE;
g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE;
return didHit;
}
}
}
}
otherOwner = &g_entities[tr.entityNum];
goto blockStuff;
}
else
{
if (g_entities[tr.entityNum].client && g_entities[tr.entityNum].client->ps.usingATST)
{
dmg *= 0.1;
}
if (g_entities[tr.entityNum].client && !g_entities[tr.entityNum].client->ps.fd.forcePowerLevel[FP_SABERATTACK])
{ //not a "jedi", so make them suffer more
if (dmg > SABER_NONATTACK_DAMAGE)
{ //don't bother increasing just for idle touch damage
dmg *= 1.5;
}
}
if (g_entities[tr.entityNum].client && g_entities[tr.entityNum].client->ps.weapon == WP_SABER)
{ //for jedi using the saber, half the damage (this comes with the increased default dmg debounce time)
if (dmg > SABER_NONATTACK_DAMAGE && !unblockable)
{ //don't reduce damage if it's only 1, or if this is an unblockable attack
if (dmg == SABER_HITDAMAGE)
{ //level 1 attack
dmg *= 0.7;
}
else
{
dmg *= 0.5;
}
}
}
G_Damage(&g_entities[tr.entityNum], self, self, dir, tr.endpos, dmg, 0, MOD_SABER);
te = G_TempEntity( tr.endpos, EV_SABER_HIT );
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
{ //don't let it play with no direction
te->s.angles[1] = 1;
}
if (g_entities[tr.entityNum].client)
{
te->s.eventParm = 1;
}
else
{
te->s.eventParm = 0;
}
self->client->ps.saberAttackWound = level.time + 100;
}
}
else if ((tr.fraction != 1 || tr.startsolid) &&
(g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER) &&
g_entities[tr.entityNum].r.contents != -1)
{ //saber clash
gentity_t *te;
otherOwner = &g_entities[g_entities[tr.entityNum].r.ownerNum];
if (otherOwner &&
otherOwner->inuse &&
otherOwner->client &&
OnSameTeam(self, otherOwner) &&
!g_friendlySaber.integer)
{
return qfalse;
}
if (otherOwner && otherOwner->client &&
otherOwner->client->ps.duelInProgress &&
otherOwner->client->ps.duelIndex != self->s.number)
{
return qfalse;
}
if (otherOwner && otherOwner->client &&
self->client->ps.duelInProgress &&
self->client->ps.duelIndex != otherOwner->s.number)
{
return qfalse;
}
didHit = qtrue;
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
if (dmg <= SABER_NONATTACK_DAMAGE)
{
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
}
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
te->s.eventParm = 1;
sabersClashed = qtrue;
//WP_SaberBlockNonRandom(self, tr.endpos, qfalse);
blockStuff:
otherUnblockable = qfalse;
if (otherOwner && otherOwner->client && otherOwner->client->ps.saberInFlight)
{
return qfalse;
}
if (dmg > SABER_NONATTACK_DAMAGE && !unblockable && !otherUnblockable)
{
int lockFactor = g_saberLockFactor.integer;
if (sabersClashed && Q_irand(1, 20) <= lockFactor)
{
if (!G_ClientIdleInWorld(otherOwner))
{
if (WP_SabersCheckLock(self, otherOwner))
{
self->client->ps.saberBlocked = BLOCKED_NONE;
otherOwner->client->ps.saberBlocked = BLOCKED_NONE;
return didHit;
}
}
}
}
if (!otherOwner || !otherOwner->client)
{
return didHit;
}
/*
if (otherOwner->client->ps.saberMove == LS_A_BACK ||
otherOwner->client->ps.saberMove == LS_A_BACK_CR ||
otherOwner->client->ps.saberMove == LS_A_BACKSTAB ||
otherOwner->client->ps.saberMove == LS_A_JUMP_T__B_)
*/
if (BG_SaberInSpecial(otherOwner->client->ps.saberMove))
{
otherUnblockable = qtrue;
otherOwner->client->ps.saberBlocked = 0;
}
if ( sabersClashed &&
dmg > SABER_NONATTACK_DAMAGE &&
self->client->ps.fd.saberAnimLevel < FORCE_LEVEL_3 &&
/*PM_SaberInParry(otherOwner->client->ps.saberMove) &&*/
!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
!PM_SaberInParry(self->client->ps.saberMove) &&
!PM_SaberInBrokenParry(self->client->ps.saberMove) &&
!BG_SaberInSpecial(self->client->ps.saberMove) &&
!PM_SaberInBounce(self->client->ps.saberMove) &&
!PM_SaberInDeflect(self->client->ps.saberMove) &&
!PM_SaberInReflect(self->client->ps.saberMove) &&
!unblockable /*&&
Q_irand(1, 10) <= 7*/)
{
//if (Q_irand(1, 10) <= 6)
if (1) //for now, just always try a deflect. (deflect func can cause bounces too)
{
if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction))
{
tryDeflectAgain = qtrue; //Failed the deflect, try it again if we can if the guy we're smashing goes into a parry and we don't break it
}
else
{
self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
didOffense = qtrue;
}
}
else
{
self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
didOffense = qtrue;
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i clashed into client %i's saber, did BLOCKED_ATK_BOUNCE\n", self->s.number, otherOwner->s.number);
}
}
}
if ( ((self->client->ps.fd.saberAnimLevel < FORCE_LEVEL_3 && ((tryDeflectAgain && Q_irand(1, 10) <= 3) || (!tryDeflectAgain && Q_irand(1, 10) <= 7))) || (Q_irand(1, 10) <= 1 && otherOwner->client->ps.fd.saberAnimLevel >= FORCE_LEVEL_3))
/*&& PM_SaberInParry(otherOwner->client->ps.saberMove)*/
&& !PM_SaberInBounce(self->client->ps.saberMove)
&& !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove)
&& !BG_SaberInSpecial(otherOwner->client->ps.saberMove)
&& !PM_SaberInBounce(otherOwner->client->ps.saberMove)
&& !PM_SaberInDeflect(otherOwner->client->ps.saberMove)
&& !PM_SaberInReflect(otherOwner->client->ps.saberMove)
&& (otherOwner->client->ps.fd.saberAnimLevel > FORCE_LEVEL_2 || ( otherOwner->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] >= 3 && Q_irand(0, otherOwner->client->ps.fd.saberAnimLevel) ))
&& !unblockable
&& !otherUnblockable
&& dmg > SABER_NONATTACK_DAMAGE
&& !didOffense) //don't allow the person we're attacking to do this if we're making an unblockable attack
{//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med. In MP, we also randomly decide this for level 3 attacks.
//Going to go ahead and let idle damage do simple knockaways. Looks sort of good that way.
//turn the parry into a knockaway
if (!PM_SaberInParry(otherOwner->client->ps.saberMove))
{
WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
otherOwner->client->ps.saberMove = BG_KnockawayForParry( otherOwner->client->ps.saberBlocked );
otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
}
else
{
otherOwner->client->ps.saberMove = G_KnockawayForParry(otherOwner->client->ps.saberMove); //BG_KnockawayForParry( otherOwner->client->ps.saberBlocked );
otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
}
//make them (me) go into a broken parry
self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove );
self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;//BLOCKED_PARRY_BROKEN;
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i sent client %i into a reflected attack with a knockaway\n", otherOwner->s.number, self->s.number);
}
didDefense = qtrue;
}
else if ((self->client->ps.fd.saberAnimLevel > FORCE_LEVEL_2 || unblockable) && //if we're doing a special attack, we can send them into a broken parry too (MP only)
( otherOwner->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] < self->client->ps.fd.saberAnimLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] == self->client->ps.fd.saberAnimLevel && (Q_irand(1, 10) >= otherOwner->client->ps.fd.saberAnimLevel*3 || unblockable)) ) &&
PM_SaberInParry(otherOwner->client->ps.saberMove) &&
!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
!PM_SaberInParry(self->client->ps.saberMove) &&
!PM_SaberInBrokenParry(self->client->ps.saberMove) &&
!PM_SaberInBounce(self->client->ps.saberMove) &&
dmg > SABER_NONATTACK_DAMAGE &&
!didOffense &&
!otherUnblockable)
{ //they are in a parry, and we are slamming down on them with a move of equal or greater force than their defense, so send them into a broken parry.. unless they are already in one.
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i sent client %i into a broken parry\n", self->s.number, otherOwner->s.number);
}
otherOwner->client->ps.saberMove = BG_BrokenParryForParry( otherOwner->client->ps.saberMove );
otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
didDefense = qtrue;
}
else if ((self->client->ps.fd.saberAnimLevel > FORCE_LEVEL_2) && //if we're doing a special attack, we can send them into a broken parry too (MP only)
//( otherOwner->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] < self->client->ps.fd.saberAnimLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] == self->client->ps.fd.saberAnimLevel && (Q_irand(1, 10) >= otherOwner->client->ps.fd.saberAnimLevel*3 || unblockable)) ) &&
otherOwner->client->ps.fd.saberAnimLevel >= FORCE_LEVEL_3 &&
PM_SaberInParry(otherOwner->client->ps.saberMove) &&
!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
!PM_SaberInParry(self->client->ps.saberMove) &&
!PM_SaberInBrokenParry(self->client->ps.saberMove) &&
!PM_SaberInBounce(self->client->ps.saberMove) &&
!PM_SaberInDeflect(self->client->ps.saberMove) &&
!PM_SaberInReflect(self->client->ps.saberMove) &&
dmg > SABER_NONATTACK_DAMAGE &&
!didOffense &&
!unblockable)
{ //they are in a parry, and we are slamming down on them with a move of equal or greater force than their defense, so send them into a broken parry.. unless they are already in one.
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i bounced off of client %i's saber\n", self->s.number, otherOwner->s.number);
}
if (!tryDeflectAgain)
{
if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction))
{
tryDeflectAgain = qtrue;
}
}
didOffense = qtrue;
}
else if (SaberAttacking(otherOwner) && dmg > SABER_NONATTACK_DAMAGE && !BG_SaberInSpecial(otherOwner->client->ps.saberMove) && !didOffense && !otherUnblockable)
{ //they were attacking and our saber hit their saber, make them bounce. But if they're in a special attack, leave them.
if (!PM_SaberInBounce(self->client->ps.saberMove) &&
!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
!PM_SaberInDeflect(self->client->ps.saberMove) &&
!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
!PM_SaberInReflect(self->client->ps.saberMove) &&
!PM_SaberInReflect(otherOwner->client->ps.saberMove))
{
// Com_Printf("BLING\n");
/*
WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction);
if (SaberAttacking(self) && !unblockable && !BG_SaberInSpecial(otherOwner->client->ps.saberMove))
{ //make them bounce/deflect
WP_GetSaberDeflectionAngle(otherOwner, self, tr.fraction);
didDefense = qtrue;
}
*/
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i and client %i bounced off of each other's sabers\n", self->s.number, otherOwner->s.number);
}
self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
didOffense = qtrue;
// didDefense = qtrue;
}
}
#ifdef G2_COLLISION_ENABLED
if (g_saberGhoul2Collision.integer && !didDefense && dmg <= SABER_NONATTACK_DAMAGE && !otherUnblockable) //with perpoly, it looks pretty weird to have clash flares coming off the guy's face and whatnot
{
if (!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
!BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
!PM_SaberInReflect(otherOwner->client->ps.saberMove))
{
WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
}
}
else if (!didDefense && dmg > SABER_NONATTACK_DAMAGE && !otherUnblockable) //if not more than idle damage, don't even bother blocking.
#else
if (!didDefense && dmg > SABER_NONATTACK_DAMAGE && !otherUnblockable) //if not more than idle damage, don't even bother blocking.
#endif
{ //block
if (!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
!BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
!PM_SaberInReflect(otherOwner->client->ps.saberMove))
{
qboolean crushTheParry = qfalse;
if (unblockable)
{ //It's unblockable. So send us into a broken parry immediately.
crushTheParry = qtrue;
}
if (!SaberAttacking(otherOwner))
{
WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
}
else if (self->client->ps.fd.saberAnimLevel > otherOwner->client->ps.fd.saberAnimLevel ||
(self->client->ps.fd.saberAnimLevel == otherOwner->client->ps.fd.saberAnimLevel && Q_irand(1, 10) <= 2))
{ //they are attacking, and we managed to make them break
//Give them a parry, so we can later break it.
WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
crushTheParry = qtrue;
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i forced client %i into a broken parry with a stronger attack\n", self->s.number, otherOwner->s.number);
}
}
else
{ //They are attacking, so are we, and obviously they have an attack level higher than or equal to ours
if (self->client->ps.fd.saberAnimLevel == otherOwner->client->ps.fd.saberAnimLevel)
{ //equal level, try to bounce off each other's sabers
if (!didOffense &&
!PM_SaberInParry(self->client->ps.saberMove) &&
!PM_SaberInBrokenParry(self->client->ps.saberMove) &&
!BG_SaberInSpecial(self->client->ps.saberMove) &&
!PM_SaberInBounce(self->client->ps.saberMove) &&
!PM_SaberInDeflect(self->client->ps.saberMove) &&
!PM_SaberInReflect(self->client->ps.saberMove) &&
!unblockable)
{
/*
if (WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction))
{
didOffense = qtrue;
}
*/
self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
didOffense = qtrue;
}
if (!didDefense &&
!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
!BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
!PM_SaberInReflect(otherOwner->client->ps.saberMove) &&
!unblockable)
{
//WP_GetSaberDeflectionAngle(otherOwner, self, tr.fraction);
otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
}
if (g_saberDebugPrint.integer)
{
Com_Printf("Equal attack level bounce/deflection for clients %i and %i\n", self->s.number, otherOwner->s.number);
}
}
else if ((level.time - otherOwner->client->lastSaberStorageTime) < 500 && !unblockable) //make sure the stored saber data is updated
{ //They are higher, this means they can actually smash us into a broken parry
/*
vec3_t saberMidPoint;
//Base the block point off of their saber's mid point
saberMidPoint[0] = otherOwner->client->lastSaberBase_Always[0] + otherOwner->client->lastSaberDir_Always[0]*20;
saberMidPoint[0] = otherOwner->client->lastSaberBase_Always[1] + otherOwner->client->lastSaberDir_Always[1]*20;
saberMidPoint[0] = otherOwner->client->lastSaberBase_Always[2] + otherOwner->client->lastSaberDir_Always[2]*20;
//Send us into a parry
WP_SaberBlockNonRandom(self, saberMidPoint, qfalse);
//And then break the parry
if (PM_SaberInParry(G_GetParryForBlock(self->client->ps.saberBlocked)))
{
self->client->ps.saberMove = BG_BrokenParryForParry( G_GetParryForBlock(self->client->ps.saberBlocked) );
self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
}
*/
//Using reflected anims instead now
self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove);
self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i hit client %i's stronger attack, was forced into a broken parry\n", self->s.number, otherOwner->s.number);
}
didOffense = qtrue;
}
}
if (crushTheParry && PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked)))
{ //This means that the attack actually hit our saber, and we went to block it.
//But, one of the above cases says we actually can't. So we will be smashed into a broken parry instead.
otherOwner->client->ps.saberMove = BG_BrokenParryForParry( G_GetParryForBlock(otherOwner->client->ps.saberBlocked) );
otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i broke through %i's parry with a special or stronger attack\n", self->s.number, otherOwner->s.number);
}
}
else if (PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked)) && !didOffense && tryDeflectAgain)
{ //We want to try deflecting again because the other is in the parry and we haven't made any new moves
int preMove = otherOwner->client->ps.saberMove;
otherOwner->client->ps.saberMove = G_GetParryForBlock(otherOwner->client->ps.saberBlocked);
WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction);
otherOwner->client->ps.saberMove = preMove;
}
}
}
self->client->ps.saberAttackWound = level.time + g_saberDmgDelay_Wound.integer;
}
return didHit;
}
#define MIN_SABER_SLICE_DISTANCE 50
#define MIN_SABER_SLICE_RETURN_DISTANCE 30
#define SABER_THROWN_HIT_DAMAGE 30
#define SABER_THROWN_RETURN_HIT_DAMAGE 5
void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace);
qboolean CheckThrownSaberDamaged(gentity_t *saberent, gentity_t *saberOwner, gentity_t *ent, int dist, int returning)
{
vec3_t vecsub;
float veclen;
gentity_t *te;
if (saberOwner && saberOwner->client && saberOwner->client->ps.saberAttackWound > level.time)
{
return qfalse;
}
if (ent && ent->client && ent->inuse && ent->s.number != saberOwner->s.number &&
ent->health > 0 && ent->takedamage &&
trap_InPVS(ent->client->ps.origin, saberent->r.currentOrigin) &&
ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
ent->client->pers.connected)
{
if (ent->inuse && ent->client &&
ent->client->ps.duelInProgress &&
ent->client->ps.duelIndex != saberOwner->s.number)
{
return qfalse;
}
if (ent->inuse && ent->client &&
saberOwner->client->ps.duelInProgress &&
saberOwner->client->ps.duelIndex != ent->s.number)
{
return qfalse;
}
VectorSubtract(saberent->r.currentOrigin, ent->client->ps.origin, vecsub);
veclen = VectorLength(vecsub);
if (veclen < dist)
{
trace_t tr;
trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, ent->client->ps.origin, saberent->s.number, MASK_SHOT);
/*
if (tr.startsolid || tr.allsolid)
{
if (!returning)
{ //return to owner if startsolid
thrownSaberTouch(saberent, saberent, NULL);
}
return qfalse;
}
*/
if (tr.fraction == 1 || tr.entityNum == ent->s.number)
{ //Slice them
if (!saberOwner->client->ps.isJediMaster && WP_SaberCanBlock(ent, tr.endpos, 0, MOD_SABER, qfalse, 8))
{
WP_SaberBlockNonRandom(ent, tr.endpos, qfalse);
te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
{
te->s.angles[1] = 1;
}
te->s.eventParm = 1;
if (!returning)
{ //return to owner if blocked
thrownSaberTouch(saberent, saberent, NULL);
}
saberOwner->client->ps.saberAttackWound = level.time + 500;
return qfalse;
}
else
{
vec3_t dir;
VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
VectorNormalize(dir);
if (!dir[0] && !dir[1] && !dir[2])
{
dir[1] = 1;
}
if (saberOwner->client->ps.isJediMaster)
{ //2x damage for the Jedi Master
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage*2, 0, MOD_SABER);
}
else
{
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage, 0, MOD_SABER);
}
te = G_TempEntity( tr.endpos, EV_SABER_HIT );
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
{
te->s.angles[1] = 1;
}
te->s.eventParm = 1;
if (!returning)
{ //return to owner if blocked
thrownSaberTouch(saberent, saberent, NULL);
}
}
saberOwner->client->ps.saberAttackWound = level.time + 500;
}
}
}
else if (ent && !ent->client && ent->inuse && ent->takedamage && ent->health > 0 && ent->s.number != saberOwner->s.number &&
ent->s.number != saberent->s.number && trap_InPVS(ent->r.currentOrigin, saberent->r.currentOrigin))
{
VectorSubtract(saberent->r.currentOrigin, ent->r.currentOrigin, vecsub);
veclen = VectorLength(vecsub);
if (veclen < dist)
{
trace_t tr;
trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, ent->r.currentOrigin, saberent->s.number, MASK_SHOT);
if (tr.fraction == 1 || tr.entityNum == ent->s.number)
{
vec3_t dir;
VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
VectorNormalize(dir);
if (ent->s.eType == ET_GRAPPLE)
{
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 40, 0, MOD_SABER);
}
else
{
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 5, 0, MOD_SABER);
}
te = G_TempEntity( tr.endpos, EV_SABER_HIT );
VectorCopy(tr.endpos, te->s.origin);
VectorCopy(tr.plane.normal, te->s.angles);
if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
{
te->s.angles[1] = 1;
}
te->s.eventParm = 1;
if (!returning)
{ //return to owner if blocked
thrownSaberTouch(saberent, saberent, NULL);
}
saberOwner->client->ps.saberAttackWound = level.time + 500;
}
}
}
return qtrue;
}
void saberCheckRadiusDamage(gentity_t *saberent, int returning)
{ //we're going to cheat and damage players within the saber's radius, just for the sake of doing things more "efficiently"
int i = 0;
int dist = 0;
gentity_t *ent;
gentity_t *saberOwner = &g_entities[saberent->r.ownerNum];
if (returning && returning != 2)
{
dist = MIN_SABER_SLICE_RETURN_DISTANCE;
}
else
{
dist = MIN_SABER_SLICE_DISTANCE;
}
if (!saberOwner || !saberOwner->client)
{
return;
}
if (saberOwner->client->ps.saberAttackWound > level.time)
{
return;
}
while (i < /*MAX_CLIENTS*/MAX_GENTITIES)
{
ent = &g_entities[i];
CheckThrownSaberDamaged(saberent, saberOwner, ent, dist, returning);
i++;
}
}
//#define THROWN_SABER_COMP
void saberMoveBack( gentity_t *ent, qboolean goingBack )
{
vec3_t origin, oldOrg;
ent->s.pos.trType = TR_LINEAR;
VectorCopy( ent->r.currentOrigin, oldOrg );
// get current position
BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
//Get current angles?
BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles );
//compensation test code..
#ifdef THROWN_SABER_COMP
if (!goingBack)
{ //acts as a fallback in case touch code fails, keeps saber from going through things between predictions
float originalLength = 0;
int iCompensationLength = 32;
trace_t tr;
vec3_t mins, maxs;
vec3_t calcComp, compensatedOrigin;
VectorSet( mins, -24.0f, -24.0f, -8.0f );
VectorSet( maxs, 24.0f, 24.0f, 8.0f );
VectorSubtract(origin, oldOrg, calcComp);
originalLength = VectorLength(calcComp);
VectorNormalize(calcComp);
compensatedOrigin[0] = oldOrg[0] + calcComp[0]*(originalLength+iCompensationLength);
compensatedOrigin[1] = oldOrg[1] + calcComp[1]*(originalLength+iCompensationLength);
compensatedOrigin[2] = oldOrg[2] + calcComp[2]*(originalLength+iCompensationLength);
trap_Trace(&tr, oldOrg, mins, maxs, compensatedOrigin, ent->r.ownerNum, MASK_PLAYERSOLID);
if ((tr.fraction != 1 || tr.startsolid || tr.allsolid) && tr.entityNum != ent->r.ownerNum)
{
VectorClear(ent->s.pos.trDelta);
//Unfortunately doing this would defeat the purpose of the compensation. We will have to settle for a jerk on the client.
//VectorCopy( origin, ent->r.currentOrigin );
CheckThrownSaberDamaged(ent, &g_entities[ent->r.ownerNum], &g_entities[tr.entityNum], 256, 0);
tr.startsolid = 0;
thrownSaberTouch(ent, &g_entities[tr.entityNum], &tr);
return;
}
}
#endif
VectorCopy( origin, ent->r.currentOrigin );
}
void SaberBounceSound( gentity_t *self, gentity_t *other, trace_t *trace )
{
VectorCopy(self->r.currentAngles, self->s.apos.trBase);
self->s.apos.trBase[PITCH] = 90;
}
void DeadSaberThink(gentity_t *saberent)
{
if (saberent->speed < level.time)
{
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
G_RunObject(saberent);
}
void MakeDeadSaber(gentity_t *ent)
{ //spawn a "dead" saber entity here so it looks like the saber fell out of the air.
//This entity will remove itself after a very short time period.
vec3_t startorg;
vec3_t startang;
gentity_t *saberent;
if (g_gametype.integer == GT_JEDIMASTER)
{ //never spawn a dead saber in JM, because the only saber on the level is really a world object
G_Sound(ent, CHAN_AUTO, saberOffSound);
return;
}
saberent = G_Spawn();
VectorCopy(ent->r.currentOrigin, startorg);
VectorCopy(ent->r.currentAngles, startang);
saberent->classname = "deadsaber";
saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
saberent->r.ownerNum = ent->s.number;
saberent->clipmask = MASK_PLAYERSOLID;
saberent->r.contents = CONTENTS_TRIGGER;//0;
VectorSet( saberent->r.mins, -3.0f, -3.0f, -3.0f );
VectorSet( saberent->r.maxs, 3.0f, 3.0f, 3.0f );
//saberent->mass = 10;
saberent->touch = SaberBounceSound;
saberent->think = DeadSaberThink;
saberent->nextthink = level.time;
VectorCopy(startorg, saberent->s.pos.trBase);
VectorCopy(startang, saberent->s.apos.trBase);
VectorCopy(startorg, saberent->s.origin);
VectorCopy(startang, saberent->s.angles);
VectorCopy(startorg, saberent->r.currentOrigin);
VectorCopy(startang, saberent->r.currentAngles);
saberent->s.apos.trType = TR_GRAVITY;
saberent->s.apos.trDelta[0] = Q_irand(200, 800);
saberent->s.apos.trDelta[1] = Q_irand(200, 800);
saberent->s.apos.trDelta[2] = Q_irand(200, 800);
saberent->s.apos.trTime = level.time-50;
saberent->s.pos.trType = TR_GRAVITY;
saberent->s.pos.trTime = level.time-50;
saberent->s.eFlags = EF_BOUNCE_HALF;
saberent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm");
saberent->s.modelGhoul2 = 1;
saberent->s.g2radius = 20;
saberent->s.eType = ET_MISSILE;
saberent->s.weapon = WP_SABER;
saberent->speed = level.time + 4000;
saberent->bounceCount = 12;
//fall off in the direction the real saber was headed
VectorCopy(ent->s.pos.trDelta, saberent->s.pos.trDelta);
saberMoveBack(saberent, qtrue);
saberent->s.pos.trType = TR_GRAVITY;
trap_LinkEntity(saberent);
}
void saberBackToOwner(gentity_t *saberent)
{
gentity_t *saberOwner = &g_entities[saberent->r.ownerNum];
vec3_t dir;
float ownerLen;
if (saberent->r.ownerNum == ENTITYNUM_NONE)
{
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
if (!g_entities[saberent->r.ownerNum].inuse ||
!g_entities[saberent->r.ownerNum].client ||
g_entities[saberent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR)
{
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
if (g_entities[saberent->r.ownerNum].health < 1 || !g_entities[saberent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABERATTACK] || !g_entities[saberent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABERTHROW])
{ //He's dead, just go back to our normal saber status
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->nextthink = level.time;
MakeDeadSaber(saberent);
saberent->r.svFlags |= (SVF_NOCLIENT);
saberent->r.contents = CONTENTS_LIGHTSABER;
VectorSet( saberent->r.mins, -SABER_BOX_SIZE, -SABER_BOX_SIZE, -SABER_BOX_SIZE );
VectorSet( saberent->r.maxs, SABER_BOX_SIZE, SABER_BOX_SIZE, SABER_BOX_SIZE );
saberent->s.loopSound = 0;
g_entities[saberent->r.ownerNum].client->ps.saberInFlight = qfalse;
g_entities[saberent->r.ownerNum].client->ps.saberThrowDelay = level.time + 500;
g_entities[saberent->r.ownerNum].client->ps.saberCanThrow = qfalse;
return;
}
saberent->r.contents = CONTENTS_LIGHTSABER;
//saberent->s.apos.trDelta[1] = 0;
VectorSubtract(saberent->pos1, saberent->r.currentOrigin, dir);
ownerLen = VectorLength(dir);
if (saberent->speed < level.time)
{
VectorNormalize(dir);
saberMoveBack(saberent, qtrue);
VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
if (g_entities[saberent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
{ //allow players with high saber throw rank to control the return speed of the saber
if (g_entities[saberent->r.ownerNum].client->buttons & BUTTON_ATTACK)
{
VectorScale(dir, 1200, saberent->s.pos.trDelta );
saberent->speed = level.time + 50;
}
else
{
VectorScale(dir, 700, saberent->s.pos.trDelta );
saberent->speed = level.time + 200;
}
}
else
{
VectorScale(dir, 700, saberent->s.pos.trDelta );
saberent->speed = level.time + 200;
}
saberent->s.pos.trTime = level.time;
}
if (ownerLen <= 512)
{
saberent->s.saberInFlight = qfalse;
saberent->s.loopSound = saberHumSound;
}
if (ownerLen <= 32)
{
saberOwner->client->ps.saberInFlight = qfalse;
saberOwner->client->ps.saberCanThrow = qfalse;
saberOwner->client->ps.saberThrowDelay = level.time + 300;
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->nextthink = level.time + 50;
return;
}
if (!saberent->s.saberInFlight)
{
saberCheckRadiusDamage(saberent, 1);
}
else
{
saberCheckRadiusDamage(saberent, 2);
}
saberMoveBack(saberent, qtrue);
//G_RunObject(saberent);
saberent->nextthink = level.time;
}
void saberFirstThrown(gentity_t *saberent);
void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace)
{
gentity_t *hitEnt = other;
if (other && other->s.number == saberent->r.ownerNum)
{
return;
}
VectorClear(saberent->s.pos.trDelta);
saberent->s.pos.trTime = level.time;
saberent->s.apos.trType = TR_LINEAR;
saberent->s.apos.trDelta[0] = 0;
saberent->s.apos.trDelta[1] = 800;
saberent->s.apos.trDelta[2] = 0;
VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
//saberent->damage = SABER_THROWN_RETURN_HIT_DAMAGE;
saberent->think = saberBackToOwner;
saberent->nextthink = level.time;
//saberCheckRadiusDamage(saberent, 2);
if (other && other->r.ownerNum < MAX_CLIENTS &&
(other->r.contents & CONTENTS_LIGHTSABER) &&
g_entities[other->r.ownerNum].client &&
g_entities[other->r.ownerNum].inuse)
{
hitEnt = &g_entities[other->r.ownerNum];
}
CheckThrownSaberDamaged(saberent, &g_entities[saberent->r.ownerNum], hitEnt, 256, 0);
saberent->speed = 0;
}
#define SABER_MAX_THROW_DISTANCE 700
void saberFirstThrown(gentity_t *saberent)
{
vec3_t vSub;
float vLen;
gentity_t *saberOwn = &g_entities[saberent->r.ownerNum];
if (saberent->r.ownerNum == ENTITYNUM_NONE)
{
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
if (!saberOwn ||
!saberOwn->inuse ||
!saberOwn->client ||
saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR)
{
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
if (saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABERATTACK] || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW])
{ //He's dead, just go back to our normal saber status
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->nextthink = level.time;
MakeDeadSaber(saberent);
saberent->r.svFlags |= (SVF_NOCLIENT);
saberent->r.contents = CONTENTS_LIGHTSABER;
VectorSet( saberent->r.mins, -SABER_BOX_SIZE, -SABER_BOX_SIZE, -SABER_BOX_SIZE );
VectorSet( saberent->r.maxs, SABER_BOX_SIZE, SABER_BOX_SIZE, SABER_BOX_SIZE );
saberent->s.loopSound = 0;
saberOwn->client->ps.saberInFlight = qfalse;
saberOwn->client->ps.saberThrowDelay = level.time + 500;
saberOwn->client->ps.saberCanThrow = qfalse;
return;
}
if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 500)
{
if (!(saberOwn->client->buttons & BUTTON_ALT_ATTACK))
{ //If owner releases altattack 500ms or later after throwing saber, it autoreturns
thrownSaberTouch(saberent, saberent, NULL);
goto runMin;
}
else if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 6000)
{ //if it's out longer than 6 seconds, return it
thrownSaberTouch(saberent, saberent, NULL);
goto runMin;
}
}
if (BG_HasYsalamiri(g_gametype.integer, &saberOwn->client->ps))
{
thrownSaberTouch(saberent, saberent, NULL);
goto runMin;
}
if (!BG_CanUseFPNow(g_gametype.integer, &saberOwn->client->ps, level.time, FP_SABERTHROW))
{
thrownSaberTouch(saberent, saberent, NULL);
goto runMin;
}
VectorSubtract(saberOwn->client->ps.origin, saberent->r.currentOrigin, vSub);
vLen = VectorLength(vSub);
if (vLen >= (SABER_MAX_THROW_DISTANCE*saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW]))
{
thrownSaberTouch(saberent, saberent, NULL);
goto runMin;
}
if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_2 &&
saberent->speed < level.time)
{ //if owner is rank 3 in saber throwing, the saber goes where he points
vec3_t fwd, traceFrom, traceTo, dir;
trace_t tr;
AngleVectors(saberOwn->client->ps.viewangles, fwd, 0, 0);
VectorCopy(saberOwn->client->ps.origin, traceFrom);
traceFrom[2] += saberOwn->client->ps.viewheight;
VectorCopy(traceFrom, traceTo);
traceTo[0] += fwd[0]*4096;
traceTo[1] += fwd[1]*4096;
traceTo[2] += fwd[2]*4096;
saberMoveBack(saberent, qfalse);
VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
{ //if highest saber throw rank, we can direct the saber toward players directly by looking at them
trap_Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_PLAYERSOLID);
}
else
{
trap_Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_SOLID);
}
//G_TestLine(traceFrom, tr.endpos, 0x000000ff, 100);
//if (tr.fraction != 1)
{
VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
VectorNormalize(dir);
VectorScale(dir, 500, saberent->s.pos.trDelta );
saberent->s.pos.trTime = level.time;
if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
{ //we'll treat them to a quicker update rate if their throw rank is high enough
saberent->speed = level.time + 100;
}
else
{
saberent->speed = level.time + 400;
}
}
}
runMin:
saberCheckRadiusDamage(saberent, 0);
G_RunObject(saberent);
}
/*
void G_DebugDirection(vec3_t base, vec3_t dir)
{
vec3_t dirPoint;
dirPoint[0] = base[0] + dir[0]*64;
dirPoint[1] = base[1] + dir[1]*64;
dirPoint[2] = base[2] + dir[2]*64;
G_TestLine(base, dirPoint, 0x0000ff, 100);
}
*/
void WP_SaberPositionUpdate( gentity_t *self, usercmd_t *ucmd )
{ //rww - keep the saber position as updated as possible on the server so that we can try to do realistic-looking contact stuff
mdxaBone_t boltMatrix;
vec3_t properAngles, properOrigin;
vec3_t boltAngles, boltOrigin;
vec3_t end;
vec3_t legAxis[3];
vec3_t addVel;
vec3_t rawAngles;
float fVSpeed = 0;
int f;
int torsoAnim;
int legsAnim;
int returnAfterUpdate = 0;
float animSpeedScale = 1;
qboolean setTorso = qfalse;
if (self && self->inuse && self->client)
{
if (self->client->saberCycleQueue)
{
self->client->ps.fd.saberDrawAnimLevel = self->client->saberCycleQueue;
}
else
{
self->client->ps.fd.saberDrawAnimLevel = self->client->ps.fd.saberAnimLevel;
}
}
if (self &&
self->inuse &&
self->client &&
self->client->saberCycleQueue &&
(self->client->ps.weaponTime <= 0 || self->health < 1))
{ //we cycled attack levels while we were busy, so update now that we aren't (even if that means we're dead)
self->client->ps.fd.saberAnimLevel = self->client->saberCycleQueue;
self->client->saberCycleQueue = 0;
}
if (!self ||
!self->client ||
!self->client->ghoul2 ||
!g2SaberInstance ||
self->health < 1)
{
return;
}
if (self->client->ps.usingATST)
{ //we don't update the server's G2 instance in the case of ATST use, so..
return;
}
if (self->client->ps.weapon != WP_SABER ||
self->client->ps.weaponstate == WEAPON_RAISING ||
self->client->ps.weaponstate == WEAPON_DROPPING)
{
returnAfterUpdate = 1;
//return;
}
if (self->client->ps.saberThrowDelay < level.time)
{
self->client->ps.saberCanThrow = qtrue;
}
if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE))
{
animSpeedScale = 2;
}
torsoAnim = (self->client->ps.torsoAnim & ~ANIM_TOGGLEBIT );
legsAnim = (self->client->ps.legsAnim & ~ANIM_TOGGLEBIT );
VectorCopy(self->client->ps.origin, properOrigin);
VectorCopy(self->client->ps.viewangles, properAngles);
//try to predict the origin based on velocity so it's more like what the client is seeing
VectorCopy(self->client->ps.velocity, addVel);
VectorNormalize(addVel);
if (self->client->ps.velocity[0] < 0)
{
fVSpeed += (-self->client->ps.velocity[0]);
}
else
{
fVSpeed += self->client->ps.velocity[0];
}
if (self->client->ps.velocity[1] < 0)
{
fVSpeed += (-self->client->ps.velocity[1]);
}
else
{
fVSpeed += self->client->ps.velocity[1];
}
if (self->client->ps.velocity[2] < 0)
{
fVSpeed += (-self->client->ps.velocity[2]);
}
else
{
fVSpeed += self->client->ps.velocity[2];
}
fVSpeed *= 0.08;
properOrigin[0] += addVel[0]*fVSpeed;
properOrigin[1] += addVel[1]*fVSpeed;
properOrigin[2] += addVel[2]*fVSpeed;
properAngles[0] = 0;
properAngles[1] = self->client->ps.viewangles[YAW];
properAngles[2] = 0;
AnglesToAxis( properAngles, legAxis );
G_G2PlayerAngles( self, legAxis, properAngles );
if (returnAfterUpdate)
{ //We don't even need to do GetBoltMatrix if we're only in here to keep the g2 server instance in sync
goto finalUpdate;
}
trap_G2API_GetBoltMatrix(self->client->ghoul2, 1, 0, &boltMatrix, properAngles, properOrigin, level.time, NULL, vec3_origin);
boltOrigin[0] = boltMatrix.matrix[0][3];
boltOrigin[1] = boltMatrix.matrix[1][3];
boltOrigin[2] = boltMatrix.matrix[2][3];
boltAngles[0] = -boltMatrix.matrix[0][1];
boltAngles[1] = -boltMatrix.matrix[1][1];
boltAngles[2] = -boltMatrix.matrix[2][1];
//immediately store these values so we don't have to recalculate this again
VectorCopy(boltOrigin, self->client->lastSaberBase_Always);
VectorCopy(boltOrigin, self->client->lastSaberDir_Always);
self->client->lastSaberStorageTime = level.time;
VectorCopy(boltAngles, rawAngles);
VectorMA( boltOrigin, 40, boltAngles, end );
if (self->client->ps.saberEntityNum)
{
gentity_t *mySaber = &g_entities[self->client->ps.saberEntityNum];
//I guess it's good to keep the position updated even when contents are 0
if (mySaber && ((mySaber->r.contents & CONTENTS_LIGHTSABER) || mySaber->r.contents == 0) && !self->client->ps.saberInFlight)
{ //place it roughly in the middle of the saber..
VectorMA( boltOrigin, 20, boltAngles, mySaber->r.currentOrigin );
if (self->client->ps.dualBlade)
{
VectorCopy(boltOrigin, mySaber->r.currentOrigin);
}
//VectorCopy(/*boltOrigin*/end, mySaber->r.currentOrigin);
}
}
boltAngles[YAW] = self->client->ps.viewangles[YAW];
//G_TestLine(boltOrigin, end, 0x000000ff, 50);
if (self->client->ps.saberInFlight)
{ //do the thrown-saber stuff
gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
if (saberent)
{
if (!self->client->ps.saberEntityState)
{
vec3_t startorg, startang, dir;
VectorCopy(boltOrigin, saberent->r.currentOrigin);
VectorCopy(boltOrigin, startorg);
VectorCopy(boltAngles, startang);
//startang[0] = 90;
//Instead of this we'll sort of fake it and slowly tilt it down on the client via
//a perframe method
saberent->r.svFlags &= ~(SVF_NOCLIENT);
VectorCopy(startorg, saberent->s.pos.trBase);
VectorCopy(startang, saberent->s.apos.trBase);
VectorCopy(startorg, saberent->s.origin);
VectorCopy(startang, saberent->s.angles);
saberent->s.saberInFlight = qtrue;
saberent->s.apos.trType = TR_LINEAR;
saberent->s.apos.trDelta[0] = 0;
saberent->s.apos.trDelta[1] = 800;
saberent->s.apos.trDelta[2] = 0;
saberent->s.pos.trType = TR_LINEAR;
saberent->s.eType = ET_GENERAL;
saberent->s.eFlags = 0;
saberent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm");
saberent->s.modelGhoul2 = 127;
saberent->parent = self;
self->client->ps.saberEntityState = 1;
//Projectile stuff:
AngleVectors(self->client->ps.viewangles, dir, NULL, NULL);
saberent->nextthink = level.time + FRAMETIME;
saberent->think = saberFirstThrown;//G_RunObject;
saberent->damage = SABER_THROWN_HIT_DAMAGE;
saberent->methodOfDeath = MOD_SABER;
saberent->splashMethodOfDeath = MOD_SABER;
saberent->s.solid = 2;
saberent->r.contents = CONTENTS_LIGHTSABER;
VectorSet( saberent->r.mins, -24.0f, -24.0f, -8.0f );
VectorSet( saberent->r.maxs, 24.0f, 24.0f, 8.0f );
saberent->s.genericenemyindex = self->s.number+1024;
saberent->touch = thrownSaberTouch;
saberent->s.weapon = WP_SABER;
VectorScale(dir, 400, saberent->s.pos.trDelta );
saberent->s.pos.trTime = level.time;
saberent->s.loopSound = saberSpinSound;
self->client->ps.saberDidThrowTime = level.time;
self->client->dangerTime = level.time;
self->client->ps.eFlags &= ~EF_INVULNERABLE;
self->client->invulnerableTimer = 0;
trap_LinkEntity(saberent);
}
else
{
VectorCopy(boltOrigin, saberent->pos1);
trap_LinkEntity(saberent);
}
}
}
else if (!self->client->ps.saberHolstered)
{
gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
if (saberent)
{
saberent->r.svFlags |= (SVF_NOCLIENT);
saberent->r.contents = CONTENTS_LIGHTSABER;
VectorSet( saberent->r.mins, -SABER_BOX_SIZE, -SABER_BOX_SIZE, -SABER_BOX_SIZE );
VectorSet( saberent->r.maxs, SABER_BOX_SIZE, SABER_BOX_SIZE, SABER_BOX_SIZE );
saberent->s.loopSound = 0;
}
if (self->client->ps.saberLockTime > level.time && self->client->ps.saberEntityNum)
{
if (self->client->ps.saberIdleWound < level.time)
{
gentity_t *te;
vec3_t dir;
te = G_TempEntity( g_entities[self->client->ps.saberEntityNum].r.currentOrigin, EV_SABER_BLOCK );
VectorSet( dir, 0, 1, 0 );
VectorCopy(g_entities[self->client->ps.saberEntityNum].r.currentOrigin, te->s.origin);
VectorCopy(dir, te->s.angles);
te->s.eventParm = 1;
self->client->ps.saberIdleWound = level.time + Q_irand(400, 600);
}
VectorCopy(boltOrigin, self->client->lastSaberBase);
VectorCopy(end, self->client->lastSaberTip);
self->client->hasCurrentPosition = qtrue;
self->client->ps.saberBlocked = BLOCKED_NONE;
goto finalUpdate;
}
if (self->client->ps.dualBlade)
{
self->client->ps.saberIdleWound = 0;
self->client->ps.saberAttackWound = 0;
}
if (self->client->hasCurrentPosition && g_saberInterpolate.integer)
{
if (g_saberInterpolate.integer == 1)
{
int trMask = CONTENTS_LIGHTSABER|CONTENTS_BODY;
int sN = 0;
qboolean gotHit = qfalse;
qboolean clientUnlinked[MAX_CLIENTS];
if (!g_saberTraceSaberFirst.integer)
{ //skip the saber-contents-only trace and get right to the full trace
trMask = (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT);
}
else
{
while (sN < MAX_CLIENTS)
{
if (g_entities[sN].inuse && g_entities[sN].client && g_entities[sN].r.linked && g_entities[sN].health > 0 && (g_entities[sN].r.contents & CONTENTS_BODY))
{ //This was linking and relinking entities. But apparently you don't even have to do that to change contents (which is a very good thing)
//trap_UnlinkEntity(&g_entities[sN]);
g_entities[sN].r.contents &= ~CONTENTS_BODY;
clientUnlinked[sN] = qtrue;
}
else
{
clientUnlinked[sN] = qfalse;
}
sN++;
}
}
while (!gotHit)
{
if (!CheckSaberDamage(self, boltOrigin, end, qfalse, trMask))
{
if (!CheckSaberDamage(self, boltOrigin, end, qtrue, trMask))
{
vec3_t oldSaberStart;
vec3_t oldSaberEnd;
vec3_t saberAngleNow;
vec3_t saberAngleBefore;
vec3_t saberMidDir;
vec3_t saberMidAngle;
vec3_t saberMidPoint;
vec3_t saberMidEnd;
vec3_t saberSubBase;
float deltaX, deltaY, deltaZ;
VectorCopy(self->client->lastSaberBase, oldSaberStart);
VectorCopy(self->client->lastSaberTip, oldSaberEnd);
VectorSubtract(oldSaberEnd, oldSaberStart, saberAngleBefore);
vectoangles(saberAngleBefore, saberAngleBefore);
VectorSubtract(end, boltOrigin, saberAngleNow);
vectoangles(saberAngleNow, saberAngleNow);
deltaX = AngleDelta(saberAngleBefore[0], saberAngleNow[0]);
deltaY = AngleDelta(saberAngleBefore[1], saberAngleNow[1]);
deltaZ = AngleDelta(saberAngleBefore[2], saberAngleNow[2]);
if ( (deltaX != 0 || deltaY != 0 || deltaZ != 0) && deltaX < 180 && deltaY < 180 && deltaZ < 180 && (BG_SaberInAttack(self->client->ps.saberMove) || PM_SaberInTransition(self->client->ps.saberMove)) )
{ //don't go beyond here if we aren't attacking/transitioning or the angle is too large.
//and don't bother if the angle is the same
saberMidAngle[0] = saberAngleBefore[0] + (deltaX/2);
saberMidAngle[1] = saberAngleBefore[1] + (deltaY/2);
saberMidAngle[2] = saberAngleBefore[2] + (deltaZ/2);
//Now that I have the angle, I'll just say the base for it is the difference between the two start
//points (even though that's quite possibly completely false)
VectorSubtract(boltOrigin, oldSaberStart, saberSubBase);
saberMidPoint[0] = boltOrigin[0] + (saberSubBase[0]*0.5);
saberMidPoint[1] = boltOrigin[1] + (saberSubBase[1]*0.5);
saberMidPoint[2] = boltOrigin[2] + (saberSubBase[2]*0.5);
AngleVectors(saberMidAngle, saberMidDir, 0, 0);
saberMidEnd[0] = saberMidPoint[0] + saberMidDir[0]*40; //40 == saber length
saberMidEnd[1] = saberMidPoint[1] + saberMidDir[1]*40;
saberMidEnd[2] = saberMidPoint[2] + saberMidDir[2]*40;
//Now that we have the difference points, check from them to both the old position and the new
/*
if (!CheckSaberDamage(self, saberMidPoint, saberMidEnd, qtrue, trMask)) //this checks between mid and old
{ //that didn't hit, so copy the mid over the old and check between the new and mid
VectorCopy(saberMidPoint, self->client->lastSaberBase);
VectorCopy(saberMidEnd, self->client->lastSaberTip);
if (CheckSaberDamage(self, boltOrigin, end, qtrue, trMask))
{
gotHit = qtrue;
}
//Then copy the old oldpoints in back for good measure
VectorCopy(oldSaberStart, self->client->lastSaberBase);
VectorCopy(oldSaberEnd, self->client->lastSaberTip);
}
else
{
gotHit = qtrue;
}
*/
//The above was more aggressive in approach, but it did add way too many traces unfortunately.
//I'll just trace straight out and not even trace between positions instead.
if (CheckSaberDamage(self, saberMidPoint, saberMidEnd, qfalse, trMask))
{
gotHit = qtrue;
}
}
}
else
{
gotHit = qtrue;
}
}
else
{
gotHit = qtrue;
}
if (g_saberTraceSaberFirst.integer)
{
sN = 0;
while (sN < MAX_CLIENTS)
{
if (clientUnlinked[sN])
{
if (g_entities[sN].inuse && g_entities[sN].health > 0)
{
g_entities[sN].r.contents |= CONTENTS_BODY;
}
}
sN++;
}
}
if (!gotHit)
{
if (trMask != (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT))
{
trMask = (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT);
}
else
{
gotHit = qtrue; //break out of the loop
}
}
}
}
else if (g_saberInterpolate.integer) //anything but 0 or 1, use the old plain method.
{
if (!CheckSaberDamage(self, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT)))
{
CheckSaberDamage(self, boltOrigin, end, qtrue, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT));
}
}
}
else
{
CheckSaberDamage(self, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT));
}
if (self->client->ps.dualBlade)
{
vec3_t otherOrg, otherEnd;
VectorMA( boltOrigin, -12, rawAngles, otherOrg );
VectorMA( otherOrg, -40, rawAngles, otherEnd );
self->client->ps.saberIdleWound = 0;
self->client->ps.saberAttackWound = 0;
CheckSaberDamage(self, otherOrg, otherEnd, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT));
}
VectorCopy(boltOrigin, self->client->lastSaberBase);
VectorCopy(end, self->client->lastSaberTip);
self->client->hasCurrentPosition = qtrue;
self->client->ps.saberEntityState = 0;
}
finalUpdate:
if (self->client->ps.saberLockFrame)
{
trap_G2API_SetBoneAnim(self->client->ghoul2, 0, "model_root", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
trap_G2API_SetBoneAnim(self->client->ghoul2, 0, "lower_lumbar", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
trap_G2API_SetBoneAnim(self->client->ghoul2, 0, "Motion", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
return;
}
if (self->client->ps.legsAnimExecute != legsAnim)
{
float animSpeed = 50.0f / bgGlobalAnimations[legsAnim].frameLerp;
int aFlags;
animSpeedScale = (animSpeed *= animSpeedScale);
if (bgGlobalAnimations[legsAnim].loopFrames != -1)
{
aFlags = BONE_ANIM_OVERRIDE_LOOP;
}
else
{
aFlags = BONE_ANIM_OVERRIDE_FREEZE;
}
aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on client position, but it's here just for the sake of matching them.
trap_G2API_SetBoneAnim(self->client->ghoul2, 0, "model_root", bgGlobalAnimations[legsAnim].firstFrame, bgGlobalAnimations[legsAnim].firstFrame+bgGlobalAnimations[legsAnim].numFrames, aFlags, animSpeedScale, level.time, -1, 150);
self->client->ps.legsAnimExecute = legsAnim;
}
if (self->client->ps.torsoAnimExecute != torsoAnim)
{
int initialFrame;
int aFlags = 0;
float animSpeed = 0;
f = torsoAnim;
initialFrame = bgGlobalAnimations[f].firstFrame;
/*
if (bgGlobalAnimations[f].numFrames > 20)
{
initialFrame += 6;
}
else if (bgGlobalAnimations[f].numFrames > 3)
{ //HACK: Force it a couple frames into the animation so it doesn't lag behind the client visual position as much..
initialFrame += 2;
}
*/
BG_SaberStartTransAnim(self->client->ps.fd.saberAnimLevel, f, &animSpeedScale);
animSpeed = 50.0f / bgGlobalAnimations[f].frameLerp;
animSpeedScale = (animSpeed *= animSpeedScale);
if (bgGlobalAnimations[f].loopFrames != -1)
{
aFlags = BONE_ANIM_OVERRIDE_LOOP;
}
else
{
aFlags = BONE_ANIM_OVERRIDE_FREEZE;
}
aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on client position, but it's here just for the sake of matching them.
trap_G2API_SetBoneAnim(self->client->ghoul2, 0, "lower_lumbar", initialFrame, bgGlobalAnimations[f].firstFrame+bgGlobalAnimations[f].numFrames, aFlags, animSpeedScale, level.time, initialFrame, 150);
self->client->ps.torsoAnimExecute = torsoAnim;
setTorso = qtrue;
}
if (!BG_FlippingAnim( self->client->ps.legsAnim ) &&
!BG_FlippingAnim( self->client->ps.torsoAnim ) &&
!BG_SpinningSaberAnim( self->client->ps.legsAnim ) &&
!BG_SpinningSaberAnim( self->client->ps.torsoAnim ) &&
!BG_InSpecialJump( self->client->ps.legsAnim ) &&
!BG_InSpecialJump( self->client->ps.torsoAnim ) &&
!BG_InRoll(&self->client->ps, self->client->ps.legsAnim) &&
!BG_SaberInSpecial(self->client->ps.saberMove) &&
!BG_SaberInSpecialAttack(self->client->ps.legsAnim) &&
!BG_SaberInSpecialAttack(self->client->ps.torsoAnim) &&
setTorso )
{
float animSpeed = 50.0f / bgGlobalAnimations[torsoAnim].frameLerp;
int aFlags;
animSpeedScale = (animSpeed *= animSpeedScale);
if (bgGlobalAnimations[torsoAnim].loopFrames != -1)
{
aFlags = BONE_ANIM_OVERRIDE_LOOP;
}
else
{
aFlags = BONE_ANIM_OVERRIDE_FREEZE;
}
aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on client position, but it's here just for the sake of matching them.
trap_G2API_SetBoneAnim(self->client->ghoul2, 0, "Motion", bgGlobalAnimations[torsoAnim].firstFrame, bgGlobalAnimations[torsoAnim].firstFrame+bgGlobalAnimations[torsoAnim].numFrames, aFlags, animSpeedScale, level.time, -1, 150);
}
}
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;
vec3_t clEye;
float rightdot;
float zdiff;
VectorCopy(self->client->ps.origin, clEye);
clEye[2] += self->client->ps.viewheight;
VectorSubtract( hitloc, clEye, 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] - clEye[2];
//FIXME: take torsoAngles into account?
if ( zdiff > 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 > -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;
}
}
/*
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;
}
}
*/
if ( missileBlock )
{
self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
//if ( !self->s.number )
//{
//G_DynaMixEvent( DM_BLOCK );
//}
}
else
{
//if ( !self->s.number )
//{
//G_DynaMixEvent( DM_PARRY );
//}
}
}
void WP_SaberBlock( gentity_t *playerent, 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;
}
}
else
{ // Bad entity passed.
return;
}
*/
//I don't see what the point of this was anyway, saber isn't used anywhere in the function.
VectorSubtract(hitloc, playerent->client->ps.origin, 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) + RandFloat(-0.2f,0.2f);
zdiff = hitloc[2] - playerent->client->ps.origin[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 );
}
}
int WP_SaberCanBlock(gentity_t *self, vec3_t point, int dflags, int mod, qboolean projectile, int attackStr)
{
qboolean thrownSaber = qfalse;
float blockFactor = 0;
if (!self || !self->client || !point)
{
return 0;
}
if (attackStr == 8)
{
attackStr = 0;
thrownSaber = qtrue;
}
if (BG_SaberInAttack(self->client->ps.saberMove))
{
return 0;
}
if (PM_InSaberAnim(self->client->ps.torsoAnim) && !self->client->ps.saberBlocked &&
self->client->ps.saberMove != LS_READY && self->client->ps.saberMove != LS_NONE)
{
if ( self->client->ps.saberMove < LS_PARRY_UP || self->client->ps.saberMove > LS_REFLECT_LL )
{
return 0;
}
}
if (PM_SaberInBrokenParry(self->client->ps.saberMove))
{
return 0;
}
if (self->client->ps.saberHolstered)
{
return 0;
}
if (self->client->ps.usingATST)
{
return 0;
}
if (self->client->ps.weapon != WP_SABER)
{
return 0;
}
if (self->client->ps.weaponstate == WEAPON_RAISING)
{
return 0;
}
if (self->client->ps.saberInFlight)
{
return 0;
}
if ((self->client->pers.cmd.buttons & BUTTON_ATTACK)/* &&
(projectile || attackStr == FORCE_LEVEL_3)*/)
{ //don't block when the player is trying to slash, if it's a projectile or he's doing a very strong attack
return 0;
}
//Removed this for no, the new broken parry stuff should handle it.
/*
if (attackStr == FORCE_LEVEL_3)
{
if (self->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] >= FORCE_LEVEL_3)
{
if (Q_irand(1, 10) < 3)
{
return 0;
}
}
else
{
return 0;
}
}
if (attackStr == FORCE_LEVEL_2 && Q_irand(1, 10) < 3)
{
if (self->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] >= FORCE_LEVEL_3)
{
//do nothing for now
}
else if (self->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] >= FORCE_LEVEL_2)
{
if (Q_irand(1, 10) < 5)
{
return 0;
}
}
else
{
return 0;
}
}
if (attackStr == FORCE_LEVEL_1 && !self->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] &&
Q_irand(1, 40) < 3)
{ //if I have no defense level at all then I might be unable to block a level 1 attack (but very rarely)
return 0;
}
*/
if (SaberAttacking(self))
{ //attacking, can't block now
//FIXME: Do a "saber box" check here to see if the enemy saber hit this guy's saber
return 0;
}
if (self->client->ps.saberMove != LS_READY &&
!self->client->ps.saberBlocking)
{
return 0;
}
if (self->client->ps.saberBlockTime >= level.time)
{
return 0;
}
if (self->client->ps.forceHandExtend != HANDEXTEND_NONE)
{
return 0;
}
if (self->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] == FORCE_LEVEL_3)
{
#ifdef G2_COLLISION_ENABLED
if (g_saberGhoul2Collision.integer)
{
blockFactor = 0.3f;
}
else
{
blockFactor = 0.05f;
}
#else
blockFactor = 0.05f;
#endif
}
else if (self->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] == FORCE_LEVEL_2)
{
blockFactor = 0.6f;
}
else if (self->client->ps.fd.forcePowerLevel[FP_SABERDEFEND] == FORCE_LEVEL_1)
{
blockFactor = 0.9f;
}
else
{ //for now we just don't get to autoblock with no def
return 0;
}
if (thrownSaber)
{
blockFactor -= 0.25f;
}
if (attackStr)
{ //blocking a saber, not a projectile.
blockFactor -= 0.25f;
}
if (!InFront( point, self->client->ps.origin, self->client->ps.viewangles, blockFactor )) //orig 0.2f
{
return 0;
}
if (projectile)
{
WP_SaberBlockNonRandom(self, point, projectile);
}
return 1;
}
qboolean HasSetSaberOnly(void)
{
int i = 0;
int wDisable = 0;
if (g_gametype.integer == GT_JEDIMASTER)
{ //set to 0
return qfalse;
}
if (g_gametype.integer == GT_TOURNAMENT)
{
wDisable = g_duelWeaponDisable.integer;
}
else
{
wDisable = g_weaponDisable.integer;
}
while (i < WP_NUM_WEAPONS)
{
if (!(wDisable & (1 << i)) &&
i != WP_SABER && i != WP_NONE)
{
return qfalse;
}
i++;
}
return qtrue;
}