jedi-academy/codemp/game/w_saber.c

9486 lines
283 KiB
C
Raw Normal View History

2013-04-19 02:52:48 +00:00
#include "g_local.h"
#include "bg_local.h"
#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 );
extern void G_TestLine(vec3_t start, vec3_t end, int color, int time);
extern vmCvar_t g_saberRealisticCombat;
extern vmCvar_t d_saberSPStyleDamage;
extern vmCvar_t g_debugSaberLocks;
// nmckenzie: SABER_DAMAGE_WALLS
extern vmCvar_t g_saberWallDamageScale;
int saberSpinSound = 0;
//would be cleaner if these were renamed to BG_ and proto'd in a header.
#include "../namespace_begin.h"
qboolean PM_SaberInTransition( int move );
qboolean PM_SaberInDeflect( int move );
qboolean PM_SaberInBrokenParry( int move );
qboolean PM_SaberInBounce( int move );
qboolean BG_SaberInReturn( int move );
qboolean BG_InKnockDownOnGround( playerState_t *ps );
qboolean BG_StabDownAnim( int anim );
qboolean BG_SabersOff( playerState_t *ps );
qboolean BG_SaberInTransitionAny( int move );
qboolean BG_SaberInAttackPure( int move );
qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum );
qboolean WP_SaberBladeDoTransitionDamage( saberInfo_t *saber, int bladeNum );
#include "../namespace_end.h"
void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin );
void WP_SaberRemoveG2Model( gentity_t *saberent );
float RandFloat(float min, float max) {
// return ((rand() * (max - min)) / 32768.0F) + min;
//for linux:
return ((rand() * (max - min)) / (float)RAND_MAX) + min;
}
#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
//general check for performing certain attacks against others
qboolean G_CanBeEnemy(gentity_t *self, gentity_t *enemy)
{
if (!self->inuse || !enemy->inuse || !self->client || !enemy->client)
{
return qfalse;
}
if (self->client->ps.duelInProgress && self->client->ps.duelIndex != enemy->s.number)
{ //dueling but not with this person
return qfalse;
}
if (enemy->client->ps.duelInProgress && enemy->client->ps.duelIndex != self->s.number)
{ //other guy dueling but not with me
return qfalse;
}
if (g_gametype.integer < GT_TEAM)
{ //ok, sure
return qtrue;
}
if (g_friendlyFire.integer)
{ //if ff on then can inflict damage normally on teammates
return qtrue;
}
if (OnSameTeam(self, enemy))
{ //ff not on, don't hurt teammates
return qfalse;
}
return qtrue;
}
//This function gets the attack power which is used to decide broken parries,
//knockaways, and numerous other things. It is not directly related to the
//actual amount of damage done, however. -rww
static GAME_INLINE int G_SaberAttackPower(gentity_t *ent, qboolean attacking)
{
int baseLevel;
assert(ent && ent->client);
baseLevel = ent->client->ps.fd.saberAnimLevel;
//Give "medium" strength for the two special stances.
if (baseLevel == SS_DUAL)
{
baseLevel = 2;
}
else if (baseLevel == SS_STAFF)
{
baseLevel = 2;
}
if (attacking)
{ //the attacker gets a boost to help penetrate defense.
//General boost up so the individual levels make a bigger difference.
baseLevel *= 2;
baseLevel++;
//Get the "speed" of the swing, roughly, and add more power
//to the attack based on it.
if (ent->client->lastSaberStorageTime >= (level.time-50) &&
ent->client->olderIsValid)
{
vec3_t vSub;
int swingDist;
int toleranceAmt;
//We want different "tolerance" levels for adding in the distance of the last swing
//to the base power level depending on which stance we are using. Otherwise fast
//would have more advantage than it should since the animations are all much faster.
switch (ent->client->ps.fd.saberAnimLevel)
{
case SS_STRONG:
toleranceAmt = 8;
break;
case SS_MEDIUM:
toleranceAmt = 16;
break;
case SS_FAST:
toleranceAmt = 24;
break;
default: //dual, staff, etc.
toleranceAmt = 16;
break;
}
VectorSubtract(ent->client->lastSaberBase_Always, ent->client->olderSaberBase, vSub);
swingDist = (int)VectorLength(vSub);
while (swingDist > 0)
{ //I would like to do something more clever. But I suppose this works, at least for now.
baseLevel++;
swingDist -= toleranceAmt;
}
}
#ifndef FINAL_BUILD
if (g_saberDebugPrint.integer > 1)
{
Com_Printf("Client %i: ATT STR: %i\n", ent->s.number, baseLevel);
}
#endif
}
if ((ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) ||
(ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
{ //We're very weak when one of our arms is broken
baseLevel *= 0.3;
}
//Cap at reasonable values now.
if (baseLevel < 1)
{
baseLevel = 1;
}
else if (baseLevel > 16)
{
baseLevel = 16;
}
if (g_gametype.integer == GT_POWERDUEL &&
ent->client->sess.duelTeam == DUELTEAM_LONE)
{ //get more power then
return baseLevel*2;
}
else if (attacking && g_gametype.integer == GT_SIEGE)
{ //in siege, saber battles should be quicker and more biased toward the attacker
return baseLevel*3;
}
return baseLevel;
}
void WP_DeactivateSaber( gentity_t *self, qboolean clearLength )
{
if ( !self || !self->client )
{
return;
}
//keep my saber off!
if ( !self->client->ps.saberHolstered )
{
self->client->ps.saberHolstered = 2;
/*
if ( clearLength )
{
self->client->ps.SetSaberLength( 0 );
}
*/
//Doens't matter ATM
if (self->client->saber[0].soundOff)
{
G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOff);
}
if (self->client->saber[1].soundOff &&
self->client->saber[1].model[0])
{
G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOff);
}
}
}
void WP_ActivateSaber( gentity_t *self )
{
if ( !self || !self->client )
{
return;
}
if (self->NPC &&
self->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT &&
(self->client->ps.forceHandExtendTime - level.time) > 200)
{ //if we're an NPC and in the middle of a taunt then stop it
self->client->ps.forceHandExtend = HANDEXTEND_NONE;
self->client->ps.forceHandExtendTime = 0;
}
else if (self->client->ps.fd.forceGripCripple)
{ //can't activate saber while being gripped
return;
}
if ( self->client->ps.saberHolstered )
{
self->client->ps.saberHolstered = 0;
if (self->client->saber[0].soundOn)
{
G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOn);
}
if (self->client->saber[1].soundOn)
{
G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOn);
}
}
}
#define PROPER_THROWN_VALUE 999 //Ah, well..
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;
ent->genericValue5 = PROPER_THROWN_VALUE;
return;
}
ent->genericValue5 = 0;
if (g_entities[ent->r.ownerNum].client->ps.weapon != WP_SABER ||
(g_entities[ent->r.ownerNum].client->ps.pm_flags & PMF_FOLLOW) ||
//RWW ADDED 7-19-03 BEGIN
g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR ||
g_entities[ent->r.ownerNum].client->tempSpectate >= level.time ||
//RWW ADDED 7-19-03 END
g_entities[ent->r.ownerNum].health < 1 ||
BG_SabersOff( &g_entities[ent->r.ownerNum].client->ps ) ||
(!g_entities[ent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] && g_entities[ent->r.ownerNum].s.eType != ET_NPC))
{ //owner is not using saber, spectating, dead, saber holstered, or has no attack level
ent->r.contents = 0;
ent->clipmask = 0;
}
else
{ //Standard contents (saber is active)
#ifdef DEBUG_SABER_BOX
if (g_saberDebugBox.integer == 1|| g_saberDebugBox.integer == 4)
{
vec3_t dbgMins;
vec3_t dbgMaxs;
VectorAdd( ent->r.currentOrigin, ent->r.mins, dbgMins );
VectorAdd( ent->r.currentOrigin, ent->r.maxs, dbgMaxs );
G_DebugBoxLines(dbgMins, dbgMaxs, (10.0f/(float)g_svfps.integer)*100);
}
#endif
if (ent->r.contents != CONTENTS_LIGHTSABER)
{
if ((level.time - g_entities[ent->r.ownerNum].client->lastSaberStorageTime) <= 200)
{ //Only go back to solid once we're sure our owner has updated recently
ent->r.contents = CONTENTS_LIGHTSABER;
ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
}
}
else
{
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.
}
#include "../namespace_begin.h"
qboolean BG_SuperBreakLoseAnim( int anim );
#include "../namespace_end.h"
static GAME_INLINE void SetSaberBoxSize(gentity_t *saberent)
{
gentity_t *owner = NULL;
vec3_t saberOrg, saberTip;
int i;
int j = 0;
int k = 0;
qboolean dualSabers = qfalse;
qboolean alwaysBlock[MAX_SABERS][MAX_BLADES];
qboolean forceBlock = qfalse;
assert(saberent && saberent->inuse);
if (saberent->r.ownerNum < MAX_CLIENTS && saberent->r.ownerNum >= 0)
{
owner = &g_entities[saberent->r.ownerNum];
}
else if (saberent->r.ownerNum >= 0 && saberent->r.ownerNum < ENTITYNUM_WORLD &&
g_entities[saberent->r.ownerNum].s.eType == ET_NPC)
{
owner = &g_entities[saberent->r.ownerNum];
}
if (!owner || !owner->inuse || !owner->client)
{
assert(!"Saber with no owner?");
return;
}
if ( owner->client->saber[1].model
&& owner->client->saber[1].model[0] )
{
dualSabers = qtrue;
}
if ( PM_SaberInBrokenParry(owner->client->ps.saberMove)
|| BG_SuperBreakLoseAnim( owner->client->ps.torsoAnim ) )
{ //let swings go right through when we're in this state
for ( i = 0; i < MAX_SABERS; i++ )
{
if ( i > 0 && !dualSabers )
{//not using a second saber, set it to not blocking
for ( j = 0; j < MAX_BLADES; j++ )
{
alwaysBlock[i][j] = qfalse;
}
}
else
{
if ( (owner->client->saber[i].saberFlags2&SFL2_ALWAYS_BLOCK) )
{
for ( j = 0; j < owner->client->saber[i].numBlades; j++ )
{
alwaysBlock[i][j] = qtrue;
forceBlock = qtrue;
}
}
if ( owner->client->saber[i].bladeStyle2Start > 0 )
{
for ( j = owner->client->saber[i].bladeStyle2Start; j < owner->client->saber[i].numBlades; j++ )
{
if ( (owner->client->saber[i].saberFlags2&SFL2_ALWAYS_BLOCK2) )
{
alwaysBlock[i][j] = qtrue;
forceBlock = qtrue;
}
else
{
alwaysBlock[i][j] = qfalse;
}
}
}
}
}
if ( !forceBlock )
{//no sabers/blades to FORCE to be on, so turn off blocking altogether
VectorSet( saberent->r.mins, 0, 0, 0 );
VectorSet( saberent->r.maxs, 0, 0, 0 );
#ifndef FINAL_BUILD
if (g_saberDebugPrint.integer > 1)
{
Com_Printf("Client %i in broken parry, saber box 0\n", owner->s.number);
}
#endif
return;
}
}
if ((level.time - owner->client->lastSaberStorageTime) > 200 ||
(level.time - owner->client->saber[j].blade[k].storageTime) > 100)
{ //it's been too long since we got a reliable point storage, so use the defaults and leave.
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 );
return;
}
if ( dualSabers
|| owner->client->saber[0].numBlades > 1 )
{//dual sabers or multi-blade saber
if ( owner->client->ps.saberHolstered > 1 )
{//entirely off
//no blocking at all
VectorSet( saberent->r.mins, 0, 0, 0 );
VectorSet( saberent->r.maxs, 0, 0, 0 );
return;
}
}
else
{//single saber
if ( owner->client->ps.saberHolstered )
{//off
//no blocking at all
VectorSet( saberent->r.mins, 0, 0, 0 );
VectorSet( saberent->r.maxs, 0, 0, 0 );
return;
}
}
//Start out at the saber origin, then go through all the blades and push out the extents
//for each blade, then set the box relative to the origin.
VectorCopy(saberent->r.currentOrigin, saberent->r.mins);
VectorCopy(saberent->r.currentOrigin, saberent->r.maxs);
for (i = 0; i < 3; i++)
{
for (j = 0; j < MAX_SABERS; j++)
{
if (!owner->client->saber[j].model[0])
{
break;
}
if ( dualSabers
&& owner->client->ps.saberHolstered == 1
&& j == 1 )
{ //this mother is holstered, get outta here.
j++;
continue;
}
for (k = 0; k < owner->client->saber[j].numBlades; k++)
{
if ( k > 0 )
{//not the first blade
if ( !dualSabers )
{//using a single saber
if ( owner->client->saber[j].numBlades > 1 )
{//with multiple blades
if( owner->client->ps.saberHolstered == 1 )
{//all blades after the first one are off
break;
}
}
}
}
if ( forceBlock )
{//only do blocking with blades that are marked to block
if ( !alwaysBlock[j][k] )
{//this blade shouldn't be blocking
continue;
}
}
//VectorMA(owner->client->saber[j].blade[k].muzzlePoint, owner->client->saber[j].blade[k].lengthMax*0.5f, owner->client->saber[j].blade[k].muzzleDir, saberOrg);
VectorCopy(owner->client->saber[j].blade[k].muzzlePoint, saberOrg);
VectorMA(owner->client->saber[j].blade[k].muzzlePoint, owner->client->saber[j].blade[k].lengthMax, owner->client->saber[j].blade[k].muzzleDir, saberTip);
if (saberOrg[i] < saberent->r.mins[i])
{
saberent->r.mins[i] = saberOrg[i];
}
if (saberTip[i] < saberent->r.mins[i])
{
saberent->r.mins[i] = saberTip[i];
}
if (saberOrg[i] > saberent->r.maxs[i])
{
saberent->r.maxs[i] = saberOrg[i];
}
if (saberTip[i] > saberent->r.maxs[i])
{
saberent->r.maxs[i] = saberTip[i];
}
//G_TestLine(saberOrg, saberTip, 0x0000ff, 50);
}
}
}
VectorSubtract(saberent->r.mins, saberent->r.currentOrigin, saberent->r.mins);
VectorSubtract(saberent->r.maxs, saberent->r.currentOrigin, saberent->r.maxs);
}
void WP_SaberInitBladeData( gentity_t *ent )
{
gentity_t *saberent = NULL;
gentity_t *checkEnt;
int i = 0;
while (i < level.num_entities)
{ //make sure there are no other saber entities floating around that think they belong to this client.
checkEnt = &g_entities[i];
if (checkEnt->inuse && checkEnt->neverFree &&
checkEnt->r.ownerNum == ent->s.number &&
checkEnt->classname && checkEnt->classname[0] &&
!Q_stricmp(checkEnt->classname, "lightsaber"))
{
if (saberent)
{ //already have one
checkEnt->neverFree = qfalse;
checkEnt->think = G_FreeEntity;
checkEnt->nextthink = level.time;
}
else
{ //hmm.. well then, take it as my own.
//free the bitch but don't issue a kg2 to avoid overflowing clients.
checkEnt->s.modelGhoul2 = 0;
G_FreeEntity(checkEnt);
//now init it manually and reuse this ent slot.
G_InitGentity(checkEnt);
saberent = checkEnt;
}
}
i++;
}
//We do not want the client to have any real knowledge of the entity whatsoever. It will only
//ever be used on the server.
if (!saberent)
{ //ok, make one then
saberent = G_Spawn();
}
ent->client->ps.saberEntityNum = ent->client->saberStoredIndex = saberent->s.number;
saberent->classname = "lightsaber";
saberent->neverFree = qtrue; //the saber being removed would be a terrible thing.
saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
saberent->r.ownerNum = ent->s.number;
saberent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
saberent->r.contents = CONTENTS_LIGHTSABER;
SetSaberBoxSize(saberent);
saberent->mass = 10;
saberent->s.eFlags |= EF_NODRAW;
saberent->r.svFlags |= SVF_NOCLIENT;
saberent->s.modelGhoul2 = 1;
//should we happen to be removed (we belong to an NPC and he is removed) then
//we want to attempt to remove our g2 instance on the client in case we had one.
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->genericValue5 = 0;
saberent->nextthink = level.time + 50;
saberSpinSound = G_SoundIndex("sound/weapons/saber/saberspin.wav");
}
#define LOOK_DEFAULT_SPEED 0.15f
#define LOOK_TALKING_SPEED 0.15f
static GAME_INLINE qboolean G_CheckLookTarget( gentity_t *ent, vec3_t lookAngles, float *lookingSpeed )
{
//FIXME: also clamp the lookAngles based on the clamp + the existing difference between
// headAngles and torsoAngles? But often the tag_torso is straight but the torso itself
// is deformed to not face straight... sigh...
if (ent->s.eType == ET_NPC &&
ent->s.m_iVehicleNum &&
ent->s.NPC_class != CLASS_VEHICLE )
{ //an NPC bolted to a vehicle should just look around randomly
if ( TIMER_Done( ent, "lookAround" ) )
{
ent->NPC->shootAngles[YAW] = flrand(0,360);
TIMER_Set( ent, "lookAround", Q_irand( 500, 3000 ) );
}
VectorSet( lookAngles, 0, ent->NPC->shootAngles[YAW], 0 );
return qtrue;
}
//Now calc head angle to lookTarget, if any
if ( ent->client->renderInfo.lookTarget >= 0 && ent->client->renderInfo.lookTarget < ENTITYNUM_WORLD )
{
vec3_t lookDir, lookOrg, eyeOrg;
int i;
if ( ent->client->renderInfo.lookMode == LM_ENT )
{
gentity_t *lookCent = &g_entities[ent->client->renderInfo.lookTarget];
if ( lookCent )
{
if ( lookCent != ent->enemy )
{//We turn heads faster than headbob speed, but not as fast as if watching an enemy
*lookingSpeed = LOOK_DEFAULT_SPEED;
}
//FIXME: Ignore small deltas from current angles so we don't bob our head in synch with theirs?
/*
if ( ent->client->renderInfo.lookTarget == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
{//Special case- use cg.refdef.vieworg if looking at player and not in third person view
VectorCopy( cg.refdef.vieworg, lookOrg );
}
*/ //No no no!
if ( lookCent->client )
{
VectorCopy( lookCent->client->renderInfo.eyePoint, lookOrg );
}
else if ( lookCent->inuse && !VectorCompare( lookCent->r.currentOrigin, vec3_origin ) )
{
VectorCopy( lookCent->r.currentOrigin, lookOrg );
}
else
{//at origin of world
return qfalse;
}
//Look in dir of lookTarget
}
}
else if ( ent->client->renderInfo.lookMode == LM_INTEREST && ent->client->renderInfo.lookTarget > -1 && ent->client->renderInfo.lookTarget < MAX_INTEREST_POINTS )
{
VectorCopy( level.interestPoints[ent->client->renderInfo.lookTarget].origin, lookOrg );
}
else
{
return qfalse;
}
VectorCopy( ent->client->renderInfo.eyePoint, eyeOrg );
VectorSubtract( lookOrg, eyeOrg, lookDir );
vectoangles( lookDir, lookAngles );
for ( i = 0; i < 3; i++ )
{
lookAngles[i] = AngleNormalize180( lookAngles[i] );
ent->client->renderInfo.eyeAngles[i] = AngleNormalize180( ent->client->renderInfo.eyeAngles[i] );
}
AnglesSubtract( lookAngles, ent->client->renderInfo.eyeAngles, lookAngles );
return qtrue;
}
return qfalse;
}
//rww - attempted "port" of the SP version which is completely client-side and
//uses illegal gentity access. I am trying to keep this from being too
//bandwidth-intensive.
//This is primarily droid stuff I guess, I'm going to try to handle all humanoid
//NPC stuff in with the actual player stuff if possible.
void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles);
static GAME_INLINE void G_G2NPCAngles(gentity_t *ent, vec3_t legs[3], vec3_t angles)
{
char *craniumBone = "cranium";
char *thoracicBone = "thoracic"; //only used by atst so doesn't need a case
qboolean looking = qfalse;
vec3_t viewAngles;
vec3_t lookAngles;
if ( ent->client )
{
if ( (ent->client->NPC_class == CLASS_PROBE )
|| (ent->client->NPC_class == CLASS_R2D2 )
|| (ent->client->NPC_class == CLASS_R5D2)
|| (ent->client->NPC_class == CLASS_ATST) )
{
vec3_t trailingLegsAngles;
if (ent->s.eType == ET_NPC &&
ent->s.m_iVehicleNum &&
ent->s.NPC_class != CLASS_VEHICLE )
{ //an NPC bolted to a vehicle should use the full angles
VectorCopy(ent->r.currentAngles, angles);
}
else
{
VectorCopy( ent->client->ps.viewangles, angles );
angles[PITCH] = 0;
}
//FIXME: use actual swing/clamp tolerances?
/*
if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
{//on the ground
CG_PlayerLegsYawFromMovement( cent, ent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
}
else
{//face legs to front
CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
}
*/
VectorCopy( ent->client->ps.viewangles, viewAngles );
// viewAngles[YAW] = viewAngles[ROLL] = 0;
viewAngles[PITCH] *= 0.5;
VectorCopy( viewAngles, lookAngles );
lookAngles[1] = 0;
if ( ent->client->NPC_class == CLASS_ATST )
{//body pitch
NPC_SetBoneAngles(ent, thoracicBone, lookAngles);
//BG_G2SetBoneAngles( cent, ent, ent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT,POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
}
VectorCopy( viewAngles, lookAngles );
if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST )
{
//CG_ATSTLegsYaw( cent, trailingLegsAngles );
AnglesToAxis( trailingLegsAngles, legs );
}
else
{
//FIXME: this needs to properly set the legs.yawing field so we don't erroneously play the turning anim, but we do play it when turning in place
/*
if ( angles[YAW] == cent->pe.legs.yawAngle )
{
cent->pe.legs.yawing = qfalse;
}
else
{
cent->pe.legs.yawing = qtrue;
}
cent->pe.legs.yawAngle = angles[YAW];
if ( ent->client )
{
ent->client->renderInfo.legsYaw = angles[YAW];
}
AnglesToAxis( angles, legs );
*/
}
// if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST )
// {
// looking = qfalse;
// }
// else
{ //look at lookTarget!
//FIXME: snaps to side when lets go of lookTarget... ?
float lookingSpeed = 0.3f;
looking = G_CheckLookTarget( ent, lookAngles, &lookingSpeed );
lookAngles[PITCH] = lookAngles[ROLL] = 0;//droids can't pitch or roll their heads
if ( looking )
{//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap)
ent->client->renderInfo.lookingDebounceTime = level.time + 1000;
}
}
if ( ent->client->renderInfo.lookingDebounceTime > level.time )
{ //adjust for current body orientation
vec3_t oldLookAngles;
lookAngles[YAW] -= 0;//ent->client->ps.viewangles[YAW];//cent->pe.torso.yawAngle;
//lookAngles[YAW] -= cent->pe.legs.yawAngle;
//normalize
lookAngles[YAW] = AngleNormalize180( lookAngles[YAW] );
//slowly lerp to this new value
//Remember last headAngles
VectorCopy( ent->client->renderInfo.lastHeadAngles, oldLookAngles );
if( VectorCompare( oldLookAngles, lookAngles ) == qfalse )
{
//FIXME: This clamp goes off viewAngles,
//but really should go off the tag_torso's axis[0] angles, no?
lookAngles[YAW] = oldLookAngles[YAW]+(lookAngles[YAW]-oldLookAngles[YAW])*0.4f;
}
//Remember current lookAngles next time
VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles );
}
else
{//Remember current lookAngles next time
VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles );
}
if ( ent->client->NPC_class == CLASS_ATST )
{
VectorCopy( ent->client->ps.viewangles, lookAngles );
lookAngles[0] = lookAngles[2] = 0;
lookAngles[YAW] -= trailingLegsAngles[YAW];
}
else
{
lookAngles[PITCH] = lookAngles[ROLL] = 0;
lookAngles[YAW] -= ent->client->ps.viewangles[YAW];
}
NPC_SetBoneAngles(ent, craniumBone, lookAngles);
//BG_G2SetBoneAngles( cent, ent, ent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
//return;
}
else//if ( (ent->client->NPC_class == CLASS_GONK ) || (ent->client->NPC_class == CLASS_INTERROGATOR) || (ent->client->NPC_class == CLASS_SENTRY) )
{
// VectorCopy( ent->client->ps.viewangles, angles );
// AnglesToAxis( angles, legs );
//return;
}
}
}
static GAME_INLINE void G_G2PlayerAngles( gentity_t *ent, vec3_t legs[3], vec3_t legsAngles)
{
qboolean tPitching = qfalse,
tYawing = qfalse,
lYawing = qfalse;
float tYawAngle = ent->client->ps.viewangles[YAW],
tPitchAngle = 0,
lYawAngle = ent->client->ps.viewangles[YAW];
int ciLegs = ent->client->ps.legsAnim;
int ciTorso = ent->client->ps.torsoAnim;
vec3_t turAngles;
vec3_t lerpOrg, lerpAng;
if (ent->s.eType == ET_NPC && ent->client)
{ //sort of hacky, but it saves a pretty big load off the server
int i = 0;
gentity_t *clEnt;
//If no real clients are in the same PVS then don't do any of this stuff, no one can see him anyway!
while (i < MAX_CLIENTS)
{
clEnt = &g_entities[i];
if (clEnt && clEnt->inuse && clEnt->client &&
trap_InPVS(clEnt->client->ps.origin, ent->client->ps.origin))
{ //this client can see him
break;
}
i++;
}
if (i == MAX_CLIENTS)
{ //no one can see him, just return
return;
}
}
VectorCopy(ent->client->ps.origin, lerpOrg);
VectorCopy(ent->client->ps.viewangles, lerpAng);
if (ent->localAnimIndex <= 1)
{ //don't do these things on non-humanoids
vec3_t lookAngles;
entityState_t *emplaced = NULL;
if (ent->client->ps.hasLookTarget)
{
VectorSubtract(g_entities[ent->client->ps.lookTarget].r.currentOrigin, ent->client->ps.origin, lookAngles);
vectoangles(lookAngles, lookAngles);
ent->client->lookTime = level.time + 1000;
}
else
{
VectorCopy(ent->client->ps.origin, lookAngles);
}
lookAngles[PITCH] = 0;
if (ent->client->ps.emplacedIndex)
{
emplaced = &g_entities[ent->client->ps.emplacedIndex].s;
}
BG_G2PlayerAngles(ent->ghoul2, ent->client->renderInfo.motionBolt, &ent->s, level.time, lerpOrg, lerpAng, legs,
legsAngles, &tYawing, &tPitching, &lYawing, &tYawAngle, &tPitchAngle, &lYawAngle, FRAMETIME, turAngles,
ent->modelScale, ciLegs, ciTorso, &ent->client->corrTime, lookAngles, ent->client->lastHeadAngles,
ent->client->lookTime, emplaced, NULL);
if (ent->client->ps.heldByClient && ent->client->ps.heldByClient <= MAX_CLIENTS)
{ //then put our arm in this client's hand
//is index+1 because index 0 is valid.
int heldByIndex = ent->client->ps.heldByClient-1;
gentity_t *other = &g_entities[heldByIndex];
int lHandBolt = 0;
if (other && other->inuse && other->client && other->ghoul2)
{
lHandBolt = trap_G2API_AddBolt(other->ghoul2, 0, "*l_hand");
}
else
{ //they left the game, perhaps?
ent->client->ps.heldByClient = 0;
return;
}
if (lHandBolt)
{
mdxaBone_t boltMatrix;
vec3_t boltOrg;
vec3_t tAngles;
VectorCopy(other->client->ps.viewangles, tAngles);
tAngles[PITCH] = tAngles[ROLL] = 0;
trap_G2API_GetBoltMatrix(other->ghoul2, 0, lHandBolt, &boltMatrix, tAngles, other->client->ps.origin, level.time, 0, other->modelScale);
boltOrg[0] = boltMatrix.matrix[0][3];
boltOrg[1] = boltMatrix.matrix[1][3];
boltOrg[2] = boltMatrix.matrix[2][3];
BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s, ent->client->ps.torsoAnim/*BOTH_DEAD1*/, boltOrg, &ent->client->ikStatus,
ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qfalse);
}
}
else if (ent->client->ikStatus)
{ //make sure we aren't IKing if we don't have anyone to hold onto us.
int lHandBolt = 0;
if (ent && ent->inuse && ent->client && ent->ghoul2)
{
lHandBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand");
}
else
{ //This shouldn't happen, but just in case it does, we'll have a failsafe.
ent->client->ikStatus = qfalse;
}
if (lHandBolt)
{
BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s,
ent->client->ps.torsoAnim/*BOTH_DEAD1*/, vec3_origin, &ent->client->ikStatus, ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qtrue);
}
}
}
else if ( ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
{
vec3_t lookAngles;
VectorCopy(ent->client->ps.viewangles, legsAngles);
legsAngles[PITCH] = 0;
AnglesToAxis( legsAngles, legs );
VectorCopy(ent->client->ps.viewangles, lookAngles);
lookAngles[YAW] = lookAngles[ROLL] = 0;
BG_G2ATSTAngles( ent->ghoul2, level.time, lookAngles );
}
else if (ent->NPC)
{ //an NPC not using a humanoid skeleton, do special angle stuff.
if (ent->s.eType == ET_NPC &&
ent->s.NPC_class == CLASS_VEHICLE &&
ent->m_pVehicle &&
ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
{ //fighters actually want to take pitch and roll into account for the axial angles
VectorCopy(ent->client->ps.viewangles, legsAngles);
AnglesToAxis( legsAngles, legs );
}
else
{
G_G2NPCAngles(ent, legs, legsAngles);
}
}
}
static GAME_INLINE 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)
{ //if we're firing and not blocking, then we're attacking.
return qtrue;
}
}
if (BG_SaberInSpecial(self->client->ps.saberMove))
{
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 );
int G_SaberLockAnim( int attackerSaberStyle, int defenderSaberStyle, int topOrSide, int lockOrBreakOrSuperBreak, int winOrLose )
{
int baseAnim = -1;
if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
{//special case: if we're using the same style and locking
if ( attackerSaberStyle == defenderSaberStyle
|| (attackerSaberStyle>=SS_FAST&&attackerSaberStyle<=SS_TAVION&&defenderSaberStyle>=SS_FAST&&defenderSaberStyle<=SS_TAVION) )
{//using same style
if ( winOrLose == SABERLOCK_LOSE )
{//you want the defender's stance...
switch ( defenderSaberStyle )
{
case SS_DUAL:
if ( topOrSide == SABERLOCK_TOP )
{
baseAnim = BOTH_LK_DL_DL_T_L_2;
}
else
{
baseAnim = BOTH_LK_DL_DL_S_L_2;
}
break;
case SS_STAFF:
if ( topOrSide == SABERLOCK_TOP )
{
baseAnim = BOTH_LK_ST_ST_T_L_2;
}
else
{
baseAnim = BOTH_LK_ST_ST_S_L_2;
}
break;
default:
if ( topOrSide == SABERLOCK_TOP )
{
baseAnim = BOTH_LK_S_S_T_L_2;
}
else
{
baseAnim = BOTH_LK_S_S_S_L_2;
}
break;
}
}
}
}
if ( baseAnim == -1 )
{
switch ( attackerSaberStyle )
{
case SS_DUAL:
switch ( defenderSaberStyle )
{
case SS_DUAL:
baseAnim = BOTH_LK_DL_DL_S_B_1_L;
break;
case SS_STAFF:
baseAnim = BOTH_LK_DL_ST_S_B_1_L;
break;
default://single
baseAnim = BOTH_LK_DL_S_S_B_1_L;
break;
}
break;
case SS_STAFF:
switch ( defenderSaberStyle )
{
case SS_DUAL:
baseAnim = BOTH_LK_ST_DL_S_B_1_L;
break;
case SS_STAFF:
baseAnim = BOTH_LK_ST_ST_S_B_1_L;
break;
default://single
baseAnim = BOTH_LK_ST_S_S_B_1_L;
break;
}
break;
default://single
switch ( defenderSaberStyle )
{
case SS_DUAL:
baseAnim = BOTH_LK_S_DL_S_B_1_L;
break;
case SS_STAFF:
baseAnim = BOTH_LK_S_ST_S_B_1_L;
break;
default://single
baseAnim = BOTH_LK_S_S_S_B_1_L;
break;
}
break;
}
//side lock or top lock?
if ( topOrSide == SABERLOCK_TOP )
{
baseAnim += 5;
}
//lock, break or superbreak?
if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
{
baseAnim += 2;
}
else
{//a break or superbreak
if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK )
{
baseAnim += 3;
}
//winner or loser?
if ( winOrLose == SABERLOCK_WIN )
{
baseAnim += 1;
}
}
}
return baseAnim;
}
#include "../namespace_begin.h"
extern qboolean BG_CheckIncrementLockAnim( int anim, int winOrLose ); //bg_saber.c
#include "../namespace_end.h"
#define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
static GAME_INLINE qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
{
int attAnim, defAnim = 0;
float attStart = 0.5f, defStart = 0.5f;
float idealDist = 48.0f;
vec3_t attAngles, defAngles, defDir;
vec3_t newOrg;
vec3_t attDir;
float diff = 0;
trace_t trace;
//MATCH ANIMS
if ( lockMode == LOCK_RANDOM )
{
lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
}
if ( attacker->client->ps.fd.saberAnimLevel >= SS_FAST
&& attacker->client->ps.fd.saberAnimLevel <= SS_TAVION
&& defender->client->ps.fd.saberAnimLevel >= SS_FAST
&& defender->client->ps.fd.saberAnimLevel <= SS_TAVION )
{//2 single sabers? Just do it the old way...
switch ( lockMode )
{
case LOCK_TOP:
attAnim = BOTH_BF2LOCK;
defAnim = BOTH_BF1LOCK;
attStart = defStart = 0.5f;
idealDist = LOCK_IDEAL_DIST_TOP;
break;
case LOCK_DIAG_TR:
attAnim = BOTH_CCWCIRCLELOCK;
defAnim = BOTH_CWCIRCLELOCK;
attStart = defStart = 0.5f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
case LOCK_DIAG_TL:
attAnim = BOTH_CWCIRCLELOCK;
defAnim = BOTH_CCWCIRCLELOCK;
attStart = defStart = 0.5f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
case LOCK_DIAG_BR:
attAnim = BOTH_CWCIRCLELOCK;
defAnim = BOTH_CCWCIRCLELOCK;
attStart = defStart = 0.85f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
case LOCK_DIAG_BL:
attAnim = BOTH_CCWCIRCLELOCK;
defAnim = BOTH_CWCIRCLELOCK;
attStart = defStart = 0.85f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
case LOCK_R:
attAnim = BOTH_CCWCIRCLELOCK;
defAnim = BOTH_CWCIRCLELOCK;
attStart = defStart = 0.75f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
case LOCK_L:
attAnim = BOTH_CWCIRCLELOCK;
defAnim = BOTH_CCWCIRCLELOCK;
attStart = defStart = 0.75f;
idealDist = LOCK_IDEAL_DIST_CIRCLE;
break;
default:
return qfalse;
break;
}
}
else
{//use the new system
idealDist = LOCK_IDEAL_DIST_JKA;//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
if ( lockMode == LOCK_TOP )
{//top lock
attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN );
defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_LOSE );
attStart = defStart = 0.5f;
}
else
{//side lock
switch ( lockMode )
{
case LOCK_DIAG_TR:
attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
attStart = defStart = 0.5f;
break;
case LOCK_DIAG_TL:
attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
attStart = defStart = 0.5f;
break;
case LOCK_DIAG_BR:
attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
{
attStart = 0.85f;//move to end of anim
}
else
{
attStart = 0.15f;//start at beginning of anim
}
if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
{
defStart = 0.85f;//start at end of anim
}
else
{
defStart = 0.15f;//start at beginning of anim
}
break;
case LOCK_DIAG_BL:
attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
{
attStart = 0.85f;//move to end of anim
}
else
{
attStart = 0.15f;//start at beginning of anim
}
if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
{
defStart = 0.85f;//start at end of anim
}
else
{
defStart = 0.15f;//start at beginning of anim
}
break;
case LOCK_R:
attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
{
attStart = 0.75f;//move to end of anim
}
else
{
attStart = 0.25f;//start at beginning of anim
}
if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
{
defStart = 0.75f;//start at end of anim
}
else
{
defStart = 0.25f;//start at beginning of anim
}
break;
case LOCK_L:
attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
//attacker starts with advantage
if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
{
attStart = 0.75f;//move to end of anim
}
else
{
attStart = 0.25f;//start at beginning of anim
}
if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
{
defStart = 0.75f;//start at end of anim
}
else
{
defStart = 0.25f;//start at beginning of anim
}
break;
default:
return qfalse;
break;
}
}
}
G_SetAnim(attacker, NULL, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
attacker->client->ps.saberLockFrame = bgAllAnims[attacker->localAnimIndex].anims[attAnim].firstFrame+(bgAllAnims[attacker->localAnimIndex].anims[attAnim].numFrames*attStart);
G_SetAnim(defender, NULL, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
defender->client->ps.saberLockFrame = bgAllAnims[defender->localAnimIndex].anims[defAnim].firstFrame+(bgAllAnims[defender->localAnimIndex].anims[defAnim].numFrames*defStart);
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 );
if (attacker->client)
{
VectorCopy(trace.endpos, attacker->client->ps.origin);
}
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 )
{
if (defender->client)
{
VectorCopy(trace.endpos, defender->client->ps.origin);
}
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_debugSaberLocks.integer )
{
WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
return qtrue;
}
//for now.. it's not fair to the lone duelist.
//we need dual saber lock animations.
if (g_gametype.integer == GT_POWERDUEL)
{
return qfalse;
}
if (!g_saberLocking.integer)
{
return qfalse;
}
if (!ent1->client || !ent2->client)
{
return qfalse;
}
if (ent1->s.eType == ET_NPC ||
ent2->s.eType == ET_NPC)
{ //if either ents is NPC, then never let an NPC lock with someone on the same playerTeam
if (ent1->client->playerTeam == ent2->client->playerTeam)
{
return qfalse;
}
}
if (!ent1->client->ps.saberEntityNum ||
!ent2->client->ps.saberEntityNum ||
ent1->client->ps.saberInFlight ||
ent2->client->ps.saberInFlight)
{ //can't get in lock if one of them has had the saber knocked out of his hand
return qfalse;
}
if (ent1->s.eType != ET_NPC && ent2->s.eType != ET_NPC)
{ //can always get into locks with NPCs
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_DUEL && g_gametype.integer != GT_POWERDUEL)
{
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 )
{//between 8 and 80 from each other
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 ( (ent1->client->saber[0].saberFlags&SFL_NOT_LOCKABLE)
|| (ent2->client->saber[0].saberFlags&SFL_NOT_LOCKABLE) )
{
return qfalse;
}
if ( ent1->client->saber[1].model
&& ent1->client->saber[1].model[0]
&& !ent1->client->ps.saberHolstered
&& (ent1->client->saber[1].saberFlags&SFL_NOT_LOCKABLE) )
{
return qfalse;
}
if ( ent2->client->saber[1].model
&& ent2->client->saber[1].model[0]
&& !ent2->client->ps.saberHolstered
&& (ent2->client->saber[1].saberFlags&SFL_NOT_LOCKABLE) )
{
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->client->ps.torsoAnim == BOTH_A6_T__B_ ||
ent1->client->ps.torsoAnim == BOTH_A7_T__B_)
{//ent1 is attacking top-down
return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
}
if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
ent2->client->ps.torsoAnim == BOTH_A5_T__B_ ||
ent2->client->ps.torsoAnim == BOTH_A6_T__B_ ||
ent2->client->ps.torsoAnim == BOTH_A7_T__B_)
{//ent2 is attacking top-down
return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
}
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->client->ps.torsoAnim == BOTH_A6_TR_BL ||
ent1->client->ps.torsoAnim == BOTH_A7_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_A6_TR_BL ||
ent2->client->ps.torsoAnim == BOTH_A7_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_A6_BR_TL ||
ent2->client->ps.torsoAnim == BOTH_A7_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->client->ps.torsoAnim == BOTH_A6_TR_BL ||
ent2->client->ps.torsoAnim == BOTH_A7_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_A6_TR_BL ||
ent1->client->ps.torsoAnim == BOTH_A7_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_A6_BR_TL ||
ent1->client->ps.torsoAnim == BOTH_A7_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->client->ps.torsoAnim == BOTH_A6_TL_BR ||
ent1->client->ps.torsoAnim == BOTH_A7_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_A6_TL_BR ||
ent2->client->ps.torsoAnim == BOTH_A7_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_A6_BL_TR ||
ent2->client->ps.torsoAnim == BOTH_A7_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->client->ps.torsoAnim == BOTH_A6_TL_BR ||
ent2->client->ps.torsoAnim == BOTH_A7_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_A6_TL_BR ||
ent1->client->ps.torsoAnim == BOTH_A7_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_A6_BL_TR ||
ent1->client->ps.torsoAnim == BOTH_A7_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->client->ps.torsoAnim == BOTH_A6__L__R ||
ent1->client->ps.torsoAnim == BOTH_A7__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_A6_TL_BR ||
ent2->client->ps.torsoAnim == BOTH_A7_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->client->ps.torsoAnim == BOTH_A6__L__R ||
ent2->client->ps.torsoAnim == BOTH_A7__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_A6_TL_BR ||
ent1->client->ps.torsoAnim == BOTH_A7_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->client->ps.torsoAnim == BOTH_A6__R__L ||
ent1->client->ps.torsoAnim == BOTH_A7__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_A6_TR_BL ||
ent2->client->ps.torsoAnim == BOTH_A7_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->client->ps.torsoAnim == BOTH_A6__R__L ||
ent2->client->ps.torsoAnim == BOTH_A7__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_A6_TR_BL ||
ent1->client->ps.torsoAnim == BOTH_A7_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;
}
static GAME_INLINE 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;
}
#include "../namespace_begin.h"
int PM_SaberBounceForAttack( int move );
int PM_SaberDeflectionForQuad( int quad );
#include "../namespace_end.h"
extern stringID_table_t animTable[MAX_ANIMATIONS+1];
static GAME_INLINE qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender, float saberHitFraction )
{
qboolean animBasedDeflection = qtrue;
int attSaberLevel, defSaberLevel;
if ( !attacker || !attacker->client || !attacker->ghoul2 )
{
return qfalse;
}
if ( !defender || !defender->client || !defender->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;
}
attSaberLevel = G_SaberAttackPower(attacker, SaberAttacking(attacker));
defSaberLevel = G_SaberAttackPower(defender, SaberAttacking(defender));
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((float)(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
&& (defSaberLevel == attSaberLevel || Q_irand( 0, defSaberLevel-attSaberLevel ) >= 0) )//and the defender's style is stronger
{
//bounce straight back
#ifndef FINAL_BUILD
int attMove = attacker->client->ps.saberMove;
#endif
attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
#ifndef FINAL_BUILD
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 );
}
#endif
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
#ifndef FINAL_BUILD
int attMove = attacker->client->ps.saberMove;
#endif
attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
#ifndef FINAL_BUILD
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 );
}
#endif
attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
return qfalse;
}
//else, pick a deflection
else
{
#ifndef FINAL_BUILD
int attMove = attacker->client->ps.saberMove;
#endif
attacker->client->ps.saberMove = PM_SaberDeflectionForQuad( newQuad );
#ifndef FINAL_BUILD
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 );
}
#endif
attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
return qtrue;
}
}
}
else
{ //old math-based method (probably broken)
vec3_t att_HitDir, def_BladeDir, temp;
float hitDot;
VectorCopy(attacker->client->lastSaberBase_Always, temp);
AngleVectors(attacker->client->lastSaberDir_Always, att_HitDir, 0, 0);
AngleVectors(defender->client->lastSaberDir_Always, def_BladeDir, 0, 0);
//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;
}
}
#define SABER_NONATTACK_DAMAGE 1
//For strong attacks, we ramp damage based on the point in the attack animation
static GAME_INLINE 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 = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[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->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs);
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)
static GAME_INLINE float G_GetAnimPoint(gentity_t *self)
{
int speedDif = 0;
float attackAnimLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[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->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs);
speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor);
attackAnimLength += speedDif;
currentPoint = self->client->ps.torsoTimer;
animPercentage = currentPoint/attackAnimLength;
//Com_Printf("%f\n", animPercentage);
return animPercentage;
}
static GAME_INLINE qboolean G_ClientIdleInWorld(gentity_t *ent)
{
if (ent->s.eType == ET_NPC)
{
return qfalse;
}
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;
}
static GAME_INLINE qboolean G_G2TraceCollide(trace_t *tr, vec3_t lastValidStart, vec3_t lastValidEnd, vec3_t traceMins, vec3_t traceMaxs)
{ //Hit the ent with the normal trace, try the collision trace.
G2Trace_t G2Trace;
gentity_t *g2Hit;
vec3_t angles;
int tN = 0;
float fRadius = 0;
if (!d_saberGhoul2Collision.integer)
{
return qfalse;
}
if (!g_entities[tr->entityNum].inuse /*||
(g_entities[tr->entityNum].s.eFlags & EF_DEAD)*/)
{ //don't do perpoly on corpses.
return qfalse;
}
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->ghoul2)
{
vec3_t g2HitOrigin;
angles[ROLL] = angles[PITCH] = 0;
if (g2Hit->client)
{
VectorCopy(g2Hit->client->ps.origin, g2HitOrigin);
angles[YAW] = g2Hit->client->ps.viewangles[YAW];
}
else
{
VectorCopy(g2Hit->r.currentOrigin, g2HitOrigin);
angles[YAW] = g2Hit->r.currentAngles[YAW];
}
if (g_optvehtrace.integer &&
g2Hit->s.eType == ET_NPC &&
g2Hit->s.NPC_class == CLASS_VEHICLE &&
g2Hit->m_pVehicle)
{
trap_G2API_CollisionDetectCache ( G2Trace, g2Hit->ghoul2, angles, g2HitOrigin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, g_g2TraceLod.integer, fRadius );
}
else
{
trap_G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2HitOrigin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, g_g2TraceLod.integer, 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
{ //The ghoul2 trace result matches, so copy the collision position into the trace endpos and send it back.
VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos);
VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal);
if (g2Hit->client)
{
g2Hit->client->g2LastSurfaceHit = G2Trace[0].mSurfaceIndex;
g2Hit->client->g2LastSurfaceTime = level.time;
}
return qtrue;
}
}
return qfalse;
}
static GAME_INLINE qboolean G_SaberInBackAttack(int move)
{
switch (move)
{
case LS_A_BACK:
case LS_A_BACK_CR:
case LS_A_BACKSTAB:
return qtrue;
}
return qfalse;
}
qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage);
qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
typedef struct saberFace_s
{
vec3_t v1;
vec3_t v2;
vec3_t v3;
} saberFace_t;
//build faces around blade for collision checking -rww
static GAME_INLINE void G_BuildSaberFaces(vec3_t base, vec3_t tip, float radius, vec3_t fwd,
vec3_t right, int *fNum, saberFace_t **fList)
{
static saberFace_t faces[12];
int i = 0;
float *d1 = NULL, *d2 = NULL;
vec3_t invFwd;
vec3_t invRight;
VectorCopy(fwd, invFwd);
VectorInverse(invFwd);
VectorCopy(right, invRight);
VectorInverse(invRight);
while (i < 8)
{
//yeah, this part is kind of a hack, but eh
if (i < 2)
{ //"left" surface
d1 = &fwd[0];
d2 = &invRight[0];
}
else if (i < 4)
{ //"right" surface
d1 = &fwd[0];
d2 = &right[0];
}
else if (i < 6)
{ //"front" surface
d1 = &right[0];
d2 = &fwd[0];
}
else if (i < 8)
{ //"back" surface
d1 = &right[0];
d2 = &invFwd[0];
}
//first triangle for this surface
VectorMA(base, radius/2.0f, d1, faces[i].v1);
VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1);
VectorMA(tip, radius/2.0f, d1, faces[i].v2);
VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2);
VectorMA(tip, -radius/2.0f, d1, faces[i].v3);
VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3);
i++;
//second triangle for this surface
VectorMA(tip, -radius/2.0f, d1, faces[i].v1);
VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1);
VectorMA(base, radius/2.0f, d1, faces[i].v2);
VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2);
VectorMA(base, -radius/2.0f, d1, faces[i].v3);
VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3);
i++;
}
//top surface
//face 1
VectorMA(tip, radius/2.0f, fwd, faces[i].v1);
VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1);
VectorMA(tip, radius/2.0f, fwd, faces[i].v2);
VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2);
VectorMA(tip, -radius/2.0f, fwd, faces[i].v3);
VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3);
i++;
//face 2
VectorMA(tip, radius/2.0f, fwd, faces[i].v1);
VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1);
VectorMA(tip, -radius/2.0f, fwd, faces[i].v2);
VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2);
VectorMA(tip, -radius/2.0f, fwd, faces[i].v3);
VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3);
i++;
//bottom surface
//face 1
VectorMA(base, radius/2.0f, fwd, faces[i].v1);
VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1);
VectorMA(base, radius/2.0f, fwd, faces[i].v2);
VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2);
VectorMA(base, -radius/2.0f, fwd, faces[i].v3);
VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3);
i++;
//face 2
VectorMA(base, radius/2.0f, fwd, faces[i].v1);
VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1);
VectorMA(base, -radius/2.0f, fwd, faces[i].v2);
VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2);
VectorMA(base, -radius/2.0f, fwd, faces[i].v3);
VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3);
i++;
//yeah.. always going to be 12 I suppose.
*fNum = i;
*fList = &faces[0];
}
//collision utility function -rww
static GAME_INLINE void G_SabCol_CalcPlaneEq(vec3_t x, vec3_t y, vec3_t z, float *planeEq)
{
planeEq[0] = x[1]*(y[2]-z[2]) + y[1]*(z[2]-x[2]) + z[1]*(x[2]-y[2]);
planeEq[1] = x[2]*(y[0]-z[0]) + y[2]*(z[0]-x[0]) + z[2]*(x[0]-y[0]);
planeEq[2] = x[0]*(y[1]-z[1]) + y[0]*(z[1]-x[1]) + z[0]*(x[1]-y[1]);
planeEq[3] = -(x[0]*(y[1]*z[2] - z[1]*y[2]) + y[0]*(z[1]*x[2] - x[1]*z[2]) + z[0]*(x[1]*y[2] - y[1]*x[2]) );
}
//collision utility function -rww
static GAME_INLINE int G_SabCol_PointRelativeToPlane(vec3_t pos, float *side, float *planeEq)
{
*side = planeEq[0]*pos[0] + planeEq[1]*pos[1] + planeEq[2]*pos[2] + planeEq[3];
if (*side > 0.0f)
{
return 1;
}
else if (*side < 0.0f)
{
return -1;
}
return 0;
}
//do actual collision check using generated saber "faces"
static GAME_INLINE qboolean G_SaberFaceCollisionCheck(int fNum, saberFace_t *fList, vec3_t atkStart,
vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint)
{
static float planeEq[4];
static float side, side2, dist;
static vec3_t dir;
static vec3_t point;
int i = 0;
if (VectorCompare(atkMins, vec3_origin) && VectorCompare(atkMaxs, vec3_origin))
{
VectorSet(atkMins, -1.0f, -1.0f, -1.0f);
VectorSet(atkMaxs, 1.0f, 1.0f, 1.0f);
}
VectorSubtract(atkEnd, atkStart, dir);
while (i < fNum)
{
G_SabCol_CalcPlaneEq(fList->v1, fList->v2, fList->v3, planeEq);
if (G_SabCol_PointRelativeToPlane(atkStart, &side, planeEq) !=
G_SabCol_PointRelativeToPlane(atkEnd, &side2, planeEq))
{ //start/end points intersect with the plane
static vec3_t extruded;
static vec3_t minPoint, maxPoint;
static vec3_t planeNormal;
static int facing;
VectorCopy(&planeEq[0], planeNormal);
side2 = planeNormal[0]*dir[0] + planeNormal[1]*dir[1] + planeNormal[2]*dir[2];
dist = side/side2;
VectorMA(atkStart, -dist, dir, point);
VectorAdd(point, atkMins, minPoint);
VectorAdd(point, atkMaxs, maxPoint);
//point is now the point at which we intersect on the plane.
//see if that point is within the edges of the face.
VectorMA(fList->v1, -2.0f, planeNormal, extruded);
G_SabCol_CalcPlaneEq(fList->v1, fList->v2, extruded, planeEq);
facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
if (facing < 0)
{ //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
if (facing < 0)
{
facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
}
}
if (facing >= 0)
{ //first edge is facing...
VectorMA(fList->v2, -2.0f, planeNormal, extruded);
G_SabCol_CalcPlaneEq(fList->v2, fList->v3, extruded, planeEq);
facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
if (facing < 0)
{ //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
if (facing < 0)
{
facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
}
}
if (facing >= 0)
{ //second edge is facing...
VectorMA(fList->v3, -2.0f, planeNormal, extruded);
G_SabCol_CalcPlaneEq(fList->v3, fList->v1, extruded, planeEq);
facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
if (facing < 0)
{ //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
if (facing < 0)
{
facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
}
}
if (facing >= 0)
{ //third edge is facing.. success
VectorCopy(point, impactPoint);
return qtrue;
}
}
}
}
i++;
fList++;
}
//did not hit anything
return qfalse;
}
//check for collision of 2 blades -rww
static GAME_INLINE qboolean G_SaberCollide(gentity_t *atk, gentity_t *def, vec3_t atkStart,
vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint)
{
static int i, j;
if (!g_saberBladeFaces.integer)
{ //detailed check not enabled
return qtrue;
}
if (!atk->inuse || !atk->client || !def->inuse || !def->client)
{ //must have 2 clients and a valid saber entity
return qfalse;
}
i = 0;
while (i < MAX_SABERS)
{
j = 0;
if (def->client->saber[i].model && def->client->saber[i].model[0])
{ //valid saber on the defender
bladeInfo_t *blade;
vec3_t v, fwd, right, base, tip;
int fNum;
saberFace_t *fList;
//go through each blade on the defender's sabers
while (j < def->client->saber[i].numBlades)
{
blade = &def->client->saber[i].blade[j];
if ((level.time-blade->storageTime) < 200)
{ //recently updated
//first get base and tip of blade
VectorCopy(blade->muzzlePoint, base);
VectorMA(base, blade->lengthMax, blade->muzzleDir, tip);
//Now get relative angles between the points
VectorSubtract(tip, base, v);
vectoangles(v, v);
AngleVectors(v, NULL, right, fwd);
//now build collision faces for this blade
G_BuildSaberFaces(base, tip, blade->radius*3.0f, fwd, right, &fNum, &fList);
if (fNum > 0)
{
#if 0
if (atk->s.number == 0)
{
int x = 0;
saberFace_t *l = fList;
while (x < fNum)
{
G_TestLine(fList->v1, fList->v2, 0x0000ff, 100);
G_TestLine(fList->v2, fList->v3, 0x0000ff, 100);
G_TestLine(fList->v3, fList->v1, 0x0000ff, 100);
fList++;
x++;
}
fList = l;
}
#endif
if (G_SaberFaceCollisionCheck(fNum, fList, atkStart, atkEnd, atkMins, atkMaxs, impactPoint))
{ //collided
return qtrue;
}
}
}
j++;
}
}
i++;
}
return qfalse;
}
float WP_SaberBladeLength( saberInfo_t *saber )
{//return largest length
int i;
float len = 0.0f;
for ( i = 0; i < saber->numBlades; i++ )
{
if ( saber->blade[i].lengthMax > len )
{
len = saber->blade[i].lengthMax;
}
}
return len;
}
float WP_SaberLength( gentity_t *ent )
{//return largest length
if ( !ent || !ent->client )
{
return 0.0f;
}
else
{
int i;
float len, bestLen = 0.0f;
for ( i = 0; i < MAX_SABERS; i++ )
{
len = WP_SaberBladeLength( &ent->client->saber[i] );
if ( len > bestLen )
{
bestLen = len;
}
}
return bestLen;
}
}
int WPDEBUG_SaberColor( saber_colors_t saberColor )
{
switch( (int)(saberColor) )
{
case SABER_RED:
return 0x000000ff;
break;
case SABER_ORANGE:
return 0x000088ff;
break;
case SABER_YELLOW:
return 0x0000ffff;
break;
case SABER_GREEN:
return 0x0000ff00;
break;
case SABER_BLUE:
return 0x00ff0000;
break;
case SABER_PURPLE:
return 0x00ff00ff;
break;
default:
return 0x00ffffff;//white
break;
}
}
/*
WP_SabersIntersect
Breaks the two saber paths into 2 tris each and tests each tri for the first saber path against each of the other saber path's tris
FIXME: subdivide the arc into a consistant increment
FIXME: test the intersection to see if the sabers really did intersect (weren't going in the same direction and/or passed through same point at different times)?
*/
extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2);
#define SABER_EXTRAPOLATE_DIST 16.0f
qboolean WP_SabersIntersect( gentity_t *ent1, int ent1SaberNum, int ent1BladeNum, gentity_t *ent2, qboolean checkDir )
{
vec3_t saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
vec3_t saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
int ent2SaberNum = 0, ent2BladeNum = 0;
vec3_t dir;
if ( !ent1 || !ent2 )
{
return qfalse;
}
if ( !ent1->client || !ent2->client )
{
return qfalse;
}
if ( BG_SabersOff( &ent1->client->ps )
|| BG_SabersOff( &ent2->client->ps ) )
{
return qfalse;
}
for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ )
{
if ( ent2->client->saber[ent2SaberNum].type != SABER_NONE )
{
for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->saber[ent2SaberNum].numBlades; ent2BladeNum++ )
{
if ( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax > 0 )
{//valid saber and this blade is on
//if ( ent1->client->saberInFlight )
{
VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 );
VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 );
VectorSubtract( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir );
VectorNormalize( dir );
VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 );
VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 );
VectorSubtract( saberTipNext1, saberTip1, dir );
VectorNormalize( dir );
VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
}
/*
else
{
VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 );
VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 );
VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 );
VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 );
}
*/
//if ( ent2->client->saberInFlight )
{
VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 );
VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 );
VectorSubtract( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir );
VectorNormalize( dir );
VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 );
VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 );
VectorSubtract( saberTipNext2, saberTip2, dir );
VectorNormalize( dir );
VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
}
/*
else
{
VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 );
VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 );
VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 );
VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirNext, saberTipNext2 );
}
*/
if ( checkDir )
{//check the direction of the two swings to make sure the sabers are swinging towards each other
vec3_t saberDir1, saberDir2;
float dot = 0.0f;
VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
VectorNormalize( saberDir1 );
VectorNormalize( saberDir2 );
if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
{//sabers moving in same dir, probably didn't actually hit
continue;
}
//now check orientation of sabers, make sure they're not parallel or close to it
dot = DotProduct( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir );
if ( dot > 0.9f || dot < -0.9f )
{//too parallel to really block effectively?
continue;
}
}
#ifdef DEBUG_SABER_BOX
if ( g_saberDebugBox.integer == 2 || g_saberDebugBox.integer == 4 )
{
G_TestLine(saberBase1, saberTip1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
G_TestLine(saberTip1, saberTipNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
G_TestLine(saberTipNext1, saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
G_TestLine(saberBase2, saberTip2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
G_TestLine(saberTip2, saberTipNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
G_TestLine(saberTipNext2, saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
}
#endif
if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
{
return qtrue;
}
if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
{
return qtrue;
}
if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
{
return qtrue;
}
if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
{
return qtrue;
}
}
}
}
}
return qfalse;
}
static GAME_INLINE int G_PowerLevelForSaberAnim( gentity_t *ent, int saberNum, qboolean mySaberHit )
{
if ( !ent || !ent->client || saberNum >= MAX_SABERS )
{
return FORCE_LEVEL_0;
}
else
{
int anim = ent->client->ps.torsoAnim;
int animTimer = ent->client->ps.torsoTimer;
int animTimeElapsed = BG_AnimLength( ent->localAnimIndex, (animNumber_t)anim ) - animTimer;
saberInfo_t *saber = &ent->client->saber[saberNum];
if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_D1_B____ )
{
//FIXME: these two need their own style
if ( saber->type == SABER_LANCE )
{
return FORCE_LEVEL_4;
}
else if ( saber->type == SABER_TRIDENT )
{
return FORCE_LEVEL_3;
}
return FORCE_LEVEL_1;
}
if ( anim >= BOTH_A2_T__B_ && anim <= BOTH_D2_B____ )
{
return FORCE_LEVEL_2;
}
if ( anim >= BOTH_A3_T__B_ && anim <= BOTH_D3_B____ )
{
return FORCE_LEVEL_3;
}
if ( anim >= BOTH_A4_T__B_ && anim <= BOTH_D4_B____ )
{//desann
return FORCE_LEVEL_4;
}
if ( anim >= BOTH_A5_T__B_ && anim <= BOTH_D5_B____ )
{//tavion
return FORCE_LEVEL_2;
}
if ( anim >= BOTH_A6_T__B_ && anim <= BOTH_D6_B____ )
{//dual
return FORCE_LEVEL_2;
}
if ( anim >= BOTH_A7_T__B_ && anim <= BOTH_D7_B____ )
{//staff
return FORCE_LEVEL_2;
}
if ( anim >= BOTH_P1_S1_T_ && anim <= BOTH_H1_S1_BR )
{//parries, knockaways and broken parries
return FORCE_LEVEL_1;//FIXME: saberAnimLevel?
}
switch ( anim )
{
case BOTH_A2_STABBACK1:
if ( mySaberHit )
{//someone else hit my saber, not asking for damage level, but defense strength
return FORCE_LEVEL_1;
}
if ( animTimer < 450 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 400 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_ATTACK_BACK:
if ( animTimer < 500 )
{//end of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_CROUCHATTACKBACK1:
if ( animTimer < 800 )
{//end of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_BUTTERFLY_LEFT:
case BOTH_BUTTERFLY_RIGHT:
case BOTH_BUTTERFLY_FL1:
case BOTH_BUTTERFLY_FR1:
//FIXME: break up?
return FORCE_LEVEL_3;
break;
case BOTH_FJSS_TR_BL:
case BOTH_FJSS_TL_BR:
//FIXME: break up?
return FORCE_LEVEL_3;
break;
case BOTH_K1_S1_T_: //# knockaway saber top
case BOTH_K1_S1_TR: //# knockaway saber top right
case BOTH_K1_S1_TL: //# knockaway saber top left
case BOTH_K1_S1_BL: //# knockaway saber bottom left
case BOTH_K1_S1_B_: //# knockaway saber bottom
case BOTH_K1_S1_BR: //# knockaway saber bottom right
//FIXME: break up?
return FORCE_LEVEL_3;
break;
case BOTH_LUNGE2_B__T_:
if ( mySaberHit )
{//someone else hit my saber, not asking for damage level, but defense strength
return FORCE_LEVEL_1;
}
if ( animTimer < 400 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 150 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_FORCELEAP2_T__B_:
if ( animTimer < 400 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 550 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_VS_ATR_S:
case BOTH_VS_ATL_S:
case BOTH_VT_ATR_S:
case BOTH_VT_ATL_S:
return FORCE_LEVEL_3;//???
break;
case BOTH_JUMPFLIPSLASHDOWN1:
if ( animTimer <= 1000 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 600 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_JUMPFLIPSTABDOWN:
if ( animTimer <= 1300 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed <= 300 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_JUMPATTACK6:
/*
if (pm->ps)
{
if ( ( pm->ps->legsAnimTimer >= 1450
&& BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 400 )
||(pm->ps->legsAnimTimer >= 400
&& BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 1100 ) )
{//pretty much sideways
return FORCE_LEVEL_3;
}
}
*/
if ( ( animTimer >= 1450
&& animTimeElapsed >= 400 )
||(animTimer >= 400
&& animTimeElapsed >= 1100 ) )
{//pretty much sideways
return FORCE_LEVEL_3;
}
return FORCE_LEVEL_0;
break;
case BOTH_JUMPATTACK7:
if ( animTimer <= 1200 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 200 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_SPINATTACK6:
if ( animTimeElapsed <= 200 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_2;//FORCE_LEVEL_3;
break;
case BOTH_SPINATTACK7:
if ( animTimer <= 500 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 500 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_2;//FORCE_LEVEL_3;
break;
case BOTH_FORCELONGLEAP_ATTACK:
if ( animTimeElapsed <= 200 )
{//1st four frames of anim
return FORCE_LEVEL_3;
}
break;
/*
case BOTH_A7_KICK_F://these kicks attack, too
case BOTH_A7_KICK_B:
case BOTH_A7_KICK_R:
case BOTH_A7_KICK_L:
//FIXME: break up
return FORCE_LEVEL_3;
break;
*/
case BOTH_STABDOWN:
if ( animTimer <= 900 )
{//end of anim
return FORCE_LEVEL_3;
}
break;
case BOTH_STABDOWN_STAFF:
if ( animTimer <= 850 )
{//end of anim
return FORCE_LEVEL_3;
}
break;
case BOTH_STABDOWN_DUAL:
if ( animTimer <= 900 )
{//end of anim
return FORCE_LEVEL_3;
}
break;
case BOTH_A6_SABERPROTECT:
if ( animTimer < 650 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 250 )
{//start of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_A7_SOULCAL:
if ( animTimer < 650 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 600 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_A1_SPECIAL:
if ( animTimer < 600 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 200 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_A2_SPECIAL:
if ( animTimer < 300 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 200 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_A3_SPECIAL:
if ( animTimer < 700 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 200 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_FLIP_ATTACK7:
return FORCE_LEVEL_3;
break;
case BOTH_PULL_IMPALE_STAB:
if ( mySaberHit )
{//someone else hit my saber, not asking for damage level, but defense strength
return FORCE_LEVEL_1;
}
if ( animTimer < 1000 )
{//end of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_PULL_IMPALE_SWING:
if ( animTimer < 500 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 650 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_ALORA_SPIN_SLASH:
if ( animTimer < 900 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 250 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_A6_FB:
if ( mySaberHit )
{//someone else hit my saber, not asking for damage level, but defense strength
return FORCE_LEVEL_1;
}
if ( animTimer < 250 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 250 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_A6_LR:
if ( mySaberHit )
{//someone else hit my saber, not asking for damage level, but defense strength
return FORCE_LEVEL_1;
}
if ( animTimer < 250 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 250 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_3;
break;
case BOTH_A7_HILT:
return FORCE_LEVEL_0;
break;
//===SABERLOCK SUPERBREAKS START===========================================================================
case BOTH_LK_S_DL_T_SB_1_W:
if ( animTimer < 700 )
{//end of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_S_ST_S_SB_1_W:
if ( animTimer < 300 )
{//end of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_S_DL_S_SB_1_W:
case BOTH_LK_S_S_S_SB_1_W:
if ( animTimer < 700 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 400 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_S_ST_T_SB_1_W:
case BOTH_LK_S_S_T_SB_1_W:
if ( animTimer < 150 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 400 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_DL_DL_T_SB_1_W:
return FORCE_LEVEL_5;
break;
case BOTH_LK_DL_DL_S_SB_1_W:
case BOTH_LK_DL_ST_S_SB_1_W:
if ( animTimeElapsed < 1000 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_DL_ST_T_SB_1_W:
if ( animTimer < 950 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 650 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_DL_S_S_SB_1_W:
if ( saberNum != 0 )
{//only right hand saber does damage in this suberbreak
return FORCE_LEVEL_0;
}
if ( animTimer < 900 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 450 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_DL_S_T_SB_1_W:
if ( saberNum != 0 )
{//only right hand saber does damage in this suberbreak
return FORCE_LEVEL_0;
}
if ( animTimer < 250 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 150 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_ST_DL_S_SB_1_W:
return FORCE_LEVEL_5;
break;
case BOTH_LK_ST_DL_T_SB_1_W:
//special suberbreak - doesn't kill, just kicks them backwards
return FORCE_LEVEL_0;
break;
case BOTH_LK_ST_ST_S_SB_1_W:
case BOTH_LK_ST_S_S_SB_1_W:
if ( animTimer < 800 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 350 )
{//beginning of anim
return FORCE_LEVEL_0;
}
return FORCE_LEVEL_5;
break;
case BOTH_LK_ST_ST_T_SB_1_W:
case BOTH_LK_ST_S_T_SB_1_W:
return FORCE_LEVEL_5;
break;
//===SABERLOCK SUPERBREAKS START===========================================================================
case BOTH_HANG_ATTACK:
//FIME: break up
if ( animTimer < 1000 )
{//end of anim
return FORCE_LEVEL_0;
}
else if ( animTimeElapsed < 250 )
{//beginning of anim
return FORCE_LEVEL_0;
}
else
{//sweet spot
return FORCE_LEVEL_5;
}
break;
case BOTH_ROLL_STAB:
if ( mySaberHit )
{//someone else hit my saber, not asking for damage level, but defense strength
return FORCE_LEVEL_1;
}
if ( animTimeElapsed > 400 )
{//end of anim
return FORCE_LEVEL_0;
}
else
{
return FORCE_LEVEL_3;
}
break;
}
return FORCE_LEVEL_0;
}
}
#define MAX_SABER_VICTIMS 16
static int victimEntityNum[MAX_SABER_VICTIMS];
static qboolean victimHitEffectDone[MAX_SABER_VICTIMS];
static float totalDmg[MAX_SABER_VICTIMS];
static vec3_t dmgDir[MAX_SABER_VICTIMS];
static vec3_t dmgSpot[MAX_SABER_VICTIMS];
static qboolean dismemberDmg[MAX_SABER_VICTIMS];
static int saberKnockbackFlags[MAX_SABER_VICTIMS];
static int numVictims = 0;
void WP_SaberClearDamage( void )
{
int ven;
for ( ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
{
victimEntityNum[ven] = ENTITYNUM_NONE;
}
memset( victimHitEffectDone, 0, sizeof( victimHitEffectDone ) );
memset( totalDmg, 0, sizeof( totalDmg ) );
memset( dmgDir, 0, sizeof( dmgDir ) );
memset( dmgSpot, 0, sizeof( dmgSpot ) );
memset( dismemberDmg, 0, sizeof( dismemberDmg ) );
memset( saberKnockbackFlags, 0, sizeof( saberKnockbackFlags ) );
numVictims = 0;
}
void WP_SaberDamageAdd( int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot,
int trDmg, qboolean doDismemberment, int knockBackFlags )
{
if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
{
return;
}
if ( trDmg )
{//did some damage to something
int curVictim = 0;
int i;
for ( i = 0; i < numVictims; i++ )
{
if ( victimEntityNum[i] == trVictimEntityNum )
{//already hit this guy before
curVictim = i;
break;
}
}
if ( i == numVictims )
{//haven't hit his guy before
if ( numVictims + 1 >= MAX_SABER_VICTIMS )
{//can't add another victim at this time
return;
}
//add a new victim to the list
curVictim = numVictims;
victimEntityNum[numVictims++] = trVictimEntityNum;
}
totalDmg[curVictim] += trDmg;
if ( VectorCompare( dmgDir[curVictim], vec3_origin ) )
{
VectorCopy( trDmgDir, dmgDir[curVictim] );
}
if ( VectorCompare( dmgSpot[curVictim], vec3_origin ) )
{
VectorCopy( trDmgSpot, dmgSpot[curVictim] );
}
if ( doDismemberment )
{
dismemberDmg[curVictim] = qtrue;
}
saberKnockbackFlags[curVictim] |= knockBackFlags;
}
}
void WP_SaberApplyDamage( gentity_t *self )
{
int i;
if ( !numVictims )
{
return;
}
for ( i = 0; i < numVictims; i++ )
{
gentity_t *victim = NULL;
int dflags = 0;
victim = &g_entities[victimEntityNum[i]];
// nmckenzie: SABER_DAMAGE_WALLS
if ( !victim->client )
{
totalDmg[i] *= g_saberWallDamageScale.value;
}
if ( !dismemberDmg[i] )
{//don't do dismemberment!
dflags |= DAMAGE_NO_DISMEMBER;
}
dflags |= saberKnockbackFlags[i];
G_Damage( victim, self, self, dmgDir[i], dmgSpot[i], totalDmg[i], dflags, MOD_SABER );
}
}
void WP_SaberDoHit( gentity_t *self, int saberNum, int bladeNum )
{
int i;
if ( !numVictims )
{
return;
}
for ( i = 0; i < numVictims; i++ )
{
gentity_t *te = NULL, *victim = NULL;
qboolean isDroid = qfalse;
if ( victimHitEffectDone[i] )
{
continue;
}
victimHitEffectDone[i] = qtrue;
victim = &g_entities[victimEntityNum[i]];
if ( victim->client )
{
class_t npc_class = victim->client->NPC_class;
if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
{ //don't make "blood" sparks for droids.
isDroid = qtrue;
}
}
te = G_TempEntity( dmgSpot[i], EV_SABER_HIT );
if ( te )
{
te->s.otherEntityNum = victimEntityNum[i];
te->s.otherEntityNum2 = self->s.number;
te->s.weapon = saberNum;
te->s.legsAnim = bladeNum;
VectorCopy(dmgSpot[i], te->s.origin);
//VectorCopy(tr.plane.normal, te->s.angles);
VectorScale( dmgDir[i], -1, 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 (!isDroid && (victim->client || victim->s.eType == ET_NPC ||
victim->s.eType == ET_BODY))
{
if ( totalDmg[i] < 5 )
{
te->s.eventParm = 3;
}
else if (totalDmg[i] < 20 )
{
te->s.eventParm = 2;
}
else
{
te->s.eventParm = 1;
}
}
else
{
if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[saberNum], bladeNum )
&& (self->client->saber[saberNum].saberFlags2&SFL2_NO_CLASH_FLARE) )
{//don't do clash flare
}
else if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[saberNum], bladeNum )
&& (self->client->saber[saberNum].saberFlags2&SFL2_NO_CLASH_FLARE2) )
{//don't do clash flare
}
else
{
if (totalDmg[i] > SABER_NONATTACK_DAMAGE)
{ //I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing.
gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE );
VectorCopy(te->s.origin, teS->s.origin);
}
te->s.eventParm = 0;
}
}
}
}
}
extern qboolean G_EntIsBreakable( int entityNum );
extern void G_Knockdown( gentity_t *victim );
void WP_SaberRadiusDamage( gentity_t *ent, vec3_t point, float radius, int damage, float knockBack )
{
if ( !ent || !ent->client )
{
return;
}
else if ( radius <= 0.0f || (damage <= 0 && knockBack <= 0) )
{
return;
}
else
{
vec3_t mins, maxs, entDir;
int radiusEnts[128];
gentity_t *radiusEnt = NULL;
int numEnts, i;
float dist;
//Setup the bbox to search in
for ( i = 0; i < 3; i++ )
{
mins[i] = point[i] - radius;
maxs[i] = point[i] + radius;
}
//Get the number of entities in a given space
numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, 128 );
for ( i = 0; i < numEnts; i++ )
{
radiusEnt = &g_entities[radiusEnts[i]];
if ( !radiusEnt->inuse )
{
continue;
}
if ( radiusEnt == ent )
{//Skip myself
continue;
}
if ( radiusEnt->client == NULL )
{//must be a client
if ( G_EntIsBreakable( radiusEnt->s.number ) )
{//damage breakables within range, but not as much
G_Damage( radiusEnt, ent, ent, vec3_origin, radiusEnt->r.currentOrigin, 10, 0, MOD_MELEE );
}
continue;
}
if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
{//can't be one being held
continue;
}
VectorSubtract( radiusEnt->r.currentOrigin, point, entDir );
dist = VectorNormalize( entDir );
if ( dist <= radius )
{//in range
if ( damage > 0 )
{//do damage
int points = ceil((float)damage*dist/radius);
G_Damage( radiusEnt, ent, ent, vec3_origin, radiusEnt->r.currentOrigin, points, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
if ( knockBack > 0 )
{//do knockback
if ( radiusEnt->client
&& radiusEnt->client->NPC_class != CLASS_RANCOR
&& radiusEnt->client->NPC_class != CLASS_ATST
&& !(radiusEnt->flags&FL_NO_KNOCKBACK) )//don't throw them back
{
float knockbackStr = knockBack*dist/radius;
entDir[2] += 0.1f;
VectorNormalize( entDir );
G_Throw( radiusEnt, entDir, knockbackStr );
if ( radiusEnt->health > 0 )
{//still alive
if ( knockbackStr > 50 )
{//close enough and knockback high enough to possibly knock down
if ( dist < (radius*0.5f)
|| radiusEnt->client->ps.groundEntityNum != ENTITYNUM_NONE )
{//within range of my fist or within ground-shaking range and not in the air
G_Knockdown( radiusEnt );//, ent, entDir, 500, qtrue );
}
}
}
}
}
}
}
}
}
static qboolean saberDoClashEffect = qfalse;
static vec3_t saberClashPos = {0};
static vec3_t saberClashNorm = {0};
static int saberClashEventParm = 1;
void WP_SaberDoClash( gentity_t *self, int saberNum, int bladeNum )
{
if ( saberDoClashEffect )
{
gentity_t *te = G_TempEntity( saberClashPos, EV_SABER_BLOCK );
VectorCopy(saberClashPos, te->s.origin);
VectorCopy(saberClashNorm, te->s.angles);
te->s.eventParm = saberClashEventParm;
te->s.otherEntityNum2 = self->s.number;
te->s.weapon = saberNum;
te->s.legsAnim = bladeNum;
}
}
void WP_SaberBounceSound( gentity_t *ent, int saberNum, int bladeNum )
{
int index = 1;
if ( !ent || !ent->client )
{
return;
}
index = Q_irand( 1, 9 );
if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
&& ent->client->saber[saberNum].bounceSound[0] )
{
G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].bounceSound[Q_irand( 0, 2 )] );
}
else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
&& ent->client->saber[saberNum].bounce2Sound[0] )
{
G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].bounce2Sound[Q_irand( 0, 2 )] );
}
else if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
&& ent->client->saber[saberNum].blockSound[0] )
{
G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].blockSound[Q_irand( 0, 2 )] );
}
else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
&& ent->client->saber[saberNum].block2Sound[0] )
{
G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
}
else
{
G_Sound( ent, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
}
}
static qboolean saberHitWall = qfalse;
static qboolean saberHitSaber = qfalse;
static float saberHitFraction = 1.0f;
//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.
//This is a large function. I feel sort of bad inlining it. But it does get called tons of times per frame.
#include "../namespace_begin.h"
qboolean BG_SuperBreakWinAnim( int anim );
#include "../namespace_end.h"
static GAME_INLINE qboolean CheckSaberDamage(gentity_t *self, int rSaberNum, int rBladeNum, vec3_t saberStart, vec3_t saberEnd, qboolean doInterpolate, int trMask, qboolean extrapolate )
{
static trace_t tr;
static vec3_t dir;
static vec3_t saberTrMins, saberTrMaxs;
static vec3_t lastValidStart;
static vec3_t lastValidEnd;
static int selfSaberLevel;
static int otherSaberLevel;
int dmg = 0;
int attackStr = 0;
float saberBoxSize = d_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 (BG_SabersOff( &self->client->ps ))
{
return qfalse;
}
selfSaberLevel = G_SaberAttackPower(self, SaberAttacking(self));
//Add the standard radius into the box size
saberBoxSize += (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.5f);
if (self->client->ps.weaponTime <= 0)
{ //if not doing any attacks or anything, just use point traces.
VectorClear(saberTrMins);
VectorClear(saberTrMaxs);
}
else if (d_saberGhoul2Collision.integer)
{
if ( d_saberSPStyleDamage.integer )
{//SP-size saber damage traces
VectorSet(saberTrMins, -2, -2, -2 );
VectorSet(saberTrMaxs, 2, 2, 2 );
}
else
{
VectorSet(saberTrMins, -saberBoxSize*3, -saberBoxSize*3, -saberBoxSize*3);
VectorSet(saberTrMaxs, saberBoxSize*3, saberBoxSize*3, saberBoxSize*3);
}
}
else 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 (d_saberAlwaysBoxTrace.integer)
{
VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
}
else
{ //just trace the minimum blade radius
saberBoxSize = (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.4f);
VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
}
while (!saberTraceDone)
{
if ( doInterpolate
&& !d_saberSPStyleDamage.integer )
{ //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;
if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
{//no valid last pos, use current
VectorCopy(saberStart, oldSaberStart);
VectorCopy(saberEnd, oldSaberEnd);
}
else
{//trace from last pos
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, 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);
trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask);
VectorCopy(saberEnd, lastValidStart);
VectorCopy(saberStart, lastValidEnd);
if (tr.entityNum < MAX_CLIENTS)
{
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
else if (tr.entityNum < ENTITYNUM_WORLD)
{
gentity_t *trHit = &g_entities[tr.entityNum];
if (trHit->inuse && trHit->ghoul2)
{ //hit a non-client entity with a g2 instance
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
}
trDif++;
while (tr.fraction == 1.0 && traceTests < 4 && tr.entityNum >= ENTITYNUM_NONE)
{
if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
{//no valid last pos, use current
VectorCopy(saberStart, oldSaberStart);
VectorCopy(saberEnd, oldSaberEnd);
}
else
{//trace from last pos
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, 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);
trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask);
VectorCopy(saberEnd, lastValidStart);
VectorCopy(saberStart, lastValidEnd);
if (tr.entityNum < MAX_CLIENTS)
{
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
else if (tr.entityNum < ENTITYNUM_WORLD)
{
gentity_t *trHit = &g_entities[tr.entityNum];
if (trHit->inuse && trHit->ghoul2)
{ //hit a non-client entity with a g2 instance
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
}
traceTests++;
trDif += 8;
}
}
else
{
vec3_t saberEndExtrapolated;
if ( extrapolate )
{//extrapolate 16
vec3_t diff;
VectorSubtract( saberEnd, saberStart, diff );
VectorNormalize( diff );
VectorMA( saberStart, SABER_EXTRAPOLATE_DIST, diff, saberEndExtrapolated );
}
else
{
VectorCopy( saberEnd, saberEndExtrapolated );
}
trap_Trace(&tr, saberStart, saberTrMins, saberTrMaxs, saberEndExtrapolated, self->s.number, trMask);
VectorCopy(saberStart, lastValidStart);
VectorCopy(saberEndExtrapolated, lastValidEnd);
/*
if ( tr.allsolid || tr.startsolid )
{
if ( tr.entityNum == ENTITYNUM_NONE )
{
qboolean whah = qtrue;
}
Com_Printf( "saber trace start/all solid - ent is %d\n", tr.entityNum );
}
*/
if (tr.entityNum < MAX_CLIENTS)
{
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
else if (tr.entityNum < ENTITYNUM_WORLD)
{
gentity_t *trHit = &g_entities[tr.entityNum];
if (trHit->inuse && trHit->ghoul2)
{ //hit a non-client entity with a g2 instance
G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
}
}
}
saberTraceDone = qtrue;
}
if ( self->client->ps.saberAttackWound < level.time
&& (SaberAttacking(self)
|| BG_SuperBreakWinAnim(self->client->ps.torsoAnim)
|| (d_saberSPStyleDamage.integer&&self->client->ps.saberInFlight&&rSaberNum==0)
|| (WP_SaberBladeDoTransitionDamage( &self->client->saber[rSaberNum], rBladeNum )&&BG_SaberInTransitionAny(self->client->ps.saberMove))
|| (self->client->ps.m_iVehicleNum && self->client->ps.saberMove > LS_READY) )
)
{ //this animation is that of the last attack movement, and so it should do full damage
qboolean saberInSpecial = BG_SaberInSpecial(self->client->ps.saberMove);
qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove);
if ( d_saberSPStyleDamage.integer )
{
float fDmg = 0.0f;
if ( self->client->ps.saberInFlight )
{
gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum];
if ( !saberEnt
|| !saberEnt->s.saberInFlight )
{//does less damage on the way back
fDmg = 1.0f;
attackStr = FORCE_LEVEL_0;
}
else
{
fDmg = 2.5f*self->client->ps.fd.forcePowerLevel[FP_SABERTHROW];
attackStr = FORCE_LEVEL_1;
}
}
else
{
attackStr = G_PowerLevelForSaberAnim( self, rSaberNum, qfalse );
if ( g_saberRealisticCombat.integer )
{
switch ( attackStr )
{
default:
case FORCE_LEVEL_3:
fDmg = 10.0f;
break;
case FORCE_LEVEL_2:
fDmg = 5.0f;
break;
case FORCE_LEVEL_1:
case FORCE_LEVEL_0:
fDmg = 2.5f;
break;
}
}
else
{
if ( self->client->ps.torsoAnim == BOTH_SPINATTACK6
|| self->client->ps.torsoAnim == BOTH_SPINATTACK7 )
{//too easy to do, lower damage
fDmg = 2.5f;
}
else
{
fDmg = 2.5f * (float)attackStr;
}
}
}
if ( g_saberRealisticCombat.integer > 1 )
{//always do damage, and lots of it
if ( g_saberRealisticCombat.integer > 2 )
{//always do damage, and lots of it
fDmg = 25.0f;
}
else if ( fDmg > 0.1f )
{//only do super damage if we would have done damage according to normal rules
fDmg = 25.0f;
}
}
/*
if ( dmg > 0.1f )
{
if ( (self->client->ps.forcePowersActive&(1<<FP_RAGE)) )
{//add some damage if raged
dmg += self->client->ps.forcePowerLevel[FP_RAGE] * 5.0f;
}
else if ( self->client->ps.forceRageRecoveryTime )
{//halve it if recovering
dmg *= 0.5f;
}
}
*/
if ( g_gametype.integer != GT_DUEL
&& g_gametype.integer != GT_POWERDUEL
&& g_gametype.integer != GT_SIEGE )
{//in faster-paced games, sabers do more damage
fDmg *= 2.0f;
}
if ( fDmg )
{//the longer the trace, the more damage it does
//FIXME: in SP, we only use the part of the trace that's actually *inside* the hit ent...
float traceLength = Distance( saberEnd, saberStart );
if ( tr.fraction >= 1.0f )
{//allsolid?
dmg = ceil( fDmg*traceLength*0.1f*0.33f );
}
else
{//fractional hit, the sooner you hit in the trace, the more damage you did
dmg = ceil( fDmg*traceLength*(1.0f-tr.fraction)*0.1f*0.33f );//(1.0f-tr.fraction) isn't really accurate, but kind of simulates what we have in SP
}
#ifdef DEBUG_SABER_BOX
if ( g_saberDebugBox.integer == 3 || g_saberDebugBox.integer == 4 )
{
G_TestLine( saberStart, saberEnd, 0x0000ff, 50 );
}
#endif
}
/*
if ( dmg )
{
Com_Printf("CL %i SABER DMG: %i, anim %s, torsoTimer %i\n", self->s.number, dmg, animTable[self->client->ps.torsoAnim].name, self->client->ps.torsoTimer );
}
*/
if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL
|| self->client->ps.torsoAnim == BOTH_A2_SPECIAL
|| self->client->ps.torsoAnim == BOTH_A3_SPECIAL )
{//parry/block/break-parry bonus for single-style kata moves
attackStr++;
}
if ( BG_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
{
trMask &= ~CONTENTS_LIGHTSABER;
}
}
else
{
dmg = SABER_HITDAMAGE;
if (self->client->ps.fd.saberAnimLevel == SS_STAFF ||
self->client->ps.fd.saberAnimLevel == SS_DUAL)
{
if (saberInSpecial)
{
//it will get auto-ramped based on the point in the attack, later on
if (self->client->ps.saberMove == LS_SPINATTACK ||
self->client->ps.saberMove == LS_SPINATTACK_DUAL)
{ //these attacks are long and have the potential to hit a lot so they will do less damage.
dmg = 10;
}
else
{
if ( BG_KickingAnim( self->client->ps.legsAnim ) ||
BG_KickingAnim( self->client->ps.torsoAnim ) )
{ //saber shouldn't do more than min dmg during kicks
dmg = 2;
}
else if (BG_SaberInKata(self->client->ps.saberMove))
{ //special kata move
if (self->client->ps.fd.saberAnimLevel == SS_DUAL)
{ //this is the nasty saber twirl, do big damage cause it makes you vulnerable
dmg = 90;
}
else
{ //staff kata
dmg = G_GetAttackDamage(self, 60, 70, 0.5f);
}
}
else
{
//dmg = 90;
//ramp from 2 to 90 by default for other specials
dmg = G_GetAttackDamage(self, 2, 90, 0.5f);
}
}
}
else
{ //otherwise we'll ramp up to 70 I guess, for both dual and staff
dmg = G_GetAttackDamage(self, 2, 70, 0.5f);
}
}
else if (self->client->ps.fd.saberAnimLevel == 3)
{
//new damage-ramping system
if (!saberInSpecial && !inBackAttack)
{
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 if (inBackAttack)
{
dmg = G_GetAttackDamage(self, 2, 30, 0.5f); //can hit multiple times (and almost always does), so..
}
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 85
dmg = G_GetAttackDamage(self, 2, 80, 0.5f);
}
else if (inBackAttack)
{
dmg = G_GetAttackDamage(self, 2, 25, 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 if (inBackAttack)
{
dmg = G_GetAttackDamage(self, 2, 30, 0.5f);
}
else
{
dmg = SABER_HITDAMAGE;
}
}
attackStr = self->client->ps.fd.saberAnimLevel;
}
}
else if (self->client->ps.saberAttackWound < level.time &&
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)
if ( (self->client->saber[0].saberFlags2&SFL2_NO_IDLE_EFFECT) )
{//no idle damage or effects
return qtrue;//true cause even though we didn't get a hit, we don't want to do those extra traces because the debounce time says not to.
}
trMask &= ~CONTENTS_LIGHTSABER;
if ( d_saberSPStyleDamage.integer )
{
if ( BG_SaberInReturn( self->client->ps.saberMove ) )
{
dmg = SABER_NONATTACK_DAMAGE;
}
else
{
if (d_saberSPStyleDamage.integer == 2)
{
dmg = SABER_NONATTACK_DAMAGE;
}
else
{
dmg = 0;
}
}
}
else
{
dmg = SABER_NONATTACK_DAMAGE;
}
idleDamage = qtrue;
}
else
{
return qtrue; //true cause even though we didn't get a hit, we don't want to do those extra traces because the debounce time says not to.
}
if (BG_SaberInSpecial(self->client->ps.saberMove))
{
qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove);
unblockable = qtrue;
self->client->ps.saberBlocked = 0;
if ( d_saberSPStyleDamage.integer )
{
}
else if (!inBackAttack)
{
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 if (self->client->ps.saberMove == LS_SPINATTACK ||
self->client->ps.saberMove == LS_SPINATTACK_DUAL)
{ //do a constant significant amount of damage but ramp up a little to the mid-point
dmg = G_GetAttackDamage(self, 2, dmg+3, 0.5f);
dmg += 10;
}
else
{
//dmg += 20;
if ( BG_KickingAnim( self->client->ps.legsAnim ) ||
BG_KickingAnim( self->client->ps.torsoAnim ) )
{ //saber shouldn't do more than min dmg during kicks
dmg = 2;
}
else
{ //auto-ramp it I guess since it's a special we don't have a special case for.
dmg = G_GetAttackDamage(self, 5, dmg+5, 0.5f);
}
}
}
}
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)
{
dmg *= g_saberDamageScale.value;
//see if this specific saber has a damagescale
if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
&& self->client->saber[rSaberNum].damageScale != 1.0f )
{
dmg = ceil( (float)dmg*self->client->saber[rSaberNum].damageScale );
}
else if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
&& self->client->saber[rSaberNum].damageScale2 != 1.0f )
{
dmg = ceil( (float)dmg*self->client->saber[rSaberNum].damageScale2 );
}
if ((self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) ||
(self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
{ //weaken it if an arm is broken
dmg *= 0.3;
if (dmg <= SABER_NONATTACK_DAMAGE)
{
dmg = SABER_NONATTACK_DAMAGE+1;
}
}
}
if (dmg > SABER_NONATTACK_DAMAGE && self->client->ps.isJediMaster)
{ //give the Jedi Master more saber attack power
dmg *= 2;
}
if (dmg > SABER_NONATTACK_DAMAGE && g_gametype.integer == GT_SIEGE &&
self->client->siegeClass != -1 && (bgSiegeClasses[self->client->siegeClass].classflags & (1<<CFL_MORESABERDMG)))
{ //this class is flagged to do extra saber damage. I guess 2x will do for now.
dmg *= 2;
}
if (g_gametype.integer == GT_POWERDUEL &&
self->client->sess.duelTeam == DUELTEAM_LONE)
{ //always x2 when we're powerdueling alone... er, so, we apparently no longer want this? So they say.
if ( g_duel_fraglimit.integer )
{
//dmg *= 1.5 - (.4 * (float)self->client->sess.wins / (float)g_duel_fraglimit.integer);
}
//dmg *= 2;
}
#ifndef FINAL_BUILD
if (g_saberDebugPrint.integer > 2 && dmg > 1)
{
Com_Printf("CL %i SABER DMG: %i\n", self->s.number, dmg);
}
#endif
VectorSubtract(saberEnd, saberStart, dir);
VectorNormalize(dir);
if (tr.entityNum == ENTITYNUM_WORLD ||
g_entities[tr.entityNum].s.eType == ET_TERRAIN)
{ //register this as a wall hit for jedi AI
self->client->ps.saberEventFlags |= SEF_HITWALL;
saberHitWall = qtrue;
}
if (saberHitWall
&& (self->client->saber[rSaberNum].saberFlags & SFL_BOUNCE_ON_WALLS)
&& (BG_SaberInAttackPure( self->client->ps.saberMove ) //only in a normal attack anim
|| self->client->ps.saberMove == LS_A_JUMP_T__B_ ) //or in the strong jump-fwd-attack "death from above" move
)
{ //then bounce off
/*
qboolean onlyIfNotSpecial = qfalse;
qboolean skipIt = qfalse;
if (tr.plane.normal[2] >= 0.8f ||
tr.plane.normal[2] <= -0.8f ||
VectorCompare(tr.plane.normal, vec3_origin))
{
if ((tr.plane.normal[2] >= 0.8f || VectorCompare(tr.plane.normal, vec3_origin)) &&
self->client->ps.viewangles[PITCH] <= 30.0f &&
self->client->pers.cmd.upmove >= 0)
{ //don't hit the ground if we are not looking down a lot/crouched
skipIt = qtrue;
}
else
{
onlyIfNotSpecial = qtrue;
}
}
if (!skipIt && (!onlyIfNotSpecial || !BG_SaberInSpecial(self->client->ps.saberMove)))
*/
{
gentity_t *te = NULL;
/*
qboolean pre = saberDoClashEffect;
VectorCopy( tr.endpos, saberClashPos );
VectorCopy( tr.plane.normal, saberClashNorm );
saberClashEventParm = 1;
saberDoClashEffect = qtrue;
WP_SaberDoClash( self, rSaberNum, rBladeNum );
saberDoClashEffect = pre;
*/
self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove);
self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
if (self->client->ps.torsoAnim == self->client->ps.legsAnim)
{ //set anim now on both parts
int anim = saberMoveData[self->client->ps.saberMove].animToUse;
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
}
//do bounce sound & force feedback
WP_SaberBounceSound( self, rSaberNum, rBladeNum );
//do hit effect
te = G_TempEntity( tr.endpos, EV_SABER_HIT );
te->s.otherEntityNum = ENTITYNUM_NONE;//we didn't hit anyone in particular
te->s.otherEntityNum2 = self->s.number;//send this so it knows who we are
te->s.weapon = rSaberNum;
te->s.legsAnim = rBladeNum;
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;
}
//do radius damage/knockback, if any
if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum ) )
{
WP_SaberRadiusDamage( self, tr.endpos, self->client->saber[rSaberNum].splashRadius, self->client->saber[rSaberNum].splashDamage, self->client->saber[rSaberNum].splashKnockback );
}
else
{
WP_SaberRadiusDamage( self, tr.endpos, self->client->saber[rSaberNum].splashRadius2, self->client->saber[rSaberNum].splashDamage2, self->client->saber[rSaberNum].splashKnockback2 );
}
return qtrue;
}
}
//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].takedamage &&
(g_entities[tr.entityNum].health > 0 || !(g_entities[tr.entityNum].s.eFlags & EF_DISINTEGRATION)) &&
tr.entityNum != self->s.number &&
g_entities[tr.entityNum].inuse)
{//hit something that had health and takes damage
if (idleDamage &&
g_entities[tr.entityNum].client &&
OnSameTeam(self, &g_entities[tr.entityNum]) &&
!g_friendlySaber.integer)
{
return qfalse;
}
if (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].client &&
self->client->ps.duelInProgress &&
self->client->ps.duelIndex != g_entities[tr.entityNum].s.number)
{
return qfalse;
}
if ( BG_StabDownAnim( self->client->ps.torsoAnim )
&& g_entities[tr.entityNum].client
&& !BG_InKnockDownOnGround( &g_entities[tr.entityNum].client->ps ) )
{//stabdowns only damage people who are actually on the ground...
return qfalse;
}
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
didHit = qtrue;
if ( !d_saberSPStyleDamage.integer//let's trying making blocks have to be blocked by a saber
&& g_entities[tr.entityNum].client
&& !unblockable
&& WP_SaberCanBlock(&g_entities[tr.entityNum], tr.endpos, 0, MOD_SABER, qfalse, attackStr))
{//hit a client who blocked the attack (fake: didn't actually hit their saber)
if (dmg <= SABER_NONATTACK_DAMAGE)
{
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
}
saberDoClashEffect = qtrue;
VectorCopy( tr.endpos, saberClashPos );
VectorCopy( tr.plane.normal, saberClashNorm );
saberClashEventParm = 1;
if (dmg > SABER_NONATTACK_DAMAGE)
{
int lockFactor = g_saberLockFactor.integer;
if ((g_entities[tr.entityNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] - self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) > 1 &&
Q_irand(1, 10) < lockFactor*2)
{ //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 ( (trMask&CONTENTS_LIGHTSABER)
&& 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 ((trMask&CONTENTS_LIGHTSABER)
&& 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
{//damage the thing we hit
qboolean doDismemberment = qfalse;
int knockbackFlags = 0;
if (g_entities[tr.entityNum].client)
{ //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 )//fd.forcePowerLevel[FP_SABER_OFFENSE])
{ //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 ( !d_saberSPStyleDamage.integer )
{
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 (g_gametype.integer != GT_SIEGE)
{ //unless siege..
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;
}
}
}
}
}
if (self->s.eType == ET_NPC &&
g_entities[tr.entityNum].client &&
self->client->playerTeam == g_entities[tr.entityNum].client->playerTeam)
{ //Oops. Since he's an NPC, we'll be forgiving and cut the damage down.
dmg *= 0.2f;
}
//store the damage, we'll apply it later
if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
&& !(self->client->saber[rSaberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
{
doDismemberment = qtrue;
}
if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
&& !(self->client->saber[rSaberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
{
doDismemberment = qtrue;
}
if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
&& self->client->saber[rSaberNum].knockbackScale > 0.0f )
{
if ( rSaberNum < 1 )
{
knockbackFlags = DAMAGE_SABER_KNOCKBACK1;
}
else
{
knockbackFlags = DAMAGE_SABER_KNOCKBACK2;
}
}
if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
&& self->client->saber[rSaberNum].knockbackScale > 0.0f )
{
if ( rSaberNum < 1 )
{
knockbackFlags = DAMAGE_SABER_KNOCKBACK1_B2;
}
else
{
knockbackFlags = DAMAGE_SABER_KNOCKBACK2_B2;
}
}
WP_SaberDamageAdd( tr.entityNum, dir, tr.endpos, dmg, doDismemberment, knockbackFlags );
if (g_entities[tr.entityNum].client)
{
//Let jedi AI know if it hit an enemy
if ( self->enemy && self->enemy == &g_entities[tr.entityNum] )
{
self->client->ps.saberEventFlags |= SEF_HITENEMY;
}
else
{
self->client->ps.saberEventFlags |= SEF_HITOBJECT;
}
}
if ( d_saberSPStyleDamage.integer )
{
}
else
{
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 &&
g_entities[tr.entityNum].inuse)
{ //saber clash
otherOwner = &g_entities[g_entities[tr.entityNum].r.ownerNum];
if (!otherOwner->inuse || !otherOwner->client)
{
return qfalse;
}
if ( otherOwner
&& otherOwner->client
&& otherOwner->client->ps.saberInFlight )
{//don't do extra collision checking vs sabers in air
}
else
{//hit an in-hand saber, do extra collision check against it
if ( d_saberSPStyleDamage.integer )
{//use SP-style blade-collision test
if ( !WP_SabersIntersect( self, rSaberNum, rBladeNum, otherOwner, qfalse ) )
{//sabers did not actually intersect
return qfalse;
}
}
else
{//MP-style
if (!G_SaberCollide(self, otherOwner, lastValidStart,
lastValidEnd, saberTrMins, saberTrMaxs, tr.endpos))
{ //detailed collision did not produce results...
return qfalse;
}
}
}
if (OnSameTeam(self, otherOwner) &&
!g_friendlySaber.integer)
{
return qfalse;
}
if ((self->s.eType == ET_NPC || otherOwner->s.eType == ET_NPC) && //just make sure one of us is an npc
self->client->playerTeam == otherOwner->client->playerTeam &&
g_gametype.integer != GT_SIEGE)
{ //don't hit your teammate's sabers if you are an NPC. It can be rather annoying.
return qfalse;
}
if (otherOwner->client->ps.duelInProgress &&
otherOwner->client->ps.duelIndex != self->s.number)
{
return qfalse;
}
if (self->client->ps.duelInProgress &&
self->client->ps.duelIndex != otherOwner->s.number)
{
return qfalse;
}
if ( g_debugSaberLocks.integer )
{
WP_SabersCheckLock2( self, otherOwner, LOCK_RANDOM );
return qtrue;
}
didHit = qtrue;
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
if (dmg <= SABER_NONATTACK_DAMAGE)
{
self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
}
saberDoClashEffect = qtrue;
VectorCopy( tr.endpos, saberClashPos );
VectorCopy( tr.plane.normal, saberClashNorm );
saberClashEventParm = 1;
sabersClashed = qtrue;
saberHitSaber = qtrue;
saberHitFraction = tr.fraction;
if (saberCheckKnockdown_Smashed(&g_entities[tr.entityNum], otherOwner, self, dmg))
{ //smashed it out of the air
return qfalse;
}
//is this my thrown saber?
if ( self->client->ps.saberEntityNum
&& self->client->ps.saberInFlight
&& rSaberNum == 0
&& saberCheckKnockdown_Smashed( &g_entities[self->client->ps.saberEntityNum], self, otherOwner, dmg))
{ //they smashed it out of the air
return qfalse;
}
blockStuff:
otherUnblockable = qfalse;
if (otherOwner && otherOwner->client && otherOwner->client->ps.saberInFlight)
{
return qfalse;
}
//this is a thrown saber, don't do any fancy saber-saber collision stuff
if ( self->client->ps.saberEntityNum
&& self->client->ps.saberInFlight
&& rSaberNum == 0 )
{
return qfalse;
}
otherSaberLevel = G_SaberAttackPower(otherOwner, SaberAttacking(otherOwner));
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 (BG_SaberInSpecial(otherOwner->client->ps.saberMove))
{
otherUnblockable = qtrue;
otherOwner->client->ps.saberBlocked = 0;
}
if ( sabersClashed &&
dmg > SABER_NONATTACK_DAMAGE &&
selfSaberLevel < FORCE_LEVEL_3 &&
!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 )
{
//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;
#ifndef FINAL_BUILD
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);
}
#endif
}
}
if ( ((selfSaberLevel < FORCE_LEVEL_3 && ((tryDeflectAgain && Q_irand(1, 10) <= 3) || (!tryDeflectAgain && Q_irand(1, 10) <= 7))) || (Q_irand(1, 10) <= 1 && otherSaberLevel >= FORCE_LEVEL_3))
&& !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)
&& (otherSaberLevel > FORCE_LEVEL_2 || ( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= 3 && Q_irand(0, otherSaberLevel) ))
&& !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 (self->client->ps.saberEntityNum) //make sure he has his saber still
{
saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner);
}
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;
#ifndef FINAL_BUILD
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);
}
#endif
didDefense = qtrue;
}
else if ((selfSaberLevel > 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_SABER_DEFENSE] < selfSaberLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == selfSaberLevel && (Q_irand(1, 10) >= otherSaberLevel*1.5 || 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 (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still
{
saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self);
}
#ifndef FINAL_BUILD
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i sent client %i into a broken parry\n", self->s.number, otherOwner->s.number);
}
#endif
otherOwner->client->ps.saberMove = BG_BrokenParryForParry( otherOwner->client->ps.saberMove );
otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
didDefense = qtrue;
}
else if ((selfSaberLevel > 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_SABER_DEFENSE] < selfSaberLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == selfSaberLevel && (Q_irand(1, 10) >= otherSaberLevel*3 || unblockable)) ) &&
otherSaberLevel >= 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.
#ifndef FINAL_BUILD
if (g_saberDebugPrint.integer)
{
Com_Printf("Client %i bounced off of client %i's saber\n", self->s.number, otherOwner->s.number);
}
#endif
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))
{
int attackAdv, defendStr = G_PowerLevelForSaberAnim( otherOwner, 0, qtrue ), attackBonus = 0;
if ( otherOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL
|| otherOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL
|| otherOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL )
{//parry/block/break-parry bonus for single-style kata moves
defendStr++;
}
defendStr += Q_irand(0, otherOwner->client->saber[0].parryBonus );
if ( otherOwner->client->saber[1].model
&& otherOwner->client->saber[1].model[0]
&& !otherOwner->client->ps.saberHolstered )
{
defendStr += Q_irand(0, otherOwner->client->saber[1].parryBonus );
}
#ifndef FINAL_BUILD
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);
}
#endif
attackBonus = Q_irand(0, self->client->saber[0].breakParryBonus );
if ( self->client->saber[1].model
&& self->client->saber[1].model[0]
&& !self->client->ps.saberHolstered )
{
attackBonus += Q_irand(0, self->client->saber[1].breakParryBonus );
}
attackAdv = (attackStr+attackBonus+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])-(defendStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]);
if ( attackAdv > 1 )
{//I won, he should knockaway
otherOwner->client->ps.saberMove = BG_BrokenParryForAttack( otherOwner->client->ps.saberMove );
otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
}
else if ( attackAdv > 0 )
{//I won, he should bounce, I should continue
otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
}
else if ( attackAdv < 1 )
{//I lost, I get knocked away
self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove );
self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
}
else if ( attackAdv < 0 )
{//I lost, I bounce off
self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
}
else
{//even, both bounce off
self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
}
didOffense = qtrue;
}
}
if (d_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);
otherOwner->client->ps.saberEventFlags |= SEF_PARRIED;
}
}
else if (!didDefense && dmg > SABER_NONATTACK_DAMAGE && !otherUnblockable) //if not more than idle damage, don't even bother blocking.
{ //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))
{
int otherIdleStr = otherOwner->client->ps.fd.saberAnimLevel;
if ( otherIdleStr == SS_DUAL
|| otherIdleStr == SS_STAFF )
{
otherIdleStr = SS_MEDIUM;
}
WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
otherOwner->client->ps.saberEventFlags |= SEF_PARRIED;
self->client->ps.saberEventFlags |= SEF_BLOCKED;
if ( attackStr+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > otherIdleStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] )
{
crushTheParry = qtrue;
}
else
{
tryDeflectAgain = qtrue;
}
}
else if (selfSaberLevel > otherSaberLevel ||
(selfSaberLevel == otherSaberLevel && 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 (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still
{
saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self);
}
#ifndef FINAL_BUILD
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);
}
#endif
}
else
{ //They are attacking, so are we, and obviously they have an attack level higher than or equal to ours
if (selfSaberLevel == otherSaberLevel)
{ //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)
{
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)
{
otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
}
#ifndef FINAL_BUILD
if (g_saberDebugPrint.integer)
{
Com_Printf("Equal attack level bounce/deflection for clients %i and %i\n", self->s.number, otherOwner->s.number);
}
#endif
self->client->ps.saberEventFlags |= SEF_DEFLECTED;
otherOwner->client->ps.saberEventFlags |= SEF_DEFLECTED;
}
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
//Using reflected anims instead now
self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove);
self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
if (self->client->ps.saberEntityNum) //make sure he has his saber still
{
saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner);
}
#ifndef FINAL_BUILD
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);
}
#endif
otherOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
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;
otherOwner->client->ps.saberEventFlags &= ~SEF_PARRIED;
self->client->ps.saberEventFlags &= ~SEF_BLOCKED;
#ifndef FINAL_BUILD
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);
}
#endif
}
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;
}
GAME_INLINE int VectorCompare2( const vec3_t v1, const vec3_t v2 ) {
if ( v1[0] > v2[0]+0.0001f || v1[0] < v2[0]-0.0001f
|| v1[1] > v2[1]+0.0001f || v1[1] < v2[1]-0.0001f
|| v1[2] > v2[2]+0.0001f || v1[2] < v2[2]-0.0001f ) {
return 0;
}
return 1;
}
#define MAX_SABER_SWING_INC 0.33f
void G_SPSaberDamageTraceLerped( gentity_t *self, int saberNum, int bladeNum, vec3_t baseNew, vec3_t endNew, int clipmask )
{
vec3_t baseOld, endOld;
vec3_t mp1, mp2;
vec3_t md1, md2;
if ( (level.time-self->client->saber[saberNum].blade[bladeNum].trail.lastTime) > 100 )
{//no valid last pos, use current
VectorCopy(baseNew, baseOld);
VectorCopy(endNew, endOld);
}
else
{//trace from last pos
VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.base, baseOld );
VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.tip, endOld );
}
VectorCopy( baseOld, mp1 );
VectorCopy( baseNew, mp2 );
VectorSubtract( endOld, baseOld, md1 );
VectorNormalize( md1 );
VectorSubtract( endNew, baseNew, md2 );
VectorNormalize( md2 );
saberHitWall = qfalse;
saberHitSaber = qfalse;
saberHitFraction = 1.0f;
if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
{//no diff
CheckSaberDamage( self, saberNum, bladeNum, baseNew, endNew, qfalse, clipmask, qfalse );
}
else
{//saber moved, lerp
float step = 8, stepsize = 8;//aveLength,
vec3_t ma1, ma2, md2ang, curBase1, curBase2;
int xx;
vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
float dirInc, curDirFrac;
vec3_t baseDiff, bladePointOld, bladePointNew;
qboolean extrapolate = qtrue;
//do the trace at the base first
VectorCopy( baseOld, bladePointOld );
VectorCopy( baseNew, bladePointNew );
CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, qtrue );
//if hit a saber, shorten rest of traces to match
if ( saberHitFraction < 1.0f )
{
//adjust muzzleDir...
vec3_t ma1, ma2;
vectoangles( md1, ma1 );
vectoangles( md2, ma2 );
for ( xx = 0; xx < 3; xx++ )
{
md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
}
AngleVectors( md2ang, md2, NULL, NULL );
//shorten the base pos
VectorSubtract( mp2, mp1, baseDiff );
VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
VectorMA( baseNew, self->client->saber[saberNum].blade[bladeNum].lengthMax, md2, endNew );
}
//If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
if ( BG_SaberInAttack( self->client->ps.saberMove )
|| BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
|| BG_SpinningSaberAnim( self->client->ps.torsoAnim )
|| BG_InSpecialJump( self->client->ps.torsoAnim ) )
//|| (g_timescale->value<1.0f&&BG_SaberInTransitionAny( ent->client->ps.saberMove )) )
{
curDirFrac = DotProduct( md1, md2 );
}
else
{
curDirFrac = 1.0f;
}
//NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
{//the saber blade spun more than 33 degrees since the last damage trace
curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
}
else
{
curDirFrac = 1.0f;
dirInc = 0.0f;
}
//qboolean hit_saber = qfalse;
vectoangles( md1, ma1 );
vectoangles( md2, ma2 );
//VectorSubtract( md2, md1, mdDiff );
VectorCopy( md1, curMD2 );
VectorCopy( baseOld, curBase2 );
while ( 1 )
{
VectorCopy( curMD2, curMD1 );
VectorCopy( curBase2, curBase1 );
if ( curDirFrac >= 1.0f )
{
VectorCopy( md2, curMD2 );
VectorCopy( baseNew, curBase2 );
}
else
{
for ( xx = 0; xx < 3; xx++ )
{
md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
}
AngleVectors( md2ang, curMD2, NULL, NULL );
//VectorMA( md1, curDirFrac, mdDiff, curMD2 );
VectorSubtract( baseNew, baseOld, baseDiff );
VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
}
// Move up the blade in intervals of stepsize
for ( step = stepsize; step <= self->client->saber[saberNum].blade[bladeNum].lengthMax /*&& step < self->client->saber[saberNum].blade[bladeNum].lengthOld*/; step += stepsize )
{
VectorMA( curBase1, step, curMD1, bladePointOld );
VectorMA( curBase2, step, curMD2, bladePointNew );
if ( step+stepsize >= self->client->saber[saberNum].blade[bladeNum].lengthMax )
{
extrapolate = qfalse;
}
//do the damage trace
CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, extrapolate );
/*
if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2,
qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
saberNum, bladeNum ) )
{
hit_wall = qtrue;
}
*/
//if hit a saber, shorten rest of traces to match
if ( saberHitFraction < 1.0f )
{
vec3_t curMA1, curMA2;
//adjust muzzle endpoint
VectorSubtract( mp2, mp1, baseDiff );
VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
VectorMA( baseNew, self->client->saber[saberNum].blade[bladeNum].lengthMax, curMD2, endNew );
//adjust muzzleDir...
vectoangles( curMD1, curMA1 );
vectoangles( curMD2, curMA2 );
for ( xx = 0; xx < 3; xx++ )
{
md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
}
AngleVectors( md2ang, curMD2, NULL, NULL );
saberHitSaber = qtrue;
}
if (saberHitWall)
{
break;
}
}
if ( saberHitWall || saberHitSaber )
{
break;
}
if ( curDirFrac >= 1.0f )
{
break;
}
else
{
curDirFrac += dirInc;
if ( curDirFrac >= 1.0f )
{
curDirFrac = 1.0f;
}
}
}
//do the trace at the end last
//Special check- adjust for length of blade not being a multiple of 12
/*
aveLength = (ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld + ent->client->ps.saber[saberNum].blade[bladeNum].length)/2;
if ( step > aveLength )
{//less dmg if the last interval was not stepsize
tipDmgMod = (stepsize-(step-aveLength))/stepsize;
}
//NOTE: since this is the tip, we do not extrapolate the extra 16
if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2,
qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
saberNum, bladeNum ) )
{
hit_wall = qtrue;
}
*/
}
}
#include "../namespace_begin.h"
qboolean BG_SaberInTransitionAny( int move );
#include "../namespace_end.h"
qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower );
qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV );
qboolean Jedi_WaitingAmbush( gentity_t *self );
void Jedi_Ambush( gentity_t *self );
evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist );
void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd )
{
float dist;
gentity_t *ent, *incoming = NULL;
int entityList[MAX_GENTITIES];
int numListedEntities;
vec3_t mins, maxs;
int i, e;
float closestDist, radius = 256;
vec3_t forward, dir, missile_dir, fwdangles = {0};
trace_t trace;
vec3_t traceTo, entDir;
float dot1, dot2;
float lookTDist = -1;
gentity_t *lookT = NULL;
qboolean doFullRoutine = qtrue;
//keep this updated even if we don't get below
if ( !(self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
{//lookTarget is set by and to the monster that's holding you, no other operations can change that
self->client->ps.hasLookTarget = qfalse;
}
if ( self->client->ps.weapon != WP_SABER && self->client->NPC_class != CLASS_BOBAFETT )
{
doFullRoutine = qfalse;
}
else if ( self->client->ps.saberInFlight )
{
doFullRoutine = qfalse;
}
else if ( self->client->ps.fd.forcePowersActive&(1<<FP_LIGHTNING) )
{//can't block while zapping
doFullRoutine = qfalse;
}
else if ( self->client->ps.fd.forcePowersActive&(1<<FP_DRAIN) )
{//can't block while draining
doFullRoutine = qfalse;
}
else if ( self->client->ps.fd.forcePowersActive&(1<<FP_PUSH) )
{//can't block while shoving
doFullRoutine = qfalse;
}
else if ( self->client->ps.fd.forcePowersActive&(1<<FP_GRIP) )
{//can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
doFullRoutine = qfalse;
}
if (self->client->ps.weaponTime > 0)
{ //don't autoblock while busy with stuff
return;
}
if ( (self->client->saber[0].saberFlags&SFL_NOT_ACTIVE_BLOCKING) )
{//can't actively block with this saber type
return;
}
if ( self->health <= 0 )
{//dead don't try to block (NOTE: actual deflection happens in missile code)
return;
}
if ( PM_InKnockDown( &self->client->ps ) )
{//can't block when knocked down
return;
}
if ( BG_SabersOff( &self->client->ps ) && self->client->NPC_class != CLASS_BOBAFETT )
{
if ( self->s.eType != ET_NPC )
{//player doesn't auto-activate
doFullRoutine = qfalse;
}
}
if ( self->s.eType == ET_PLAYER )
{//don't do this if already attacking!
if ( ucmd->buttons & BUTTON_ATTACK
|| BG_SaberInAttack( self->client->ps.saberMove )
|| BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
|| BG_SaberInTransitionAny( self->client->ps.saberMove ))
{
doFullRoutine = qfalse;
}
}
if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
{//can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
doFullRoutine = qfalse;
}
fwdangles[1] = self->client->ps.viewangles[1];
AngleVectors( fwdangles, forward, NULL, NULL );
for ( i = 0 ; i < 3 ; i++ )
{
mins[i] = self->r.currentOrigin[i] - radius;
maxs[i] = self->r.currentOrigin[i] + radius;
}
numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
closestDist = radius;
for ( e = 0 ; e < numListedEntities ; e++ )
{
ent = &g_entities[entityList[ e ]];
if (ent == self)
continue;
//as long as we're here I'm going to get a looktarget too, I guess. -rww
if (self->s.eType == ET_PLAYER &&
ent->client &&
(ent->s.eType == ET_NPC || ent->s.eType == ET_PLAYER) &&
!OnSameTeam(ent, self) &&
ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
!(ent->client->ps.pm_flags & PMF_FOLLOW) &&
(ent->s.eType != ET_NPC || ent->s.NPC_class != CLASS_VEHICLE) && //don't look at vehicle NPCs
ent->health > 0)
{ //seems like a valid enemy to look at.
vec3_t vecSub;
float vecLen;
VectorSubtract(self->client->ps.origin, ent->client->ps.origin, vecSub);
vecLen = VectorLength(vecSub);
if (lookTDist == -1 || vecLen < lookTDist)
{
trace_t tr;
vec3_t myEyes;
VectorCopy(self->client->ps.origin, myEyes);
myEyes[2] += self->client->ps.viewheight;
trap_Trace(&tr, myEyes, NULL, NULL, ent->client->ps.origin, self->s.number, MASK_PLAYERSOLID);
if (tr.fraction == 1.0f || tr.entityNum == ent->s.number)
{ //we have a clear line of sight to him, so it's all good.
lookT = ent;
lookTDist = vecLen;
}
}
}
if (!doFullRoutine)
{ //don't care about the rest then
continue;
}
if (ent->r.ownerNum == self->s.number)
continue;
if ( !(ent->inuse) )
continue;
if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
{//not a normal projectile
gentity_t *pOwner;
if (ent->r.ownerNum < 0 || ent->r.ownerNum >= ENTITYNUM_WORLD)
{ //not going to be a client then.
continue;
}
pOwner = &g_entities[ent->r.ownerNum];
if (!pOwner->inuse || !pOwner->client)
{
continue; //not valid cl owner
}
if (!pOwner->client->ps.saberEntityNum ||
!pOwner->client->ps.saberInFlight ||
pOwner->client->ps.saberEntityNum != ent->s.number)
{ //the saber is knocked away and/or not flying actively, or this ent is not the cl's saber ent at all
continue;
}
//If we get here then it's ok to be treated as a thrown saber, I guess.
}
else
{
if ( ent->s.pos.trType == TR_STATIONARY && self->s.eType == ET_PLAYER )
{//nothing you can do with a stationary missile if you're the player
continue;
}
}
//see if they're in front of me
VectorSubtract( ent->r.currentOrigin, self->r.currentOrigin, dir );
dist = VectorNormalize( dir );
//FIXME: handle detpacks, proximity mines and tripmines
if ( ent->s.weapon == WP_THERMAL )
{//thermal detonator!
if ( self->NPC && dist < ent->splashRadius )
{
if ( dist < ent->splashRadius &&
ent->nextthink < level.time + 600 &&
ent->count &&
self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
(ent->s.pos.trType == TR_STATIONARY||
ent->s.pos.trType == TR_INTERPOLATE||
(dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
!WP_ForcePowerUsable( self, FP_PUSH )) )
{//TD is close enough to hurt me, I'm on the ground and the thing is at rest or behind me and about to blow up, or I don't have force-push so force-jump!
//FIXME: sometimes this might make me just jump into it...?
self->client->ps.fd.forceJumpCharge = 480;
}
else if ( self->client->NPC_class != CLASS_BOBAFETT )
{//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
ForceThrow( self, qfalse );
}
}
continue;
}
else if ( ent->splashDamage && ent->splashRadius )
{//exploding missile
//FIXME: handle tripmines and detpacks somehow...
// maybe do a force-gesture that makes them explode?
// But what if we're within it's splashradius?
if ( self->s.eType == ET_PLAYER )
{//players don't auto-handle these at all
continue;
}
else
{
//if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK)
// && self->client->NPC_class != CLASS_BOBAFETT )
if (0) //Maybe handle this later?
{//a placed explosive like a tripmine or detpack
if ( InFOV3( ent->r.currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
{//in front of me
if ( G_ClearLOS4( self, ent ) )
{//can see it
vec3_t throwDir;
//make the gesture
ForceThrow( self, qfalse );
//take it off the wall and toss it
ent->s.pos.trType = TR_GRAVITY;
ent->s.eType = ET_MISSILE;
ent->s.eFlags &= ~EF_MISSILE_STICK;
ent->flags |= FL_BOUNCE_HALF;
AngleVectors( ent->r.currentAngles, throwDir, NULL, NULL );
VectorMA( ent->r.currentOrigin, ent->r.maxs[0]+4, throwDir, ent->r.currentOrigin );
VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
VectorScale( throwDir, 300, ent->s.pos.trDelta );
ent->s.pos.trDelta[2] += 150;
VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
ent->s.pos.trTime = level.time; // move a bit on the very first frame
VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
ent->r.ownerNum = self->s.number;
// make it explode, but with less damage
ent->splashDamage /= 3;
ent->splashRadius /= 3;
//ent->think = WP_Explode;
ent->nextthink = level.time + Q_irand( 500, 3000 );
}
}
}
else if ( dist < ent->splashRadius &&
self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
(DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE||
!WP_ForcePowerUsable( self, FP_PUSH )) )
{//NPCs try to evade it
self->client->ps.fd.forceJumpCharge = 480;
}
else if ( self->client->NPC_class != CLASS_BOBAFETT )
{//else, try to force-throw it away
//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
ForceThrow( self, qfalse );
}
}
//otherwise, can't block it, so we're screwed
continue;
}
if ( ent->s.weapon != WP_SABER )
{//only block shots coming from behind
if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
continue;
}
else if ( self->s.eType == ET_PLAYER )
{//player never auto-blocks thrown sabers
continue;
}//NPCs always try to block sabers coming from behind!
//see if they're heading towards me
VectorCopy( ent->s.pos.trDelta, missile_dir );
VectorNormalize( missile_dir );
if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
continue;
//FIXME: must have a clear trace to me, too...
if ( dist < closestDist )
{
VectorCopy( self->r.currentOrigin, traceTo );
traceTo[2] = self->r.absmax[2] - 4;
trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, traceTo, ent->s.number, ent->clipmask );
if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
{//okay, try one more check
VectorNormalize2( ent->s.pos.trDelta, entDir );
VectorMA( ent->r.currentOrigin, radius, entDir, traceTo );
trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, traceTo, ent->s.number, ent->clipmask );
if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
{//can't hit me, ignore it
continue;
}
}
if ( self->s.eType == ET_NPC )
{//An NPC
if ( self->NPC && !self->enemy && ent->r.ownerNum != ENTITYNUM_NONE )
{
gentity_t *owner = &g_entities[ent->r.ownerNum];
if ( owner->health >= 0 && (!owner->client || owner->client->playerTeam != self->client->playerTeam) )
{
G_SetEnemy( self, owner );
}
}
}
//FIXME: if NPC, predict the intersection between my current velocity/path and the missile's, see if it intersects my bounding box (+/-saberLength?), don't try to deflect unless it does?
closestDist = dist;
incoming = ent;
}
}
if (self->s.eType == ET_NPC && self->localAnimIndex <= 1)
{ //humanoid NPCs don't set angles based on server angles for looking, unlike other NPCs
if (self->client && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD)
{
lookT = &g_entities[self->client->renderInfo.lookTarget];
}
}
if (lookT)
{ //we got a looktarget at some point so we'll assign it then.
if ( !(self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
{//lookTarget is set by and to the monster that's holding you, no other operations can change that
self->client->ps.hasLookTarget = qtrue;
self->client->ps.lookTarget = lookT->s.number;
}
}
if (!doFullRoutine)
{ //then we're done now
return;
}
if ( incoming )
{
if ( self->NPC /*&& !G_ControlledByPlayer( self )*/ )
{
if ( Jedi_WaitingAmbush( self ) )
{
Jedi_Ambush( self );
}
if ( self->client->NPC_class == CLASS_BOBAFETT
&& (self->client->ps.eFlags2&EF2_FLYING)//moveType == MT_FLYSWIM
&& incoming->methodOfDeath != MOD_ROCKET_HOMING )
{//a hovering Boba Fett, not a tracking rocket
if ( !Q_irand( 0, 1 ) )
{//strafe
self->NPC->standTime = 0;
self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
}
if ( !Q_irand( 0, 1 ) )
{//go up/down
TIMER_Set( self, "heightChange", Q_irand( 1000, 3000 ) );
self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
}
}
else if ( Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming, 0.0f ) != EVASION_NONE )
{//make sure to turn on your saber if it's not on
if ( self->client->NPC_class != CLASS_BOBAFETT )
{
//self->client->ps.SaberActivate();
WP_ActivateSaber(self);
}
}
}
else//player
{
gentity_t *owner = &g_entities[incoming->r.ownerNum];
WP_SaberBlockNonRandom( self, incoming->r.currentOrigin, qtrue );
if ( owner && owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
{
self->enemy = owner;
//NPC_SetLookTarget( self, owner->s.number, level.time+1000 );
//player looktargetting done differently
}
}
}
}
#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);
static GAME_INLINE qboolean CheckThrownSaberDamaged(gentity_t *saberent, gentity_t *saberOwner, gentity_t *ent, int dist, int returning, qboolean noDCheck)
{
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 || ent->s.eType == ET_NPC))
{ //hit a client
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)
{ //within range
trace_t tr;
trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, ent->client->ps.origin, saberent->s.number, MASK_SHOT);
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, 999))
{ //they blocked it
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;
te->s.weapon = 0;//saberNum
te->s.legsAnim = 0;//bladeNum
if (saberCheckKnockdown_Thrown(saberent, saberOwner, &g_entities[tr.entityNum]))
{ //it was knocked out of the air
return qfalse;
}
if (!returning)
{ //return to owner if blocked
thrownSaberTouch(saberent, saberent, NULL);
}
saberOwner->client->ps.saberAttackWound = level.time + 500;
return qfalse;
}
else
{ //a good hit
vec3_t dir;
int dflags = 0;
VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
VectorNormalize(dir);
if (!dir[0] && !dir[1] && !dir[2])
{
dir[1] = 1;
}
if ( (saberOwner->client->saber[0].saberFlags2&SFL2_NO_DISMEMBERMENT) )
{
dflags |= DAMAGE_NO_DISMEMBER;
}
if ( saberOwner->client->saber[0].knockbackScale > 0.0f )
{
dflags |= DAMAGE_SABER_KNOCKBACK1;
}
if (saberOwner->client->ps.isJediMaster)
{ //2x damage for the Jedi Master
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage*2, dflags, MOD_SABER);
}
else
{
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage, dflags, MOD_SABER);
}
te = G_TempEntity( tr.endpos, EV_SABER_HIT );
te->s.otherEntityNum = ent->s.number;
te->s.otherEntityNum2 = saberOwner->s.number;
te->s.weapon = 0;//saberNum
te->s.legsAnim = 0;//bladeNum
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 && (noDCheck ||trap_InPVS(ent->r.currentOrigin, saberent->r.currentOrigin)))
{ //hit a non-client
if (noDCheck)
{
veclen = 0;
}
else
{
VectorSubtract(saberent->r.currentOrigin, ent->r.currentOrigin, vecsub);
veclen = VectorLength(vecsub);
}
if (veclen < dist)
{
trace_t tr;
vec3_t entOrigin;
if (ent->s.eType == ET_MOVER)
{
VectorSubtract( ent->r.absmax, ent->r.absmin, entOrigin );
VectorMA( ent->r.absmin, 0.5, entOrigin, entOrigin );
VectorAdd( ent->r.absmin, ent->r.absmax, entOrigin );
VectorScale( entOrigin, 0.5f, entOrigin );
}
else
{
VectorCopy(ent->r.currentOrigin, entOrigin);
}
trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, entOrigin, saberent->s.number, MASK_SHOT);
if (tr.fraction == 1 || tr.entityNum == ent->s.number)
{
vec3_t dir;
int dflags = 0;
VectorSubtract(tr.endpos, entOrigin, dir);
VectorNormalize(dir);
if ( (saberOwner->client->saber[0].saberFlags2&SFL2_NO_DISMEMBERMENT) )
{
dflags |= DAMAGE_NO_DISMEMBER;
}
if ( saberOwner->client->saber[0].knockbackScale > 0.0f )
{
dflags |= DAMAGE_SABER_KNOCKBACK1;
}
if (ent->s.eType == ET_NPC)
{ //an animent
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 40, dflags, MOD_SABER);
}
else
{
G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 5, dflags, MOD_SABER);
}
te = G_TempEntity( tr.endpos, EV_SABER_HIT );
te->s.otherEntityNum = ENTITYNUM_NONE; //don't do this for throw damage
//te->s.otherEntityNum = ent->s.number;
te->s.otherEntityNum2 = saberOwner->s.number;//actually, do send this, though - for the overridden per-saber hit effects/sounds
te->s.weapon = 0;//saberNum
te->s.legsAnim = 0;//bladeNum
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;
}
if ( ent->s.eType == ET_MOVER )
{
if ( saberOwner
&& saberOwner->client
&& (saberOwner->client->saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
{//don't do clash flare - NOTE: assumes same is true for both sabers if using dual sabers!
G_FreeEntity( te );//kind of a waste, but...
}
else
{
//I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing.
gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE );
VectorCopy(te->s.origin, teS->s.origin);
te->s.eventParm = 0;
}
}
else
{
te->s.eventParm = 1;
}
if (!returning)
{ //return to owner if blocked
thrownSaberTouch(saberent, saberent, NULL);
}
saberOwner->client->ps.saberAttackWound = level.time + 500;
}
}
}
return qtrue;
}
static GAME_INLINE 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" (and because the saber entity has no server g2 instance)
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 < level.num_entities)
{
ent = &g_entities[i];
CheckThrownSaberDamaged(saberent, saberOwner, ent, dist, returning, qfalse);
i++;
}
}
#define THROWN_SABER_COMP
static GAME_INLINE 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 && ent->s.pos.trType != TR_GRAVITY)
{ //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 && !(g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER))
{
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 );
//we'll skip the dist check, since we don't really care about that (we just hit it physically)
CheckThrownSaberDamaged(ent, &g_entities[ent->r.ownerNum], &g_entities[tr.entityNum], 256, 0, qtrue);
if (ent->s.pos.trType == TR_GRAVITY)
{ //got blocked and knocked away in the damage func
return;
}
tr.startsolid = 0;
if (tr.entityNum == ENTITYNUM_NONE)
{ //eh, this is a filthy lie. (obviously it had to hit something or it wouldn't be in here, so we'll say it hit the world)
tr.entityNum = ENTITYNUM_WORLD;
}
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;
gentity_t *owner = NULL;
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, -1.5f );
VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f );
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->flags = FL_BOUNCE_HALF;
if (ent->r.ownerNum >= 0 && ent->r.ownerNum < ENTITYNUM_WORLD)
{
owner = &g_entities[ent->r.ownerNum];
if (owner->inuse && owner->client &&
owner->client->saber[0].model[0])
{
WP_SaberAddG2Model( saberent, owner->client->saber[0].model, owner->client->saber[0].skin );
}
else
{
//WP_SaberAddG2Model( saberent, NULL, 0 );
//argh!!!!
G_FreeEntity(saberent);
return;
}
}
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);
}
#define MAX_LEAVE_TIME 20000
void saberReactivate(gentity_t *saberent, gentity_t *saberOwner);
void saberBackToOwner(gentity_t *saberent);
void DownedSaberThink(gentity_t *saberent)
{
gentity_t *saberOwn = NULL;
qboolean notDisowned = qfalse;
qboolean pullBack = qfalse;
saberent->nextthink = level.time;
if (saberent->r.ownerNum == ENTITYNUM_NONE)
{
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
saberOwn = &g_entities[saberent->r.ownerNum];
if (!saberOwn ||
!saberOwn->inuse ||
!saberOwn->client ||
saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR ||
(saberOwn->client->ps.pm_flags & PMF_FOLLOW))
{
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
if (saberOwn->client->ps.saberEntityNum)
{
if (saberOwn->client->ps.saberEntityNum == saberent->s.number)
{ //owner shouldn't have this set if we're thinking in here. Must've fallen off a cliff and instantly respawned or something.
notDisowned = qtrue;
}
else
{ //This should never happen, but just in case..
assert(!"ULTRA BAD THING");
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
}
if (notDisowned || saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
{ //He's dead, just go back to our normal saber status
saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex;
//MakeDeadSaber(saberent); //spawn a dead saber on top of where we are now. The "bodyqueue" method.
//Actually this will get taken care of when the thrown saber func sees we're dead.
#ifdef _DEBUG
if (saberOwn->client->saberStoredIndex != saberent->s.number)
{ //I'm paranoid.
assert(!"Bad saber index!!!");
}
#endif
saberReactivate(saberent, saberOwn);
if (saberOwn->health < 1)
{
saberOwn->client->ps.saberInFlight = qfalse;
MakeDeadSaber(saberent);
}
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->genericValue5 = 0;
saberent->nextthink = level.time;
saberent->r.svFlags |= (SVF_NOCLIENT);
//saberent->r.contents = CONTENTS_LIGHTSABER;
saberent->s.loopSound = 0;
saberent->s.loopIsSoundset = qfalse;
if (saberOwn->health > 0)
{ //only set this if he's alive. If dead we want to reflect the lack of saber on the corpse, as he died with his saber out.
saberOwn->client->ps.saberInFlight = qfalse;
WP_SaberRemoveG2Model( saberent );
}
saberOwn->client->ps.saberEntityState = 0;
saberOwn->client->ps.saberThrowDelay = level.time + 500;
saberOwn->client->ps.saberCanThrow = qfalse;
return;
}
if (saberOwn->client->saberKnockedTime < level.time && (saberOwn->client->pers.cmd.buttons & BUTTON_ATTACK))
{ //He wants us back
pullBack = qtrue;
}
else if ((level.time - saberOwn->client->saberKnockedTime) > MAX_LEAVE_TIME)
{ //Been sitting around for too long, go back no matter what he wants.
pullBack = qtrue;
}
if (pullBack)
{ //Get going back to the owner.
saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex;
#ifdef _DEBUG
if (saberOwn->client->saberStoredIndex != saberent->s.number)
{ //I'm paranoid.
assert(!"Bad saber index!!!");
}
#endif
saberReactivate(saberent, saberOwn);
saberent->touch = SaberGotHit;
saberent->think = saberBackToOwner;
saberent->speed = 0;
saberent->genericValue5 = 0;
saberent->nextthink = level.time;
saberent->r.contents = CONTENTS_LIGHTSABER;
G_Sound( saberOwn, CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
if (saberOwn->client->saber[0].soundOn)
{
G_Sound( saberent, CHAN_BODY, saberOwn->client->saber[0].soundOn );
}
if (saberOwn->client->saber[1].soundOn)
{
G_Sound( saberOwn, CHAN_BODY, saberOwn->client->saber[1].soundOn );
}
return;
}
G_RunObject(saberent);
saberent->nextthink = level.time;
}
void saberReactivate(gentity_t *saberent, gentity_t *saberOwner)
{
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->parent = saberOwner;
saberent->genericValue5 = 0;
SetSaberBoxSize(saberent);
saberent->touch = thrownSaberTouch;
saberent->s.weapon = WP_SABER;
saberOwner->client->ps.saberEntityState = 1;
trap_LinkEntity(saberent);
}
#define SABER_RETRIEVE_DELAY 3000 //3 seconds for now. This will leave you nice and open if you lose your saber.
void saberKnockDown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
{
saberOwner->client->ps.saberEntityNum = 0; //still stored in client->saberStoredIndex
saberOwner->client->saberKnockedTime = level.time + SABER_RETRIEVE_DELAY;
saberent->clipmask = MASK_SOLID;
saberent->r.contents = CONTENTS_TRIGGER;//0;
VectorSet( saberent->r.mins, -3.0f, -3.0f, -1.5f );
VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f );
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->flags |= FL_BOUNCE_HALF;
WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin );
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 = -5;//8;
saberMoveBack(saberent, qtrue);
saberent->s.pos.trType = TR_GRAVITY;
saberent->s.loopSound = 0; //kill this in case it was spinning.
saberent->s.loopIsSoundset = qfalse;
saberent->r.svFlags &= ~(SVF_NOCLIENT); //make sure the client is getting updates on where it is and such.
saberent->touch = SaberBounceSound;
saberent->think = DownedSaberThink;
saberent->nextthink = level.time;
if (saberOwner != other)
{ //if someone knocked it out of the air and it wasn't turned off, go in the direction they were facing.
if (other->inuse && other->client)
{
vec3_t otherFwd;
float deflectSpeed = 200;
AngleVectors(other->client->ps.viewangles, otherFwd, 0, 0);
saberent->s.pos.trDelta[0] = otherFwd[0]*deflectSpeed;
saberent->s.pos.trDelta[1] = otherFwd[1]*deflectSpeed;
saberent->s.pos.trDelta[2] = otherFwd[2]*deflectSpeed;
}
}
trap_LinkEntity(saberent);
if (saberOwner->client->saber[0].soundOff)
{
G_Sound( saberent, CHAN_BODY, saberOwner->client->saber[0].soundOff );
}
if (saberOwner->client->saber[1].soundOff &&
saberOwner->client->saber[1].model[0])
{
G_Sound( saberOwner, CHAN_BODY, saberOwner->client->saber[1].soundOff );
}
}
//sort of a silly macro I guess. But if I change anything in here I'll probably want it to be everywhere.
#define SABERINVALID (!saberent || !saberOwner || !other || !saberent->inuse || !saberOwner->inuse || !other->inuse || !saberOwner->client || !other->client || !saberOwner->client->ps.saberEntityNum || saberOwner->client->ps.saberLockTime > (level.time-100))
void WP_SaberRemoveG2Model( gentity_t *saberent )
{
if ( saberent->ghoul2 )
{
trap_G2API_RemoveGhoul2Models( &saberent->ghoul2 );
}
}
void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin )
{
WP_SaberRemoveG2Model( saberent );
if ( saberModel && saberModel[0] )
{
saberent->s.modelindex = G_ModelIndex(saberModel);
}
else
{
saberent->s.modelindex = G_ModelIndex( "models/weapons2/saber/saber_w.glm" );
}
//FIXME: use customSkin?
trap_G2API_InitGhoul2Model( &saberent->ghoul2, saberModel, saberent->s.modelindex, saberSkin, 0, 0, 0 );
}
//Make the saber go flying directly out of the owner's hand in the specified direction
qboolean saberKnockOutOfHand(gentity_t *saberent, gentity_t *saberOwner, vec3_t velocity)
{
if (!saberent || !saberOwner ||
!saberent->inuse || !saberOwner->inuse ||
!saberOwner->client)
{
return qfalse;
}
if (!saberOwner->client->ps.saberEntityNum)
{ //already gone
return qfalse;
}
if ((level.time - saberOwner->client->lastSaberStorageTime) > 50)
{ //must have a reasonably updated saber base pos
return qfalse;
}
if (saberOwner->client->ps.saberLockTime > (level.time-100))
{
return qfalse;
}
if ( (saberOwner->client->saber[0].saberFlags&SFL_NOT_DISARMABLE) )
{
return qfalse;
}
saberOwner->client->ps.saberInFlight = qtrue;
saberOwner->client->ps.saberEntityState = 1;
saberent->s.saberInFlight = qfalse;//qtrue;
saberent->s.pos.trType = TR_LINEAR;
saberent->s.eType = ET_GENERAL;
saberent->s.eFlags = 0;
WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin );
saberent->s.modelGhoul2 = 127;
saberent->parent = saberOwner;
saberent->damage = SABER_THROWN_HIT_DAMAGE;
saberent->methodOfDeath = MOD_SABER;
saberent->splashMethodOfDeath = MOD_SABER;
saberent->s.solid = 2;
saberent->r.contents = CONTENTS_LIGHTSABER;
saberent->genericValue5 = 0;
VectorSet( saberent->r.mins, -24.0f, -24.0f, -8.0f );
VectorSet( saberent->r.maxs, 24.0f, 24.0f, 8.0f );
saberent->s.genericenemyindex = saberOwner->s.number+1024;
saberent->s.weapon = WP_SABER;
saberent->genericValue5 = 0;
G_SetOrigin(saberent, saberOwner->client->lastSaberBase_Always); //use this as opposed to the right hand bolt,
//because I don't want to risk reconstructing the skel again to get it here. And it isn't worth storing.
saberKnockDown(saberent, saberOwner, saberOwner);
VectorCopy(velocity, saberent->s.pos.trDelta); //override the velocity on the knocked away saber.
return qtrue;
}
//Called at the result of a circle lock duel - the loser gets his saber tossed away and is put into a reflected attack anim
qboolean saberCheckKnockdown_DuelLoss(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
{
vec3_t dif;
float totalDistance = 1;
float distScale = 6.5f;
qboolean validMomentum = qtrue;
int disarmChance = 1;
if (SABERINVALID)
{
return qfalse;
}
VectorClear(dif);
if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200)
{ //see if the spots are valid
validMomentum = qfalse;
}
if (validMomentum)
{
//Get the difference
VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif);
totalDistance = VectorNormalize(dif);
if (!totalDistance)
{ //fine, try our own
if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200)
{
validMomentum = qfalse;
}
if (validMomentum)
{
VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif);
totalDistance = VectorNormalize(dif);
}
}
if (validMomentum)
{
if (!totalDistance)
{ //try the difference between the two blades
VectorSubtract(saberOwner->client->lastSaberBase_Always, other->client->lastSaberBase_Always, dif);
totalDistance = VectorNormalize(dif);
}
if (totalDistance)
{ //if we still have no difference somehow, just let it fall to the ground when the time comes.
if (totalDistance < 20)
{
totalDistance = 20;
}
VectorScale(dif, totalDistance*distScale, dif);
}
}
}
saberOwner->client->ps.saberMove = LS_V1_BL; //rwwFIXMEFIXME: Ideally check which lock it was exactly and use the proper anim (same goes for the attacker)
saberOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
if ( other && other->client )
{
disarmChance += other->client->saber[0].disarmBonus;
if ( other->client->saber[1].model
&& other->client->saber[1].model[0]
&& !other->client->ps.saberHolstered )
{
other->client->saber[1].disarmBonus;
}
}
if ( Q_irand( 0, disarmChance ) )
{
return saberKnockOutOfHand(saberent, saberOwner, dif);
}
else
{
return qfalse;
}
}
//Called when we want to try knocking the saber out of the owner's hand upon them going into a broken parry.
//Also called on reflected attacks.
qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
{
int myAttack;
int otherAttack;
qboolean doKnock = qfalse;
int disarmChance = 1;
if (SABERINVALID)
{
return qfalse;
}
//Neither gets an advantage based on attack state, when it comes to knocking
//saber out of hand.
myAttack = G_SaberAttackPower(saberOwner, qfalse);
otherAttack = G_SaberAttackPower(other, qfalse);
if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200)
{ //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it
return qfalse;
}
//only knock the saber out of the hand if they're in a stronger stance I suppose. Makes strong more advantageous.
if (otherAttack > myAttack+1 && Q_irand(1, 10) <= 7)
{ //This would be, say, strong stance against light stance.
doKnock = qtrue;
}
else if (otherAttack > myAttack && Q_irand(1, 10) <= 3)
{ //Strong vs. medium, medium vs. light
doKnock = qtrue;
}
if (doKnock)
{
vec3_t dif;
float totalDistance;
float distScale = 6.5f;
VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif);
totalDistance = VectorNormalize(dif);
if (!totalDistance)
{ //fine, try our own
if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200)
{ //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it
return qfalse;
}
VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif);
totalDistance = VectorNormalize(dif);
}
if (!totalDistance)
{ //...forget it then.
return qfalse;
}
if (totalDistance < 20)
{
totalDistance = 20;
}
VectorScale(dif, totalDistance*distScale, dif);
if ( other && other->client )
{
disarmChance += other->client->saber[0].disarmBonus;
if ( other->client->saber[1].model
&& other->client->saber[1].model[0]
&& !other->client->ps.saberHolstered )
{
other->client->saber[1].disarmBonus;
}
}
if ( Q_irand( 0, disarmChance ) )
{
return saberKnockOutOfHand(saberent, saberOwner, dif);
}
}
return qfalse;
}
#include "../namespace_begin.h"
qboolean BG_InExtraDefenseSaberMove( int move );
#include "../namespace_end.h"
//Called upon an enemy actually slashing into a thrown saber
qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage)
{
if (SABERINVALID)
{
return qfalse;
}
if (!saberOwner->client->ps.saberInFlight)
{ //can only do this if the saber is already actually in flight
return qfalse;
}
if ( other
&& other->inuse
&& other->client
&& BG_InExtraDefenseSaberMove( other->client->ps.saberMove ) )
{ //make sure the blow was strong enough
saberKnockDown(saberent, saberOwner, other);
return qtrue;
}
if (damage > 10)
{ //make sure the blow was strong enough
saberKnockDown(saberent, saberOwner, other);
return qtrue;
}
return qfalse;
}
//Called upon blocking a thrown saber. If the throw level compared to the blocker's defense level
//is inferior, or equal and a random factor is met, then the saber will be tossed to the ground.
qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
{
int throwLevel = 0;
int defenLevel = 0;
qboolean tossIt = qfalse;
if (SABERINVALID)
{
return qfalse;
}
defenLevel = other->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE];
throwLevel = saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW];
if (defenLevel > throwLevel)
{
tossIt = qtrue;
}
else if (defenLevel == throwLevel && Q_irand(1, 10) <= 4)
{
tossIt = qtrue;
}
//otherwise don't
if (tossIt)
{
saberKnockDown(saberent, saberOwner, other);
return qtrue;
}
return qfalse;
}
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 (!saberOwner->inuse ||
!saberOwner->client ||
saberOwner->client->sess.sessionTeam == TEAM_SPECTATOR)
{
MakeDeadSaber(saberent);
saberent->think = G_FreeEntity;
saberent->nextthink = level.time;
return;
}
if (saberOwner->health < 1 || !saberOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
{ //He's dead, just go back to our normal saber status
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->genericValue5 = 0;
saberent->nextthink = level.time;
if (saberOwner->client &&
saberOwner->client->saber[0].soundOff)
{
G_Sound(saberent, CHAN_AUTO, saberOwner->client->saber[0].soundOff);
}
MakeDeadSaber(saberent);
saberent->r.svFlags |= (SVF_NOCLIENT);
saberent->r.contents = CONTENTS_LIGHTSABER;
SetSaberBoxSize(saberent);
saberent->s.loopSound = 0;
saberent->s.loopIsSoundset = qfalse;
WP_SaberRemoveG2Model( saberent );
saberOwner->client->ps.saberInFlight = qfalse;
saberOwner->client->ps.saberEntityState = 0;
saberOwner->client->ps.saberThrowDelay = level.time + 500;
saberOwner->client->ps.saberCanThrow = qfalse;
return;
}
//make sure this is set alright
assert(saberOwner->client->ps.saberEntityNum == saberent->s.number ||
saberOwner->client->saberStoredIndex == saberent->s.number);
saberOwner->client->ps.saberEntityNum = saberent->s.number;
saberent->r.contents = CONTENTS_LIGHTSABER;
VectorSubtract(saberent->pos1, saberent->r.currentOrigin, dir);
ownerLen = VectorLength(dir);
if (saberent->speed < level.time)
{
float baseSpeed = 900;
VectorNormalize(dir);
saberMoveBack(saberent, qtrue);
VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
if (saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
{ //allow players with high saber throw rank to control the return speed of the saber
baseSpeed = 900;
saberent->speed = level.time;// + 200;
}
else
{
baseSpeed = 700;
saberent->speed = level.time + 50;
}
//Gradually slow down as it approaches, so it looks smoother coming into the hand.
if (ownerLen < 64)
{
VectorScale(dir, baseSpeed-200, saberent->s.pos.trDelta );
}
else if (ownerLen < 128)
{
VectorScale(dir, baseSpeed-150, saberent->s.pos.trDelta );
}
else if (ownerLen < 256)
{
VectorScale(dir, baseSpeed-100, saberent->s.pos.trDelta );
}
else
{
VectorScale(dir, baseSpeed, saberent->s.pos.trDelta );
}
saberent->s.pos.trTime = level.time;
}
/*
if (ownerLen <= 512)
{
saberent->s.saberInFlight = qfalse;
saberent->s.loopSound = saberHumSound;
saberent->s.loopIsSoundset = qfalse;
}
*/
//I'm just doing this now. I don't really like the spin on the way back. And it does weird stuff with the new saber-knocked-away code.
if (saberOwner->client->ps.saberEntityNum == saberent->s.number)
{
if ( !(saberOwner->client->saber[0].saberFlags&SFL_RETURN_DAMAGE)
|| saberOwner->client->ps.saberHolstered )
{
saberent->s.saberInFlight = qfalse;
}
saberent->s.loopSound = saberOwner->client->saber[0].soundLoop;
saberent->s.loopIsSoundset = qfalse;
if (ownerLen <= 32)
{
G_Sound( saberent, CHAN_AUTO, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
saberOwner->client->ps.saberInFlight = qfalse;
saberOwner->client->ps.saberEntityState = 0;
saberOwner->client->ps.saberCanThrow = qfalse;
saberOwner->client->ps.saberThrowDelay = level.time + 300;
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->genericValue5 = 0;
saberent->nextthink = level.time + 50;
WP_SaberRemoveG2Model( saberent );
return;
}
if (!saberent->s.saberInFlight)
{
saberCheckRadiusDamage(saberent, 1);
}
else
{
saberCheckRadiusDamage(saberent, 2);
}
saberMoveBack(saberent, qtrue);
}
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->think = saberBackToOwner;
saberent->nextthink = level.time;
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];
}
//we'll skip the dist check, since we don't really care about that (we just hit it physically)
CheckThrownSaberDamaged(saberent, &g_entities[saberent->r.ownerNum], hitEnt, 256, 0, qtrue);
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_SABER_OFFENSE])
{ //He's dead, just go back to our normal saber status
saberent->touch = SaberGotHit;
saberent->think = SaberUpdateSelf;
saberent->genericValue5 = 0;
saberent->nextthink = level.time;
if (saberOwn->client &&
saberOwn->client->saber[0].soundOff)
{
G_Sound(saberent, CHAN_AUTO, saberOwn->client->saber[0].soundOff);
}
MakeDeadSaber(saberent);
saberent->r.svFlags |= (SVF_NOCLIENT);
saberent->r.contents = CONTENTS_LIGHTSABER;
SetSaberBoxSize(saberent);
saberent->s.loopSound = 0;
saberent->s.loopIsSoundset = qfalse;
WP_SaberRemoveG2Model( saberent );
saberOwn->client->ps.saberInFlight = qfalse;
saberOwn->client->ps.saberEntityState = 0;
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);
}
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 UpdateClientRenderBolts(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles)
{
mdxaBone_t boltMatrix;
renderInfo_t *ri = &self->client->renderInfo;
if (!self->ghoul2)
{
VectorCopy(self->client->ps.origin, ri->headPoint);
VectorCopy(self->client->ps.origin, ri->handRPoint);
VectorCopy(self->client->ps.origin, ri->handLPoint);
VectorCopy(self->client->ps.origin, ri->torsoPoint);
VectorCopy(self->client->ps.origin, ri->crotchPoint);
VectorCopy(self->client->ps.origin, ri->footRPoint);
VectorCopy(self->client->ps.origin, ri->footLPoint);
}
else
{
//head
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->headPoint[0] = boltMatrix.matrix[0][3];
ri->headPoint[1] = boltMatrix.matrix[1][3];
ri->headPoint[2] = boltMatrix.matrix[2][3];
//right hand
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->handRPoint[0] = boltMatrix.matrix[0][3];
ri->handRPoint[1] = boltMatrix.matrix[1][3];
ri->handRPoint[2] = boltMatrix.matrix[2][3];
//left hand
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->handLPoint[0] = boltMatrix.matrix[0][3];
ri->handLPoint[1] = boltMatrix.matrix[1][3];
ri->handLPoint[2] = boltMatrix.matrix[2][3];
//chest
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->torsoPoint[0] = boltMatrix.matrix[0][3];
ri->torsoPoint[1] = boltMatrix.matrix[1][3];
ri->torsoPoint[2] = boltMatrix.matrix[2][3];
//crotch
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->crotchPoint[0] = boltMatrix.matrix[0][3];
ri->crotchPoint[1] = boltMatrix.matrix[1][3];
ri->crotchPoint[2] = boltMatrix.matrix[2][3];
//right foot
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->footRPoint[0] = boltMatrix.matrix[0][3];
ri->footRPoint[1] = boltMatrix.matrix[1][3];
ri->footRPoint[2] = boltMatrix.matrix[2][3];
//left foot
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->footLPoint[0] = boltMatrix.matrix[0][3];
ri->footLPoint[1] = boltMatrix.matrix[1][3];
ri->footLPoint[2] = boltMatrix.matrix[2][3];
}
self->client->renderInfo.boltValidityTime = level.time;
}
void UpdateClientRenderinfo(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles)
{
renderInfo_t *ri = &self->client->renderInfo;
if ( ri->mPCalcTime < level.time )
{
//We're just going to give rough estimates on most of this stuff,
//it's not like most of it matters.
#if 0 //#if 0'd since it's a waste setting all this to 0 each frame.
//Should you wish to make any of this valid then feel free to do so.
ri->headYawRangeLeft = ri->headYawRangeRight = ri->headPitchRangeUp = ri->headPitchRangeDown = 0;
ri->torsoYawRangeLeft = ri->torsoYawRangeRight = ri->torsoPitchRangeUp = ri->torsoPitchRangeDown = 0;
ri->torsoFpsMod = ri->legsFpsMod = 0;
VectorClear(ri->customRGB);
ri->customAlpha = 0;
ri->renderFlags = 0;
ri->lockYaw = 0;
VectorClear(ri->headAngles);
VectorClear(ri->torsoAngles);
//VectorClear(ri->eyeAngles);
ri->legsYaw = 0;
#endif
if (self->ghoul2 &&
self->ghoul2 != ri->lastG2)
{ //the g2 instance changed, so update all the bolts.
//rwwFIXMEFIXME: Base on skeleton used? Assuming humanoid currently.
ri->lastG2 = self->ghoul2;
if (self->localAnimIndex <= 1)
{
ri->headBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*head_eyes");
ri->handRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_hand");
ri->handLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_hand");
ri->torsoBolt = trap_G2API_AddBolt(self->ghoul2, 0, "thoracic");
ri->crotchBolt = trap_G2API_AddBolt(self->ghoul2, 0, "pelvis");
ri->footRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_leg_foot");
ri->footLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_leg_foot");
ri->motionBolt = trap_G2API_AddBolt(self->ghoul2, 0, "Motion");
}
else
{
ri->headBolt = -1;
ri->handRBolt = -1;
ri->handLBolt = -1;
ri->torsoBolt = -1;
ri->crotchBolt = -1;
ri->footRBolt = -1;
ri->footLBolt = -1;
ri->motionBolt = -1;
}
ri->lastG2 = self->ghoul2;
}
VectorCopy( self->client->ps.viewangles, self->client->renderInfo.eyeAngles );
//we'll just say the legs/torso are whatever the first frame of our current anim is.
ri->torsoFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].firstFrame;
ri->legsFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.legsAnim].firstFrame;
if (g_debugServerSkel.integer)
{ //Alright, I was doing this, but it's just too slow to do every frame.
//From now on if we want this data to be valid we're going to have to make a verify call for it before
//accessing it. I'm only doing this now if we want to debug the server skel by drawing lines from bolt
//positions every frame.
mdxaBone_t boltMatrix;
if (!self->ghoul2)
{
VectorCopy(self->client->ps.origin, ri->headPoint);
VectorCopy(self->client->ps.origin, ri->handRPoint);
VectorCopy(self->client->ps.origin, ri->handLPoint);
VectorCopy(self->client->ps.origin, ri->torsoPoint);
VectorCopy(self->client->ps.origin, ri->crotchPoint);
VectorCopy(self->client->ps.origin, ri->footRPoint);
VectorCopy(self->client->ps.origin, ri->footLPoint);
}
else
{
//head
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->headPoint[0] = boltMatrix.matrix[0][3];
ri->headPoint[1] = boltMatrix.matrix[1][3];
ri->headPoint[2] = boltMatrix.matrix[2][3];
//right hand
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->handRPoint[0] = boltMatrix.matrix[0][3];
ri->handRPoint[1] = boltMatrix.matrix[1][3];
ri->handRPoint[2] = boltMatrix.matrix[2][3];
//left hand
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->handLPoint[0] = boltMatrix.matrix[0][3];
ri->handLPoint[1] = boltMatrix.matrix[1][3];
ri->handLPoint[2] = boltMatrix.matrix[2][3];
//chest
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->torsoPoint[0] = boltMatrix.matrix[0][3];
ri->torsoPoint[1] = boltMatrix.matrix[1][3];
ri->torsoPoint[2] = boltMatrix.matrix[2][3];
//crotch
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->crotchPoint[0] = boltMatrix.matrix[0][3];
ri->crotchPoint[1] = boltMatrix.matrix[1][3];
ri->crotchPoint[2] = boltMatrix.matrix[2][3];
//right foot
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->footRPoint[0] = boltMatrix.matrix[0][3];
ri->footRPoint[1] = boltMatrix.matrix[1][3];
ri->footRPoint[2] = boltMatrix.matrix[2][3];
//left foot
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
ri->footLPoint[0] = boltMatrix.matrix[0][3];
ri->footLPoint[1] = boltMatrix.matrix[1][3];
ri->footLPoint[2] = boltMatrix.matrix[2][3];
}
//Now draw the skel for debug
G_TestLine(ri->headPoint, ri->torsoPoint, 0x000000ff, 50);
G_TestLine(ri->torsoPoint, ri->handRPoint, 0x000000ff, 50);
G_TestLine(ri->torsoPoint, ri->handLPoint, 0x000000ff, 50);
G_TestLine(ri->torsoPoint, ri->crotchPoint, 0x000000ff, 50);
G_TestLine(ri->crotchPoint, ri->footRPoint, 0x000000ff, 50);
G_TestLine(ri->crotchPoint, ri->footLPoint, 0x000000ff, 50);
}
//muzzle point calc (we are going to be cheap here)
VectorCopy(ri->muzzlePoint, ri->muzzlePointOld);
VectorCopy(self->client->ps.origin, ri->muzzlePoint);
VectorCopy(ri->muzzleDir, ri->muzzleDirOld);
AngleVectors(self->client->ps.viewangles, ri->muzzleDir, 0, 0);
ri->mPCalcTime = level.time;
VectorCopy(self->client->ps.origin, ri->eyePoint);
ri->eyePoint[2] += self->client->ps.viewheight;
}
}
#define STAFF_KICK_RANGE 16
extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ); //NPC_utils.c
extern qboolean BG_InKnockDown( int anim );
static qboolean G_KickDownable(gentity_t *ent)
{
if (!d_saberKickTweak.integer)
{
return qtrue;
}
if (!ent || !ent->inuse || !ent->client)
{
return qfalse;
}
if (BG_InKnockDown(ent->client->ps.legsAnim) ||
BG_InKnockDown(ent->client->ps.torsoAnim))
{
return qfalse;
}
if (ent->client->ps.weaponTime <= 0 &&
ent->client->ps.weapon == WP_SABER &&
ent->client->ps.groundEntityNum != ENTITYNUM_NONE)
{
return qfalse;
}
return qtrue;
}
static void G_TossTheMofo(gentity_t *ent, vec3_t tossDir, float tossStr)
{
if (!ent->inuse || !ent->client)
{ //no good
return;
}
if (ent->s.eType == ET_NPC && ent->s.NPC_class == CLASS_VEHICLE)
{ //no, silly
return;
}
VectorMA(ent->client->ps.velocity, tossStr, tossDir, ent->client->ps.velocity);
ent->client->ps.velocity[2] = 200;
if (ent->health > 0 && ent->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN &&
BG_KnockDownable(&ent->client->ps) &&
G_KickDownable(ent))
{ //if they are alive, knock them down I suppose
ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
ent->client->ps.forceHandExtendTime = level.time + 700;
ent->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim
//ent->client->ps.quickerGetup = qtrue;
}
}
static gentity_t *G_KickTrace( gentity_t *ent, vec3_t kickDir, float kickDist, vec3_t kickEnd, int kickDamage, float kickPush )
{
vec3_t traceOrg, traceEnd, kickMins, kickMaxs;
trace_t trace;
gentity_t *hitEnt = NULL;
VectorSet(kickMins, -2.0f, -2.0f, -2.0f);
VectorSet(kickMaxs, 2.0f, 2.0f, 2.0f);
//FIXME: variable kick height?
if ( kickEnd && !VectorCompare( kickEnd, vec3_origin ) )
{//they passed us the end point of the trace, just use that
//this makes the trace flat
VectorSet( traceOrg, ent->r.currentOrigin[0], ent->r.currentOrigin[1], kickEnd[2] );
VectorCopy( kickEnd, traceEnd );
}
else
{//extrude
VectorSet( traceOrg, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2]+ent->r.maxs[2]*0.5f );
VectorMA( traceOrg, kickDist, kickDir, traceEnd );
}
if (d_saberKickTweak.integer)
{
trap_G2Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
}
else
{
trap_Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT );
}
//G_TestLine(traceOrg, traceEnd, 0x0000ff, 5000);
if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid )
{
if (ent->client->jediKickTime > level.time)
{
if (trace.entityNum == ent->client->jediKickIndex)
{ //we are hitting the same ent we last hit in this same anim, don't hit it again
return NULL;
}
}
ent->client->jediKickIndex = trace.entityNum;
ent->client->jediKickTime = level.time + ent->client->ps.legsTimer;
hitEnt = &g_entities[trace.entityNum];
//FIXME: regardless of what we hit, do kick hit sound and impact effect
//G_PlayEffect( "misc/kickHit", trace.endpos, trace.plane.normal );
if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
{
G_Sound( ent, CHAN_AUTO, G_SoundIndex( "sound/movers/objects/saber_slam" ) );
}
else
{
G_Sound( ent, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
}
if ( hitEnt->inuse )
{//we hit an entity
//FIXME: don't hit same ent more than once per kick
if ( hitEnt->takedamage )
{//hurt it
if (hitEnt->client)
{
hitEnt->client->ps.otherKiller = ent->s.number;
hitEnt->client->ps.otherKillerDebounceTime = level.time + 10000;
hitEnt->client->ps.otherKillerTime = level.time + 10000;
hitEnt->client->otherKillerMOD = MOD_MELEE;
hitEnt->client->otherKillerVehWeapon = 0;
hitEnt->client->otherKillerWeaponType = WP_NONE;
}
if (d_saberKickTweak.integer)
{
G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage*0.2f, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
else
{
G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
}
}
if ( hitEnt->client
&& !(hitEnt->client->ps.pm_flags&PMF_TIME_KNOCKBACK) //not already flying through air? Intended to stop multiple hits, but...
&& G_CanBeEnemy(ent, hitEnt) )
{//FIXME: this should not always work
if ( hitEnt->health <= 0 )
{//we kicked a dead guy
//throw harder - FIXME: no matter how hard I push them, they don't go anywhere... corpses use less physics???
// G_Throw( hitEnt, kickDir, kickPush*4 );
//see if we should play a better looking death on them
// G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos );
G_TossTheMofo(hitEnt, kickDir, kickPush*4.0f);
}
else
{
/*
G_Throw( hitEnt, kickDir, kickPush );
if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) )
{
G_Knockdown( hitEnt, ent, kickDir, 300, qtrue );
}
else
{
G_Knockdown( hitEnt, ent, kickDir, kickPush, qtrue );
}
*/
if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) )
{
G_TossTheMofo(hitEnt, kickDir, 300.0f);
}
else
{
G_TossTheMofo(hitEnt, kickDir, kickPush);
}
}
}
}
}
return (hitEnt);
}
static void G_KickSomeMofos(gentity_t *ent)
{
vec3_t kickDir, kickEnd, fwdAngs;
float animLength = BG_AnimLength( ent->localAnimIndex, (animNumber_t)ent->client->ps.legsAnim );
float elapsedTime = (float)(animLength-ent->client->ps.legsTimer);
float remainingTime = (animLength-elapsedTime);
float kickDist = (ent->r.maxs[0]*1.5f)+STAFF_KICK_RANGE+8.0f;//fudge factor of 8
int kickDamage = Q_irand(10, 15);//Q_irand( 3, 8 ); //since it can only hit a guy once now
int kickPush = flrand( 50.0f, 100.0f );
qboolean doKick = qfalse;
renderInfo_t *ri = &ent->client->renderInfo;
VectorSet(kickDir, 0.0f, 0.0f, 0.0f);
VectorSet(kickEnd, 0.0f, 0.0f, 0.0f);
VectorSet(fwdAngs, 0.0f, ent->client->ps.viewangles[YAW], 0.0f);
//HMM... or maybe trace from origin to footRBolt/footLBolt? Which one? G2 trace? Will do hitLoc, if so...
if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
{
if ( elapsedTime >= 250 && remainingTime >= 250 )
{//front
doKick = qtrue;
if ( ri->handRBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->handRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->client->ps.origin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
}
else
{//guess
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
}
}
else
{
switch ( ent->client->ps.legsAnim )
{
case BOTH_GETUP_BROLL_B:
case BOTH_GETUP_BROLL_F:
case BOTH_GETUP_FROLL_B:
case BOTH_GETUP_FROLL_F:
if ( elapsedTime >= 250 && remainingTime >= 250 )
{//front
doKick = qtrue;
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->client->ps.origin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
}
else
{//guess
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
}
break;
case BOTH_A7_KICK_F_AIR:
case BOTH_A7_KICK_B_AIR:
case BOTH_A7_KICK_R_AIR:
case BOTH_A7_KICK_L_AIR:
if ( elapsedTime >= 100 && remainingTime >= 250 )
{//air
doKick = qtrue;
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
}
else
{//guess
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
}
break;
case BOTH_A7_KICK_F:
//FIXME: push forward?
if ( elapsedTime >= 250 && remainingTime >= 250 )
{//front
doKick = qtrue;
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
}
else
{//guess
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
}
break;
case BOTH_A7_KICK_B:
//FIXME: push back?
if ( elapsedTime >= 250 && remainingTime >= 250 )
{//back
doKick = qtrue;
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
}
else
{//guess
AngleVectors( fwdAngs, kickDir, NULL, NULL );
VectorScale( kickDir, -1, kickDir );
}
}
break;
case BOTH_A7_KICK_R:
//FIXME: push right?
if ( elapsedTime >= 250 && remainingTime >= 250 )
{//right
doKick = qtrue;
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
}
else
{//guess
AngleVectors( fwdAngs, NULL, kickDir, NULL );
}
}
break;
case BOTH_A7_KICK_L:
//FIXME: push left?
if ( elapsedTime >= 250 && remainingTime >= 250 )
{//left
doKick = qtrue;
if ( ri->footLBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
}
else
{//guess
AngleVectors( fwdAngs, NULL, kickDir, NULL );
VectorScale( kickDir, -1, kickDir );
}
}
break;
case BOTH_A7_KICK_S:
kickPush = flrand( 75.0f, 125.0f );
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
if ( elapsedTime >= 550
&& elapsedTime <= 1050 )
{
doKick = qtrue;
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
VectorMA( kickEnd, 8.0f, kickDir, kickEnd );
}
}
else
{//guess
if ( elapsedTime >= 400 && elapsedTime < 500 )
{//front
doKick = qtrue;
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
else if ( elapsedTime >= 500 && elapsedTime < 600 )
{//front-right?
doKick = qtrue;
fwdAngs[YAW] += 45;
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
else if ( elapsedTime >= 600 && elapsedTime < 700 )
{//right
doKick = qtrue;
AngleVectors( fwdAngs, NULL, kickDir, NULL );
}
else if ( elapsedTime >= 700 && elapsedTime < 800 )
{//back-right?
doKick = qtrue;
fwdAngs[YAW] += 45;
AngleVectors( fwdAngs, NULL, kickDir, NULL );
}
else if ( elapsedTime >= 800 && elapsedTime < 900 )
{//back
doKick = qtrue;
AngleVectors( fwdAngs, kickDir, NULL, NULL );
VectorScale( kickDir, -1, kickDir );
}
else if ( elapsedTime >= 900 && elapsedTime < 1000 )
{//back-left?
doKick = qtrue;
fwdAngs[YAW] += 45;
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
else if ( elapsedTime >= 1000 && elapsedTime < 1100 )
{//left
doKick = qtrue;
AngleVectors( fwdAngs, NULL, kickDir, NULL );
VectorScale( kickDir, -1, kickDir );
}
else if ( elapsedTime >= 1100 && elapsedTime < 1200 )
{//front-left?
doKick = qtrue;
fwdAngs[YAW] += 45;
AngleVectors( fwdAngs, NULL, kickDir, NULL );
VectorScale( kickDir, -1, kickDir );
}
}
break;
case BOTH_A7_KICK_BF:
kickPush = flrand( 75.0f, 125.0f );
kickDist += 20.0f;
if ( elapsedTime < 1500 )
{//auto-aim!
// overridAngles = PM_AdjustAnglesForBFKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles;
//FIXME: if we haven't done the back kick yet and there's no-one there to
// kick anymore, go into some anim that returns us to our base stance
}
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
if ( ( elapsedTime >= 750 && elapsedTime < 850 )
|| ( elapsedTime >= 1400 && elapsedTime < 1500 ) )
{//right, though either would do
doKick = qtrue;
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
VectorMA( kickEnd, 8, kickDir, kickEnd );
}
}
else
{//guess
if ( elapsedTime >= 250 && elapsedTime < 350 )
{//front
doKick = qtrue;
AngleVectors( fwdAngs, kickDir, NULL, NULL );
}
else if ( elapsedTime >= 350 && elapsedTime < 450 )
{//back
doKick = qtrue;
AngleVectors( fwdAngs, kickDir, NULL, NULL );
VectorScale( kickDir, -1, kickDir );
}
}
break;
case BOTH_A7_KICK_RL:
kickPush = flrand( 75.0f, 125.0f );
kickDist += 10.0f;
//ok, I'm tracing constantly on these things, they NEVER hit otherwise (in MP at least)
//FIXME: auto aim at enemies on the side of us?
//overridAngles = PM_AdjustAnglesForRLKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles;
//if ( elapsedTime >= 250 && elapsedTime < 350 )
if (level.framenum&1)
{//right
doKick = qtrue;
if ( ri->footRBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
VectorMA( kickEnd, 8, kickDir, kickEnd );
}
else
{//guess
AngleVectors( fwdAngs, NULL, kickDir, NULL );
}
}
//else if ( elapsedTime >= 350 && elapsedTime < 450 )
else
{//left
doKick = qtrue;
if ( ri->footLBolt != -1 )
{//actually trace to a bolt
G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 );
VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
kickDir[2] = 0;//ah, flatten it, I guess...
VectorNormalize( kickDir );
//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
VectorMA( kickEnd, 8, kickDir, kickEnd );
}
else
{//guess
AngleVectors( fwdAngs, NULL, kickDir, NULL );
VectorScale( kickDir, -1, kickDir );
}
}
break;
}
}
if ( doKick )
{
// G_KickTrace( ent, kickDir, kickDist, kickEnd, kickDamage, kickPush );
G_KickTrace( ent, kickDir, kickDist, NULL, kickDamage, kickPush );
}
}
static GAME_INLINE qboolean G_PrettyCloseIGuess(float a, float b, float tolerance)
{
if ((a-b) < tolerance &&
(a-b) > -tolerance)
{
return qtrue;
}
return qfalse;
}
static void G_GrabSomeMofos(gentity_t *self)
{
renderInfo_t *ri = &self->client->renderInfo;
mdxaBone_t boltMatrix;
vec3_t flatAng;
vec3_t pos;
vec3_t grabMins, grabMaxs;
trace_t trace;
if (!self->ghoul2 || ri->handRBolt == -1)
{ //no good
return;
}
VectorSet(flatAng, 0.0f, self->client->ps.viewangles[1], 0.0f);
trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, flatAng, self->client->ps.origin,
level.time, NULL, self->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pos);
VectorSet(grabMins, -4.0f, -4.0f, -4.0f);
VectorSet(grabMaxs, 4.0f, 4.0f, 4.0f);
//trace from my origin to my hand, if we hit anyone then get 'em
trap_G2Trace( &trace, self->client->ps.origin, grabMins, grabMaxs, pos, self->s.number, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
if (trace.fraction != 1.0f &&
trace.entityNum < ENTITYNUM_WORLD)
{
gentity_t *grabbed = &g_entities[trace.entityNum];
if (grabbed->inuse && (grabbed->s.eType == ET_PLAYER || grabbed->s.eType == ET_NPC) &&
grabbed->client && grabbed->health > 0 &&
G_CanBeEnemy(self, grabbed) &&
G_PrettyCloseIGuess(grabbed->client->ps.origin[2], self->client->ps.origin[2], 4.0f) &&
(!BG_InGrappleMove(grabbed->client->ps.torsoAnim) || grabbed->client->ps.torsoAnim == BOTH_KYLE_GRAB) &&
(!BG_InGrappleMove(grabbed->client->ps.legsAnim) || grabbed->client->ps.legsAnim == BOTH_KYLE_GRAB))
{ //grabbed an active player/npc
int tortureAnim = -1;
int correspondingAnim = -1;
if (self->client->pers.cmd.forwardmove > 0)
{ //punch grab
tortureAnim = BOTH_KYLE_PA_1;
correspondingAnim = BOTH_PLAYER_PA_1;
}
else if (self->client->pers.cmd.forwardmove < 0)
{ //knee-throw
tortureAnim = BOTH_KYLE_PA_2;
correspondingAnim = BOTH_PLAYER_PA_2;
}
if (tortureAnim == -1 || correspondingAnim == -1)
{
if (self->client->ps.torsoTimer < 300 && !self->client->grappleState)
{ //you failed to grab anyone, play the "failed to grab" anim
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
{ //providing the anim set succeeded..
self->client->ps.weaponTime = self->client->ps.torsoTimer;
}
}
return;
}
self->client->grappleIndex = grabbed->s.number;
self->client->grappleState = 1;
grabbed->client->grappleIndex = self->s.number;
grabbed->client->grappleState = 20;
//time to crack some heads
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, tortureAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
if (self->client->ps.torsoAnim == tortureAnim)
{ //providing the anim set succeeded..
self->client->ps.weaponTime = self->client->ps.torsoTimer;
}
G_SetAnim(grabbed, &grabbed->client->pers.cmd, SETANIM_BOTH, correspondingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
if (grabbed->client->ps.torsoAnim == correspondingAnim)
{ //providing the anim set succeeded..
if (grabbed->client->ps.weapon == WP_SABER)
{ //turn it off
if (!grabbed->client->ps.saberHolstered)
{
grabbed->client->ps.saberHolstered = 2;
if (grabbed->client->saber[0].soundOff)
{
G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[0].soundOff);
}
if (grabbed->client->saber[1].soundOff &&
grabbed->client->saber[1].model[0])
{
G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[1].soundOff);
}
}
}
if (grabbed->client->ps.torsoTimer < self->client->ps.torsoTimer)
{ //make sure they stay in the anim at least as long as the grabber
grabbed->client->ps.torsoTimer = self->client->ps.torsoTimer;
}
grabbed->client->ps.weaponTime = grabbed->client->ps.torsoTimer;
}
}
}
if (self->client->ps.torsoTimer < 300 && !self->client->grappleState)
{ //you failed to grab anyone, play the "failed to grab" anim
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
{ //providing the anim set succeeded..
self->client->ps.weaponTime = self->client->ps.torsoTimer;
}
}
}
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
//Note that this function also does the majority of working in maintaining the server g2 client instance (updating angles/anims/etc)
gentity_t *mySaber = NULL;
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 returnAfterUpdate = 0;
float animSpeedScale = 1.0f;
int saberNum;
qboolean clientOverride;
gentity_t *vehEnt = NULL;
int rSaberNum = 0;
int rBladeNum = 0;
#ifdef _DEBUG
if (g_disableServerG2.integer)
{
return;
}
#endif
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->inuse ||
!self->client ||
!self->ghoul2 ||
!g2SaberInstance)
{
return;
}
if (BG_KickingAnim(self->client->ps.legsAnim))
{ //do some kick traces and stuff if we're in the appropriate anim
G_KickSomeMofos(self);
}
else if (self->client->ps.torsoAnim == BOTH_KYLE_GRAB)
{ //try to grab someone
G_GrabSomeMofos(self);
}
else if (self->client->grappleState)
{
gentity_t *grappler = &g_entities[self->client->grappleIndex];
if (!grappler->inuse || !grappler->client || grappler->client->grappleIndex != self->s.number ||
!BG_InGrappleMove(grappler->client->ps.torsoAnim) || !BG_InGrappleMove(grappler->client->ps.legsAnim) ||
!BG_InGrappleMove(self->client->ps.torsoAnim) || !BG_InGrappleMove(self->client->ps.legsAnim) ||
!self->client->grappleState || !grappler->client->grappleState ||
grappler->health < 1 || self->health < 1 ||
!G_PrettyCloseIGuess(self->client->ps.origin[2], grappler->client->ps.origin[2], 4.0f))
{
self->client->grappleState = 0;
if ((BG_InGrappleMove(self->client->ps.torsoAnim) && self->client->ps.torsoTimer > 100) ||
(BG_InGrappleMove(self->client->ps.legsAnim) && self->client->ps.legsTimer > 100))
{ //if they're pretty far from finishing the anim then shove them into another anim
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
{ //providing the anim set succeeded..
self->client->ps.weaponTime = self->client->ps.torsoTimer;
}
}
}
else
{
vec3_t grapAng;
VectorSubtract(grappler->client->ps.origin, self->client->ps.origin, grapAng);
if (VectorLength(grapAng) > 64.0f)
{ //too far away, break it off
if ((BG_InGrappleMove(self->client->ps.torsoAnim) && self->client->ps.torsoTimer > 100) ||
(BG_InGrappleMove(self->client->ps.legsAnim) && self->client->ps.legsTimer > 100))
{
self->client->grappleState = 0;
G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
{ //providing the anim set succeeded..
self->client->ps.weaponTime = self->client->ps.torsoTimer;
}
}
}
else
{
vectoangles(grapAng, grapAng);
SetClientViewAngle(self, grapAng);
if (self->client->grappleState >= 20)
{ //grapplee
//try to position myself at the correct distance from my grappler
float idealDist;
vec3_t gFwd, idealSpot;
trace_t trace;
if (grappler->client->ps.torsoAnim == BOTH_KYLE_PA_1)
{ //grab punch
idealDist = 46.0f;
}
else
{ //knee-throw
idealDist = 34.0f;
}
AngleVectors(grappler->client->ps.viewangles, gFwd, 0, 0);
VectorMA(grappler->client->ps.origin, idealDist, gFwd, idealSpot);
trap_Trace(&trace, self->client->ps.origin, self->r.mins, self->r.maxs, idealSpot, self->s.number, self->clipmask);
if (!trace.startsolid && !trace.allsolid && trace.fraction == 1.0f)
{ //go there
G_SetOrigin(self, idealSpot);
VectorCopy(idealSpot, self->client->ps.origin);
}
}
else if (self->client->grappleState >= 1)
{ //grappler
if (grappler->client->ps.weapon == WP_SABER)
{ //make sure their saber is shut off
if (!grappler->client->ps.saberHolstered)
{
grappler->client->ps.saberHolstered = 2;
if (grappler->client->saber[0].soundOff)
{
G_Sound(grappler, CHAN_AUTO, grappler->client->saber[0].soundOff);
}
if (grappler->client->saber[1].soundOff &&
grappler->client->saber[1].model[0])
{
G_Sound(grappler, CHAN_AUTO, grappler->client->saber[1].soundOff);
}
}
}
//check for smashy events
if (self->client->ps.torsoAnim == BOTH_KYLE_PA_1)
{ //grab punch
if (self->client->grappleState == 1)
{ //smack
if (self->client->ps.torsoTimer < 3400)
{
int grapplerAnim = grappler->client->ps.torsoAnim;
int grapplerTime = grappler->client->ps.torsoTimer;
G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE);
//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
//it might try to put them into a pain anim or something, so override it back again
if (grappler->health > 0)
{
grappler->client->ps.torsoAnim = grapplerAnim;
grappler->client->ps.torsoTimer = grapplerTime;
grappler->client->ps.legsAnim = grapplerAnim;
grappler->client->ps.legsTimer = grapplerTime;
grappler->client->ps.weaponTime = grapplerTime;
}
self->client->grappleState++;
}
}
else if (self->client->grappleState == 2)
{ //smack!
if (self->client->ps.torsoTimer < 2550)
{
int grapplerAnim = grappler->client->ps.torsoAnim;
int grapplerTime = grappler->client->ps.torsoTimer;
G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE);
//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
//it might try to put them into a pain anim or something, so override it back again
if (grappler->health > 0)
{
grappler->client->ps.torsoAnim = grapplerAnim;
grappler->client->ps.torsoTimer = grapplerTime;
grappler->client->ps.legsAnim = grapplerAnim;
grappler->client->ps.legsTimer = grapplerTime;
grappler->client->ps.weaponTime = grapplerTime;
}
self->client->grappleState++;
}
}
else
{ //SMACK!
if (self->client->ps.torsoTimer < 1300)
{
vec3_t tossDir;
G_Damage(grappler, self, self, NULL, self->client->ps.origin, 30, 0, MOD_MELEE);
//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
self->client->grappleState = 0;
VectorSubtract(grappler->client->ps.origin, self->client->ps.origin, tossDir);
VectorNormalize(tossDir);
VectorScale(tossDir, 500.0f, tossDir);
tossDir[2] = 200.0f;
VectorAdd(grappler->client->ps.velocity, tossDir, grappler->client->ps.velocity);
if (grappler->health > 0)
{ //if still alive knock them down
grappler->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
grappler->client->ps.forceHandExtendTime = level.time + 1300;
}
}
}
}
else if (self->client->ps.torsoAnim == BOTH_KYLE_PA_2)
{ //knee throw
if (self->client->grappleState == 1)
{ //knee to the face
if (self->client->ps.torsoTimer < 3200)
{
int grapplerAnim = grappler->client->ps.torsoAnim;
int grapplerTime = grappler->client->ps.torsoTimer;
G_Damage(grappler, self, self, NULL, self->client->ps.origin, 20, 0, MOD_MELEE);
//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
//it might try to put them into a pain anim or something, so override it back again
if (grappler->health > 0)
{
grappler->client->ps.torsoAnim = grapplerAnim;
grappler->client->ps.torsoTimer = grapplerTime;
grappler->client->ps.legsAnim = grapplerAnim;
grappler->client->ps.legsTimer = grapplerTime;
grappler->client->ps.weaponTime = grapplerTime;
}
self->client->grappleState++;
}
}
else if (self->client->grappleState == 2)
{ //smashed on the ground
if (self->client->ps.torsoTimer < 2000)
{
//G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE);
//don't do damage on this one, it would look very freaky if they died
G_EntitySound( grappler, CHAN_VOICE, G_SoundIndex("*pain100.wav") );
//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
self->client->grappleState++;
}
}
else
{ //and another smash
if (self->client->ps.torsoTimer < 1000)
{
G_Damage(grappler, self, self, NULL, self->client->ps.origin, 30, 0, MOD_MELEE);
//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
//it might try to put them into a pain anim or something, so override it back again
if (grappler->health > 0)
{
grappler->client->ps.torsoTimer = 1000;
//G_SetAnim(grappler, &grappler->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP3, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
grappler->client->grappleState = 0;
}
else
{ //override death anim
grappler->client->ps.torsoAnim = BOTH_DEADFLOP1;
grappler->client->ps.legsAnim = BOTH_DEADFLOP1;
}
self->client->grappleState = 0;
}
}
}
else
{ //?
}
}
}
}
}
//If this is a listen server (client+server running on same machine),
//then lets try to steal the skeleton/etc data off the client instance
//for this entity to save us processing time.
clientOverride = trap_G2API_OverrideServer(self->ghoul2);
saberNum = self->client->ps.saberEntityNum;
if (!saberNum)
{
saberNum = self->client->saberStoredIndex;
}
if (!saberNum)
{
returnAfterUpdate = 1;
goto nextStep;
}
mySaber = &g_entities[saberNum];
if (self->health < 1)
{ //we don't want to waste precious CPU time calculating saber positions for corpses. But we want to avoid the saber ent position lagging on spawn, so..
//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)
{ //Since we haven't got a bolt position, place it on top of the player origin.
VectorCopy(self->client->ps.origin, mySaber->r.currentOrigin);
}
//I don't want to return now actually, I want to keep g2 instances for corpses up to
//date because I'm doing better corpse hit detection/dismem (particularly for the
//npc's)
//return;
}
if ( BG_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
{
self->client->ps.weaponstate = WEAPON_FIRING;
}
if (self->client->ps.weapon != WP_SABER ||
self->client->ps.weaponstate == WEAPON_RAISING ||
self->client->ps.weaponstate == WEAPON_DROPPING ||
self->health < 1)
{
if (!self->client->ps.saberInFlight)
{
returnAfterUpdate = 1;
}
}
if (self->client->ps.saberThrowDelay < level.time)
{
if ( (self->client->saber[0].saberFlags&SFL_NOT_THROWABLE) )
{//cant throw it normally!
if ( (self->client->saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE) )
{//but can throw it if only have 1 blade on
if ( self->client->saber[0].numBlades > 1
&& self->client->ps.saberHolstered == 1 )
{//have multiple blades and only one blade on
self->client->ps.saberCanThrow = qtrue;//qfalse;
//huh? want to be able to throw then right?
}
else
{//multiple blades on, can't throw
self->client->ps.saberCanThrow = qfalse;
}
}
else
{//never can throw it
self->client->ps.saberCanThrow = qfalse;
}
}
else
{//can throw it!
self->client->ps.saberCanThrow = qtrue;
}
}
nextStep:
if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE))
{
animSpeedScale = 2;
}
VectorCopy(self->client->ps.origin, properOrigin);
//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;
fVSpeed *= 1.6f/g_svfps.value;
//Cap it off at reasonable values so the saber box doesn't go flying ahead of us or
//something if we get a big speed boost from something.
if (fVSpeed > 70)
{
fVSpeed = 70;
}
if (fVSpeed < -70)
{
fVSpeed = -70;
}
properOrigin[0] += addVel[0]*fVSpeed;
properOrigin[1] += addVel[1]*fVSpeed;
properOrigin[2] += addVel[2]*fVSpeed;
properAngles[0] = 0;
if (self->s.number < MAX_CLIENTS && self->client->ps.m_iVehicleNum)
{
vehEnt = &g_entities[self->client->ps.m_iVehicleNum];
if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle)
{
properAngles[1] = vehEnt->m_pVehicle->m_vOrientation[YAW];
}
else
{
properAngles[1] = self->client->ps.viewangles[YAW];
vehEnt = NULL;
}
}
else
{
properAngles[1] = self->client->ps.viewangles[YAW];
}
properAngles[2] = 0;
AnglesToAxis( properAngles, legAxis );
UpdateClientRenderinfo(self, properOrigin, properAngles);
if (!clientOverride)
{ //if we get the client instance we don't need to do this
G_G2PlayerAngles( self, legAxis, properAngles );
}
if (vehEnt)
{
properAngles[1] = vehEnt->m_pVehicle->m_vOrientation[YAW];
}
if (returnAfterUpdate && saberNum)
{ //We don't even need to do GetBoltMatrix if we're only in here to keep the g2 server instance in sync
//but keep our saber entity in sync too, just copy it over our origin.
//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)
{ //Since we haven't got a bolt position, place it on top of the player origin.
VectorCopy(self->client->ps.origin, mySaber->r.currentOrigin);
}
goto finalUpdate;
}
if (returnAfterUpdate)
{
goto finalUpdate;
}
//We'll get data for blade 0 first no matter what it is and stick them into
//the constant ("_Always") values. Later we will handle going through each blade.
trap_G2API_GetBoltMatrix(self->ghoul2, 1, 0, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrigin);
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, boltAngles);
//immediately store these values so we don't have to recalculate this again
if (self->client->lastSaberStorageTime && (level.time - self->client->lastSaberStorageTime) < 200)
{ //alright
VectorCopy(self->client->lastSaberBase_Always, self->client->olderSaberBase);
self->client->olderIsValid = qtrue;
}
else
{
self->client->olderIsValid = qfalse;
}
VectorCopy(boltOrigin, self->client->lastSaberBase_Always);
VectorCopy(boltAngles, self->client->lastSaberDir_Always);
self->client->lastSaberStorageTime = level.time;
VectorCopy(boltAngles, rawAngles);
VectorMA( boltOrigin, self->client->saber[0].blade[0].lengthMax, boltAngles, end );
if (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, self->client->saber[0].blade[0].lengthMax, boltAngles, mySaber->r.currentOrigin );
}
}
boltAngles[YAW] = self->client->ps.viewangles[YAW];
/* {
static int lastDTime = 0;
if (lastDTime < level.time)
{
G_TestLine(boltOrigin, end, 0x0000ff, 200);
lastDTime = level.time + 200;
}
}
*/
if (self->client->ps.saberInFlight)
{ //do the thrown-saber stuff
gentity_t *saberent = &g_entities[saberNum];
if (saberent)
{
if (!self->client->ps.saberEntityState && self->client->ps.saberEntityNum)
{
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 (which doesn't actually affect where or how the saber hits)
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;
WP_SaberAddG2Model( saberent, self->client->saber[0].model, self->client->saber[0].skin );
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;
saberent->damage = SABER_THROWN_HIT_DAMAGE;
saberent->methodOfDeath = MOD_SABER;
saberent->splashMethodOfDeath = MOD_SABER;
saberent->s.solid = 2;
saberent->r.contents = CONTENTS_LIGHTSABER;
saberent->genericValue5 = 0;
VectorSet( saberent->r.mins, SABERMINS_X, SABERMINS_Y, SABERMINS_Z );
VectorSet( saberent->r.maxs, SABERMAXS_X, SABERMAXS_Y, SABERMAXS_Z );
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;
if ( self->client->saber[0].spinSound )
{
saberent->s.loopSound = self->client->saber[0].spinSound;
}
else
{
saberent->s.loopSound = saberSpinSound;
}
saberent->s.loopIsSoundset = qfalse;
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 if (self->client->ps.saberEntityNum) //only do this stuff if your saber is active and has not been knocked out of the air.
{
VectorCopy(boltOrigin, saberent->pos1);
trap_LinkEntity(saberent);
if (saberent->genericValue5 == PROPER_THROWN_VALUE)
{ //return to the owner now, this is a bad state to be in for here..
saberent->genericValue5 = 0;
saberent->think = SaberUpdateSelf;
saberent->nextthink = level.time;
WP_SaberRemoveG2Model( saberent );
self->client->ps.saberInFlight = qfalse;
self->client->ps.saberEntityState = 0;
self->client->ps.saberThrowDelay = level.time + 500;
self->client->ps.saberCanThrow = qfalse;
}
}
}
}
/*
if (self->client->ps.saberInFlight)
{ //if saber is thrown then only do the standard stuff for the left hand saber
rSaberNum = 1;
}
*/
if (!BG_SabersOff(&self->client->ps))
{
gentity_t *saberent = &g_entities[saberNum];
if (!self->client->ps.saberInFlight && saberent)
{
saberent->r.svFlags |= (SVF_NOCLIENT);
saberent->r.contents = CONTENTS_LIGHTSABER;
SetSaberBoxSize(saberent);
saberent->s.loopSound = 0;
saberent->s.loopIsSoundset = qfalse;
}
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[saberNum].r.currentOrigin, EV_SABER_BLOCK );
VectorSet( dir, 0, 1, 0 );
VectorCopy(g_entities[saberNum].r.currentOrigin, te->s.origin);
VectorCopy(dir, te->s.angles);
te->s.eventParm = 1;
te->s.weapon = 0;//saberNum
te->s.legsAnim = 0;//bladeNum
self->client->ps.saberIdleWound = level.time + Q_irand(400, 600);
}
while (rSaberNum < MAX_SABERS)
{
rBladeNum = 0;
while (rBladeNum < self->client->saber[rSaberNum].numBlades)
{ //Don't bother updating the bolt for each blade for this, it's just a very rough fallback method for during saberlocks
VectorCopy(boltOrigin, self->client->saber[saberNum].blade[rBladeNum].trail.base);
VectorCopy(end, self->client->saber[saberNum].blade[rBladeNum].trail.tip);
self->client->saber[saberNum].blade[rBladeNum].trail.lastTime = level.time;
rBladeNum++;
}
rSaberNum++;
}
self->client->hasCurrentPosition = qtrue;
self->client->ps.saberBlocked = BLOCKED_NONE;
goto finalUpdate;
}
//reset it in case we used it for cycling before
rSaberNum = rBladeNum = 0;
if (self->client->ps.saberInFlight)
{ //if saber is thrown then only do the standard stuff for the left hand saber
if (!self->client->ps.saberEntityNum)
{ //however, if saber is not in flight but rather knocked away, our left saber is off, and thus we may do nothing.
rSaberNum = 1;//was 2?
}
else
{//thrown saber still in flight, so do damage
rSaberNum = 0;//was 1?
}
}
WP_SaberClearDamage();
saberDoClashEffect = qfalse;
//Now cycle through each saber and each blade on the saber and do damage traces.
while (rSaberNum < MAX_SABERS)
{
if (!self->client->saber[rSaberNum].model[0])
{
rSaberNum++;
continue;
}
/*
if (rSaberNum == 0 && (self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)))
{ //don't do saber 0 is the right arm is broken
rSaberNum++;
continue;
}
*/
//for now I'm keeping a broken right arm swingable, it will just look and act damaged
//but still be useable
if (rSaberNum == 1 && (self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
{ //don't to saber 1 if the left arm is broken
break;
}
if (rSaberNum > 0
&& self->client->saber[1].model
&& self->client->saber[1].model[0]
&& self->client->ps.saberHolstered == 1 )
{ //don't to saber 2 if it's off
break;
}
rBladeNum = 0;
while (rBladeNum < self->client->saber[rSaberNum].numBlades)
{
//update muzzle data for the blade
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePointOld);
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDirOld);
if ( rBladeNum > 0 //more than one blade
&& (!self->client->saber[1].model||!self->client->saber[1].model[0])//not using dual blades
&& self->client->saber[rSaberNum].numBlades > 1//using a multi-bladed saber
&& self->client->ps.saberHolstered == 1 )//
{ //don't to extra blades if they're off
break;
}
//get the new data
//then update the bolt pos/dir. rBladeNum corresponds to the bolt index because blade bolts are added in order.
if ( rSaberNum == 0 && self->client->ps.saberInFlight )
{
if ( !self->client->ps.saberEntityNum )
{//dropped it... shouldn't get here, but...
//assert(0);
//FIXME: It's getting here a lot actually....
rSaberNum++;
rBladeNum = 0;
continue;
}
else
{
gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum];
vec3_t saberOrg, saberAngles;
if ( !saberEnt
|| !saberEnt->inuse
|| !saberEnt->ghoul2 )
{//wtf?
rSaberNum++;
rBladeNum = 0;
continue;
}
if ( saberent->s.saberInFlight )
{//spinning
BG_EvaluateTrajectory( &saberEnt->s.pos, level.time+50, saberOrg );
BG_EvaluateTrajectory( &saberEnt->s.apos, level.time+50, saberAngles );
}
else
{//coming right back
vec3_t saberDir;
BG_EvaluateTrajectory( &saberEnt->s.pos, level.time, saberOrg );
VectorSubtract( self->r.currentOrigin, saberOrg, saberDir );
vectoangles( saberDir, saberAngles );
}
trap_G2API_GetBoltMatrix(saberEnt->ghoul2, 0, rBladeNum, &boltMatrix, saberAngles, saberOrg, level.time, NULL, self->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint);
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir);
VectorCopy( self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, boltOrigin );
VectorMA( boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].lengthMax, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, end );
}
}
else
{
trap_G2API_GetBoltMatrix(self->ghoul2, rSaberNum+1, rBladeNum, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint);
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir);
VectorCopy( self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, boltOrigin );
VectorMA( boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].lengthMax, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, end );
}
self->client->saber[rSaberNum].blade[rBladeNum].storageTime = level.time;
if (self->client->hasCurrentPosition && d_saberInterpolate.integer)
{
if (self->client->ps.weaponTime <= 0)
{ //rww - 07/17/02 - don't bother doing the extra stuff unless actually attacking. This is in attempt to save CPU.
CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse);
}
else if (d_saberInterpolate.integer == 1)
{
int trMask = CONTENTS_LIGHTSABER|CONTENTS_BODY;
int sN = 0;
qboolean gotHit = qfalse;
qboolean clientUnlinked[MAX_CLIENTS];
qboolean skipSaberTrace = qfalse;
if (!g_saberTraceSaberFirst.integer)
{
skipSaberTrace = qtrue;
}
else if (g_saberTraceSaberFirst.integer >= 2 &&
g_gametype.integer != GT_DUEL &&
g_gametype.integer != GT_POWERDUEL &&
!self->client->ps.duelInProgress)
{ //if value is >= 2, and not in a duel, skip
skipSaberTrace = qtrue;
}
if (skipSaberTrace)
{ //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))
{ //Take this mask off before the saber trace, because we want to hit the saber first
g_entities[sN].r.contents &= ~CONTENTS_BODY;
clientUnlinked[sN] = qtrue;
}
else
{
clientUnlinked[sN] = qfalse;
}
sN++;
}
}
while (!gotHit)
{
if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, trMask, qfalse))
{
if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qtrue, trMask, qfalse))
{
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;
if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
{//no valid last pos, use current
VectorCopy(boltOrigin, oldSaberStart);
VectorCopy(end, oldSaberEnd);
}
else
{//trace from last pos
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, 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]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax;
saberMidEnd[1] = saberMidPoint[1] + saberMidDir[1]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax;
saberMidEnd[2] = saberMidPoint[2] + saberMidDir[2]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax;
//I'll just trace straight out and not even trace between positions to save speed.
if (CheckSaberDamage(self, rSaberNum, rBladeNum, saberMidPoint, saberMidEnd, qfalse, trMask, qfalse))
{
gotHit = qtrue;
}
}
}
else
{
gotHit = qtrue;
}
}
else
{
gotHit = qtrue;
}
if (g_saberTraceSaberFirst.integer)
{
sN = 0;
while (sN < MAX_CLIENTS)
{
if (clientUnlinked[sN])
{ //Make clients clip properly again.
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 (d_saberInterpolate.integer) //anything but 0 or 1, use the old plain method.
{
if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse))
{
CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qtrue, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse);
}
}
}
else if ( d_saberSPStyleDamage.integer )
{
G_SPSaberDamageTraceLerped( self, rSaberNum, rBladeNum, boltOrigin, end, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT) );
}
else
{
CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse);
}
VectorCopy(boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].trail.base);
VectorCopy(end, self->client->saber[rSaberNum].blade[rBladeNum].trail.tip);
self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime = level.time;
//VectorCopy(boltOrigin, self->client->lastSaberBase);
//VectorCopy(end, self->client->lastSaberTip);
self->client->hasCurrentPosition = qtrue;
//do hit effects
WP_SaberDoHit( self, rSaberNum, rBladeNum );
WP_SaberDoClash( self, rSaberNum, rBladeNum );
rBladeNum++;
}
rSaberNum++;
}
WP_SaberApplyDamage( self );
//NOTE: doing one call like this after the 2 loops above is a bit cheaper, tempentity-wise... but won't use the correct saber and blade numbers...
//now actually go through and apply all the damage we did
//WP_SaberDoHit( self, 0, 0 );
//WP_SaberDoClash( self, 0, 0 );
if (mySaber && mySaber->inuse)
{
trap_LinkEntity(mySaber);
}
if (!self->client->ps.saberInFlight)
{
self->client->ps.saberEntityState = 0;
}
}
finalUpdate:
if (clientOverride)
{ //if we get the client instance we don't even need to bother setting anims and stuff
return;
}
G_UpdateClientAnims(self, animSpeedScale);
}
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];
if ( zdiff > 0 )
{
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
}
if ( rightdot > 0.1 )
{
self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
}
else if ( rightdot < -0.1 )
{
self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
}
else
{
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 ( missileBlock )
{
self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
}
}
void WP_SaberBlock( gentity_t *playerent, vec3_t hitloc, qboolean missileBlock )
{
vec3_t diff, fwdangles={0,0,0}, right;
float rightdot;
float zdiff;
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 == 999)
{
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.saberEntityNum)
{ //saber is knocked away
return 0;
}
if (BG_SabersOff( &self->client->ps ))
{
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 now, the new broken parry stuff should handle it. This is how
//blocks were decided before the 1.03 patch (as you can see, it was STUPID.. for the most part)
/*
if (attackStr == FORCE_LEVEL_3)
{
if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= 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_SABER_DEFENSE] >= FORCE_LEVEL_3)
{
//do nothing for now
}
else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= 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_SABER_DEFENSE] &&
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
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_SABER_DEFENSE] == FORCE_LEVEL_3)
{
if (d_saberGhoul2Collision.integer)
{
blockFactor = 0.3f;
}
else
{
blockFactor = 0.05f;
}
}
else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == FORCE_LEVEL_2)
{
blockFactor = 0.6f;
}
else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == 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_DUEL || g_gametype.integer == GT_POWERDUEL)
{
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;
}