codemp/game/w_saber.c

Go to the documentation of this file.
00001 #include "g_local.h"
00002 #include "bg_local.h"
00003 #include "w_saber.h"
00004 #include "ai_main.h"
00005 #include "../ghoul2/G2.h"
00006 
00007 #define SABER_BOX_SIZE 16.0f
00008 extern bot_state_t *botstates[MAX_CLIENTS];
00009 extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold );
00010 extern void G_TestLine(vec3_t start, vec3_t end, int color, int time);
00011 
00012 extern vmCvar_t         g_saberRealisticCombat;
00013 extern vmCvar_t         d_saberSPStyleDamage;
00014 extern vmCvar_t         g_debugSaberLocks;
00015 // nmckenzie: SABER_DAMAGE_WALLS
00016 extern vmCvar_t         g_saberWallDamageScale;
00017 
00018 int saberSpinSound = 0;
00019 
00020 //would be cleaner if these were renamed to BG_ and proto'd in a header.
00021 #include "../namespace_begin.h"
00022 qboolean PM_SaberInTransition( int move );
00023 qboolean PM_SaberInDeflect( int move );
00024 qboolean PM_SaberInBrokenParry( int move );
00025 qboolean PM_SaberInBounce( int move );
00026 qboolean BG_SaberInReturn( int move );
00027 qboolean BG_InKnockDownOnGround( playerState_t *ps );
00028 qboolean BG_StabDownAnim( int anim );
00029 qboolean BG_SabersOff( playerState_t *ps );
00030 qboolean BG_SaberInTransitionAny( int move );
00031 qboolean BG_SaberInAttackPure( int move );
00032 qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum );
00033 qboolean WP_SaberBladeDoTransitionDamage( saberInfo_t *saber, int bladeNum );
00034 #include "../namespace_end.h"
00035 
00036 void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin );
00037 void WP_SaberRemoveG2Model( gentity_t *saberent );
00038 
00039 float RandFloat(float min, float max) {
00040         return ((rand() * (max - min)) / 32768.0F) + min;
00041 }
00042 
00043 #ifdef DEBUG_SABER_BOX
00044 void    G_DebugBoxLines(vec3_t mins, vec3_t maxs, int duration)
00045 {
00046         vec3_t start;
00047         vec3_t end;
00048 
00049         float x = maxs[0] - mins[0];
00050         float y = maxs[1] - mins[1];
00051 
00052         // top of box
00053         VectorCopy(maxs, start);
00054         VectorCopy(maxs, end);
00055         start[0] -= x;
00056         G_TestLine(start, end, 0x00000ff, duration);
00057         end[0] = start[0];
00058         end[1] -= y;
00059         G_TestLine(start, end, 0x00000ff, duration);
00060         start[1] = end[1];
00061         start[0] += x;
00062         G_TestLine(start, end, 0x00000ff, duration);
00063         G_TestLine(start, maxs, 0x00000ff, duration);
00064         // bottom of box
00065         VectorCopy(mins, start);
00066         VectorCopy(mins, end);
00067         start[0] += x;
00068         G_TestLine(start, end, 0x00000ff, duration);
00069         end[0] = start[0];
00070         end[1] += y;
00071         G_TestLine(start, end, 0x00000ff, duration);
00072         start[1] = end[1];
00073         start[0] -= x;
00074         G_TestLine(start, end, 0x00000ff, duration);
00075         G_TestLine(start, mins, 0x00000ff, duration);
00076 }
00077 #endif
00078 
00079 //general check for performing certain attacks against others
00080 qboolean G_CanBeEnemy(gentity_t *self, gentity_t *enemy)
00081 {
00082         if (!self->inuse || !enemy->inuse || !self->client || !enemy->client)
00083         {
00084                 return qfalse;
00085         }
00086 
00087         if (self->client->ps.duelInProgress && self->client->ps.duelIndex != enemy->s.number)
00088         { //dueling but not with this person
00089                 return qfalse;
00090         }
00091 
00092         if (enemy->client->ps.duelInProgress && enemy->client->ps.duelIndex != self->s.number)
00093         { //other guy dueling but not with me
00094                 return qfalse;
00095         }
00096 
00097         if (g_gametype.integer < GT_TEAM)
00098         { //ok, sure
00099                 return qtrue;
00100         }
00101 
00102         if (g_friendlyFire.integer)
00103         { //if ff on then can inflict damage normally on teammates
00104                 return qtrue;
00105         }
00106 
00107         if (OnSameTeam(self, enemy))
00108         { //ff not on, don't hurt teammates
00109                 return qfalse;
00110         }
00111 
00112         return qtrue;
00113 }
00114 
00115 //This function gets the attack power which is used to decide broken parries,
00116 //knockaways, and numerous other things. It is not directly related to the
00117 //actual amount of damage done, however. -rww
00118 static GAME_INLINE int G_SaberAttackPower(gentity_t *ent, qboolean attacking)
00119 {
00120         int baseLevel;
00121         assert(ent && ent->client);
00122 
00123         baseLevel = ent->client->ps.fd.saberAnimLevel;
00124 
00125         //Give "medium" strength for the two special stances.
00126         if (baseLevel == SS_DUAL)
00127         {
00128                 baseLevel = 2;
00129         }
00130         else if (baseLevel == SS_STAFF)
00131         {
00132                 baseLevel = 2;
00133         }
00134 
00135         if (attacking)
00136         { //the attacker gets a boost to help penetrate defense.
00137                 //General boost up so the individual levels make a bigger difference.
00138                 baseLevel *= 2;
00139 
00140                 baseLevel++;
00141 
00142                 //Get the "speed" of the swing, roughly, and add more power
00143                 //to the attack based on it.
00144                 if (ent->client->lastSaberStorageTime >= (level.time-50) &&
00145                         ent->client->olderIsValid)
00146                 {
00147                         vec3_t vSub;
00148                         int swingDist;
00149                         int toleranceAmt;
00150 
00151                         //We want different "tolerance" levels for adding in the distance of the last swing
00152                         //to the base power level depending on which stance we are using. Otherwise fast
00153                         //would have more advantage than it should since the animations are all much faster.
00154                         switch (ent->client->ps.fd.saberAnimLevel)
00155                         {
00156                         case SS_STRONG:
00157                                 toleranceAmt = 8;
00158                                 break;
00159                         case SS_MEDIUM:
00160                                 toleranceAmt = 16;
00161                                 break;
00162                         case SS_FAST:
00163                                 toleranceAmt = 24;
00164                                 break;
00165                         default: //dual, staff, etc.
00166                                 toleranceAmt = 16;
00167                                 break;
00168                         }
00169 
00170             VectorSubtract(ent->client->lastSaberBase_Always, ent->client->olderSaberBase, vSub);
00171                         swingDist = (int)VectorLength(vSub);
00172 
00173                         while (swingDist > 0)
00174                         { //I would like to do something more clever. But I suppose this works, at least for now.
00175                                 baseLevel++;
00176                                 swingDist -= toleranceAmt;
00177                         }
00178                 }
00179 
00180 #ifndef FINAL_BUILD
00181                 if (g_saberDebugPrint.integer > 1)
00182                 {
00183                         Com_Printf("Client %i: ATT STR: %i\n", ent->s.number, baseLevel);
00184                 }
00185 #endif
00186         }
00187 
00188         if ((ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) ||
00189                 (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
00190         { //We're very weak when one of our arms is broken
00191                 baseLevel *= 0.3;
00192         }
00193 
00194         //Cap at reasonable values now.
00195         if (baseLevel < 1)
00196         {
00197                 baseLevel = 1;
00198         }
00199         else if (baseLevel > 16)
00200         {
00201                 baseLevel = 16;
00202         }
00203 
00204         if (g_gametype.integer == GT_POWERDUEL &&
00205                 ent->client->sess.duelTeam == DUELTEAM_LONE)
00206         { //get more power then
00207                 return baseLevel*2;
00208         }
00209         else if (attacking && g_gametype.integer == GT_SIEGE)
00210         { //in siege, saber battles should be quicker and more biased toward the attacker
00211                 return baseLevel*3;
00212         }
00213         
00214         return baseLevel;
00215 }
00216 
00217 void WP_DeactivateSaber( gentity_t *self, qboolean clearLength )
00218 {
00219         if ( !self || !self->client )
00220         {
00221                 return;
00222         }
00223         //keep my saber off!
00224         if ( !self->client->ps.saberHolstered )
00225         {
00226                 self->client->ps.saberHolstered = 2;
00227                 /*
00228                 if ( clearLength )
00229                 {
00230                         self->client->ps.SetSaberLength( 0 );
00231                 }
00232                 */
00233                 //Doens't matter ATM
00234                 if (self->client->saber[0].soundOff)
00235                 {
00236                         G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOff);
00237                 }
00238 
00239                 if (self->client->saber[1].soundOff &&
00240                         self->client->saber[1].model[0])
00241                 {
00242                         G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOff);
00243                 }
00244 
00245         }
00246 }
00247 
00248 void WP_ActivateSaber( gentity_t *self )
00249 {
00250         if ( !self || !self->client )
00251         {
00252                 return;
00253         }
00254 
00255         if (self->NPC &&
00256                 self->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT &&
00257                 (self->client->ps.forceHandExtendTime - level.time) > 200)
00258         { //if we're an NPC and in the middle of a taunt then stop it
00259                 self->client->ps.forceHandExtend = HANDEXTEND_NONE;
00260                 self->client->ps.forceHandExtendTime = 0;
00261         }
00262         else if (self->client->ps.fd.forceGripCripple)
00263         { //can't activate saber while being gripped
00264                 return;
00265         }
00266 
00267         if ( self->client->ps.saberHolstered )
00268         {
00269                 self->client->ps.saberHolstered = 0;
00270                 if (self->client->saber[0].soundOn)
00271                 {
00272                         G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOn);
00273                 }
00274 
00275                 if (self->client->saber[1].soundOn)
00276                 {
00277                         G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOn);
00278                 }
00279         }
00280 }
00281 
00282 #define PROPER_THROWN_VALUE 999 //Ah, well.. 
00283 
00284 void SaberUpdateSelf(gentity_t *ent)
00285 {
00286         if (ent->r.ownerNum == ENTITYNUM_NONE)
00287         {
00288                 ent->think = G_FreeEntity;
00289                 ent->nextthink = level.time;
00290                 return;
00291         }
00292 
00293         if (!g_entities[ent->r.ownerNum].inuse ||
00294                 !g_entities[ent->r.ownerNum].client/* ||
00295                 g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR*/)
00296         {
00297                 ent->think = G_FreeEntity;
00298                 ent->nextthink = level.time;
00299                 return;
00300         }
00301 
00302         if (g_entities[ent->r.ownerNum].client->ps.saberInFlight && g_entities[ent->r.ownerNum].health > 0)
00303         { //let The Master take care of us now (we'll get treated like a missile until we return)
00304                 ent->nextthink = level.time;
00305                 ent->genericValue5 = PROPER_THROWN_VALUE;
00306                 return;
00307         }
00308 
00309         ent->genericValue5 = 0;
00310 
00311         if (g_entities[ent->r.ownerNum].client->ps.weapon != WP_SABER ||
00312                 (g_entities[ent->r.ownerNum].client->ps.pm_flags & PMF_FOLLOW) ||
00313                 //RWW ADDED 7-19-03 BEGIN
00314                 g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR ||
00315                 g_entities[ent->r.ownerNum].client->tempSpectate >= level.time ||
00316                 //RWW ADDED 7-19-03 END
00317                 g_entities[ent->r.ownerNum].health < 1 ||
00318                 BG_SabersOff( &g_entities[ent->r.ownerNum].client->ps ) ||
00319                 (!g_entities[ent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] && g_entities[ent->r.ownerNum].s.eType != ET_NPC))
00320         { //owner is not using saber, spectating, dead, saber holstered, or has no attack level
00321                 ent->r.contents = 0;
00322                 ent->clipmask = 0;
00323         }
00324         else
00325         { //Standard contents (saber is active)
00326 #ifdef DEBUG_SABER_BOX
00327                 if (g_saberDebugBox.integer == 1|| g_saberDebugBox.integer == 4)
00328                 {
00329                         vec3_t dbgMins;
00330                         vec3_t dbgMaxs;
00331 
00332                         VectorAdd( ent->r.currentOrigin, ent->r.mins, dbgMins );
00333                         VectorAdd( ent->r.currentOrigin, ent->r.maxs, dbgMaxs );
00334 
00335                         G_DebugBoxLines(dbgMins, dbgMaxs, (10.0f/(float)g_svfps.integer)*100);
00336                 }
00337 #endif
00338                 if (ent->r.contents != CONTENTS_LIGHTSABER)
00339                 {
00340                         if ((level.time - g_entities[ent->r.ownerNum].client->lastSaberStorageTime) <= 200)
00341                         { //Only go back to solid once we're sure our owner has updated recently
00342                                 ent->r.contents = CONTENTS_LIGHTSABER;
00343                                 ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
00344                         }
00345                 }
00346                 else
00347                 {
00348                         ent->r.contents = CONTENTS_LIGHTSABER;
00349                         ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
00350                 }
00351         }
00352 
00353         trap_LinkEntity(ent);
00354 
00355         ent->nextthink = level.time;
00356 }
00357 
00358 void SaberGotHit( gentity_t *self, gentity_t *other, trace_t *trace )
00359 {
00360         gentity_t *own = &g_entities[self->r.ownerNum];
00361 
00362         if (!own || !own->client)
00363         {
00364                 return;
00365         }
00366 
00367         //Do something here..? Was handling projectiles here, but instead they're now handled in their own functions.
00368 }
00369 
00370 #include "../namespace_begin.h"
00371 qboolean BG_SuperBreakLoseAnim( int anim );
00372 #include "../namespace_end.h"
00373 
00374 static GAME_INLINE void SetSaberBoxSize(gentity_t *saberent)
00375 {
00376         gentity_t *owner = NULL;
00377         vec3_t saberOrg, saberTip;
00378         int i;
00379         int j = 0;
00380         int k = 0;
00381         qboolean dualSabers = qfalse;
00382         qboolean alwaysBlock[MAX_SABERS][MAX_BLADES];
00383         qboolean forceBlock = qfalse;
00384 
00385         assert(saberent && saberent->inuse);
00386 
00387         if (saberent->r.ownerNum < MAX_CLIENTS && saberent->r.ownerNum >= 0)
00388         {
00389                 owner = &g_entities[saberent->r.ownerNum];
00390         }
00391         else if (saberent->r.ownerNum >= 0 && saberent->r.ownerNum < ENTITYNUM_WORLD &&
00392                 g_entities[saberent->r.ownerNum].s.eType == ET_NPC)
00393         {
00394                 owner = &g_entities[saberent->r.ownerNum];
00395         }
00396 
00397         if (!owner || !owner->inuse || !owner->client)
00398         {
00399                 assert(!"Saber with no owner?");
00400                 return;
00401         }
00402 
00403         if ( owner->client->saber[1].model
00404                 && owner->client->saber[1].model[0] )
00405         {
00406                 dualSabers = qtrue;
00407         }
00408 
00409         if ( PM_SaberInBrokenParry(owner->client->ps.saberMove)
00410                 || BG_SuperBreakLoseAnim( owner->client->ps.torsoAnim ) )
00411         { //let swings go right through when we're in this state
00412                 for ( i = 0; i < MAX_SABERS; i++ )
00413                 {
00414                         if ( i > 0 && !dualSabers )
00415                         {//not using a second saber, set it to not blocking
00416                                 for ( j = 0; j < MAX_BLADES; j++ )
00417                                 {
00418                                         alwaysBlock[i][j] = qfalse;
00419                                 }
00420                         }
00421                         else
00422                         {
00423                                 if ( (owner->client->saber[i].saberFlags2&SFL2_ALWAYS_BLOCK) )
00424                                 {
00425                                         for ( j = 0; j < owner->client->saber[i].numBlades; j++ )
00426                                         {
00427                                                 alwaysBlock[i][j] = qtrue;
00428                                                 forceBlock = qtrue;
00429                                         }
00430                                 }
00431                                 if ( owner->client->saber[i].bladeStyle2Start > 0 )
00432                                 {
00433                                         for ( j = owner->client->saber[i].bladeStyle2Start; j < owner->client->saber[i].numBlades; j++ )
00434                                         {
00435                                                 if ( (owner->client->saber[i].saberFlags2&SFL2_ALWAYS_BLOCK2) )
00436                                                 {
00437                                                         alwaysBlock[i][j] = qtrue;
00438                                                         forceBlock = qtrue;
00439                                                 }
00440                                                 else
00441                                                 {
00442                                                         alwaysBlock[i][j] = qfalse;
00443                                                 }
00444                                         }
00445                                 }
00446                         }
00447                 }
00448                 if ( !forceBlock )
00449                 {//no sabers/blades to FORCE to be on, so turn off blocking altogether
00450                         VectorSet( saberent->r.mins, 0, 0, 0 );
00451                         VectorSet( saberent->r.maxs, 0, 0, 0 );
00452 #ifndef FINAL_BUILD
00453                         if (g_saberDebugPrint.integer > 1)
00454                         {
00455                                 Com_Printf("Client %i in broken parry, saber box 0\n", owner->s.number);
00456                         }
00457 #endif
00458                         return;
00459                 }
00460         }
00461 
00462         if ((level.time - owner->client->lastSaberStorageTime) > 200 ||
00463                 (level.time - owner->client->saber[j].blade[k].storageTime) > 100)
00464         { //it's been too long since we got a reliable point storage, so use the defaults and leave.
00465                 VectorSet( saberent->r.mins, -SABER_BOX_SIZE, -SABER_BOX_SIZE, -SABER_BOX_SIZE );
00466                 VectorSet( saberent->r.maxs, SABER_BOX_SIZE, SABER_BOX_SIZE, SABER_BOX_SIZE );
00467                 return;
00468         }
00469 
00470         if ( dualSabers
00471                 || owner->client->saber[0].numBlades > 1 )
00472         {//dual sabers or multi-blade saber
00473                 if ( owner->client->ps.saberHolstered > 1 )
00474                 {//entirely off
00475                         //no blocking at all
00476                         VectorSet( saberent->r.mins, 0, 0, 0 );
00477                         VectorSet( saberent->r.maxs, 0, 0, 0 );
00478                         return;
00479                 }
00480         }
00481         else
00482         {//single saber
00483                 if ( owner->client->ps.saberHolstered )
00484                 {//off
00485                         //no blocking at all
00486                         VectorSet( saberent->r.mins, 0, 0, 0 );
00487                         VectorSet( saberent->r.maxs, 0, 0, 0 );
00488                         return;
00489                 }
00490         }
00491         //Start out at the saber origin, then go through all the blades and push out the extents
00492         //for each blade, then set the box relative to the origin.
00493         VectorCopy(saberent->r.currentOrigin, saberent->r.mins);
00494         VectorCopy(saberent->r.currentOrigin, saberent->r.maxs);
00495 
00496         for (i = 0; i < 3; i++)
00497         {
00498                 for (j = 0; j < MAX_SABERS; j++)
00499                 {
00500                         if (!owner->client->saber[j].model[0])
00501                         {
00502                                 break;
00503                         }
00504                         if ( dualSabers
00505                                 && owner->client->ps.saberHolstered == 1 
00506                                 && j == 1 )
00507                         { //this mother is holstered, get outta here.
00508                                 j++;
00509                                 continue;
00510                         }
00511                         for (k = 0; k < owner->client->saber[j].numBlades; k++)
00512                         {
00513                                 if ( k > 0 )
00514                                 {//not the first blade
00515                                         if ( !dualSabers )
00516                                         {//using a single saber
00517                                                 if ( owner->client->saber[j].numBlades > 1 )
00518                                                 {//with multiple blades
00519                                                         if( owner->client->ps.saberHolstered == 1 )
00520                                                         {//all blades after the first one are off
00521                                                                 break;
00522                                                         }
00523                                                 }
00524                                         }
00525                                 }
00526                                 if ( forceBlock )
00527                                 {//only do blocking with blades that are marked to block
00528                                         if ( !alwaysBlock[j][k] )
00529                                         {//this blade shouldn't be blocking
00530                                                 continue;
00531                                         }
00532                                 }
00533                                 //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);
00534                                 VectorCopy(owner->client->saber[j].blade[k].muzzlePoint, saberOrg);
00535                                 VectorMA(owner->client->saber[j].blade[k].muzzlePoint, owner->client->saber[j].blade[k].lengthMax, owner->client->saber[j].blade[k].muzzleDir, saberTip);
00536 
00537                                 if (saberOrg[i] < saberent->r.mins[i])
00538                                 {
00539                                         saberent->r.mins[i] = saberOrg[i];
00540                                 }
00541                                 if (saberTip[i] < saberent->r.mins[i])
00542                                 {
00543                                         saberent->r.mins[i] = saberTip[i];
00544                                 }
00545 
00546                                 if (saberOrg[i] > saberent->r.maxs[i])
00547                                 {
00548                                         saberent->r.maxs[i] = saberOrg[i];
00549                                 }
00550                                 if (saberTip[i] > saberent->r.maxs[i])
00551                                 {
00552                                         saberent->r.maxs[i] = saberTip[i];
00553                                 }
00554 
00555                                 //G_TestLine(saberOrg, saberTip, 0x0000ff, 50);
00556                         }
00557                 }
00558         }
00559 
00560         VectorSubtract(saberent->r.mins, saberent->r.currentOrigin, saberent->r.mins);
00561         VectorSubtract(saberent->r.maxs, saberent->r.currentOrigin, saberent->r.maxs);
00562 }
00563 
00564 void WP_SaberInitBladeData( gentity_t *ent )
00565 {
00566         gentity_t *saberent = NULL;
00567         gentity_t *checkEnt;
00568         int i = 0;
00569 
00570         while (i < level.num_entities)
00571         { //make sure there are no other saber entities floating around that think they belong to this client.
00572                 checkEnt = &g_entities[i];
00573 
00574                 if (checkEnt->inuse && checkEnt->neverFree &&
00575                         checkEnt->r.ownerNum == ent->s.number &&
00576                         checkEnt->classname && checkEnt->classname[0] &&
00577                         !Q_stricmp(checkEnt->classname, "lightsaber"))
00578                 {
00579                         if (saberent)
00580                         { //already have one
00581                                 checkEnt->neverFree = qfalse;
00582                                 checkEnt->think = G_FreeEntity;
00583                                 checkEnt->nextthink = level.time;
00584                         }
00585                         else
00586                         { //hmm.. well then, take it as my own.
00587                                 //free the bitch but don't issue a kg2 to avoid overflowing clients.
00588                                 checkEnt->s.modelGhoul2 = 0;
00589                                 G_FreeEntity(checkEnt);
00590 
00591                                 //now init it manually and reuse this ent slot.
00592                                 G_InitGentity(checkEnt);
00593                                 saberent = checkEnt;
00594                         }
00595                 }
00596 
00597                 i++;
00598         }
00599 
00600         //We do not want the client to have any real knowledge of the entity whatsoever. It will only
00601         //ever be used on the server.
00602         if (!saberent)
00603         { //ok, make one then
00604                 saberent = G_Spawn();
00605         }
00606         ent->client->ps.saberEntityNum = ent->client->saberStoredIndex = saberent->s.number;
00607         saberent->classname = "lightsaber";
00608         
00609         saberent->neverFree = qtrue; //the saber being removed would be a terrible thing.
00610 
00611         saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
00612         saberent->r.ownerNum = ent->s.number;
00613 
00614         saberent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
00615         saberent->r.contents = CONTENTS_LIGHTSABER;
00616 
00617         SetSaberBoxSize(saberent);
00618 
00619         saberent->mass = 10;
00620 
00621         saberent->s.eFlags |= EF_NODRAW;
00622         saberent->r.svFlags |= SVF_NOCLIENT;
00623 
00624         saberent->s.modelGhoul2 = 1;
00625         //should we happen to be removed (we belong to an NPC and he is removed) then
00626         //we want to attempt to remove our g2 instance on the client in case we had one.
00627 
00628         saberent->touch = SaberGotHit;
00629 
00630         saberent->think = SaberUpdateSelf;
00631         saberent->genericValue5 = 0;
00632         saberent->nextthink = level.time + 50;
00633 
00634         saberSpinSound = G_SoundIndex("sound/weapons/saber/saberspin.wav");
00635 }
00636 
00637 #define LOOK_DEFAULT_SPEED      0.15f
00638 #define LOOK_TALKING_SPEED      0.15f   
00639 
00640 static GAME_INLINE qboolean G_CheckLookTarget( gentity_t *ent, vec3_t   lookAngles, float *lookingSpeed )
00641 {
00642         //FIXME: also clamp the lookAngles based on the clamp + the existing difference between
00643         //              headAngles and torsoAngles?  But often the tag_torso is straight but the torso itself
00644         //              is deformed to not face straight... sigh...
00645 
00646         if (ent->s.eType == ET_NPC &&
00647                 ent->s.m_iVehicleNum &&
00648                 ent->s.NPC_class != CLASS_VEHICLE )
00649         { //an NPC bolted to a vehicle should just look around randomly
00650                 if ( TIMER_Done( ent, "lookAround" ) )
00651                 {
00652                         ent->NPC->shootAngles[YAW] = flrand(0,360);
00653                         TIMER_Set( ent, "lookAround", Q_irand( 500, 3000 ) );
00654                 }
00655                 VectorSet( lookAngles, 0, ent->NPC->shootAngles[YAW], 0 );
00656                 return qtrue;
00657         }
00658         //Now calc head angle to lookTarget, if any
00659         if ( ent->client->renderInfo.lookTarget >= 0 && ent->client->renderInfo.lookTarget < ENTITYNUM_WORLD )
00660         {
00661                 vec3_t  lookDir, lookOrg, eyeOrg;
00662                 int i;
00663 
00664                 if ( ent->client->renderInfo.lookMode == LM_ENT )
00665                 {
00666                         gentity_t       *lookCent = &g_entities[ent->client->renderInfo.lookTarget];
00667                         if ( lookCent )
00668                         {
00669                                 if ( lookCent != ent->enemy )
00670                                 {//We turn heads faster than headbob speed, but not as fast as if watching an enemy
00671                                         *lookingSpeed = LOOK_DEFAULT_SPEED;
00672                                 }
00673 
00674                                 //FIXME: Ignore small deltas from current angles so we don't bob our head in synch with theirs?
00675 
00676                                 /*
00677                                 if ( ent->client->renderInfo.lookTarget == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
00678                                 {//Special case- use cg.refdef.vieworg if looking at player and not in third person view
00679                                         VectorCopy( cg.refdef.vieworg, lookOrg );
00680                                 }
00681                                 */ //No no no!
00682                                 if ( lookCent->client )
00683                                 {
00684                                         VectorCopy( lookCent->client->renderInfo.eyePoint, lookOrg );
00685                                 }
00686                                 else if ( lookCent->inuse && !VectorCompare( lookCent->r.currentOrigin, vec3_origin ) )
00687                                 {
00688                                         VectorCopy( lookCent->r.currentOrigin, lookOrg );
00689                                 }
00690                                 else
00691                                 {//at origin of world
00692                                         return qfalse;
00693                                 }
00694                                 //Look in dir of lookTarget
00695                         }
00696                 }
00697                 else if ( ent->client->renderInfo.lookMode == LM_INTEREST && ent->client->renderInfo.lookTarget > -1 && ent->client->renderInfo.lookTarget < MAX_INTEREST_POINTS )
00698                 {
00699                         VectorCopy( level.interestPoints[ent->client->renderInfo.lookTarget].origin, lookOrg );
00700                 }
00701                 else
00702                 {
00703                         return qfalse;
00704                 }
00705 
00706                 VectorCopy( ent->client->renderInfo.eyePoint, eyeOrg );
00707 
00708                 VectorSubtract( lookOrg, eyeOrg, lookDir );
00709 
00710                 vectoangles( lookDir, lookAngles );
00711 
00712                 for ( i = 0; i < 3; i++ )
00713                 {
00714                         lookAngles[i] = AngleNormalize180( lookAngles[i] );
00715                         ent->client->renderInfo.eyeAngles[i] = AngleNormalize180( ent->client->renderInfo.eyeAngles[i] );
00716                 }
00717                 AnglesSubtract( lookAngles, ent->client->renderInfo.eyeAngles, lookAngles );
00718                 return qtrue;
00719         }
00720 
00721         return qfalse;
00722 }
00723 
00724 //rww - attempted "port" of the SP version which is completely client-side and
00725 //uses illegal gentity access. I am trying to keep this from being too
00726 //bandwidth-intensive.
00727 //This is primarily droid stuff I guess, I'm going to try to handle all humanoid
00728 //NPC stuff in with the actual player stuff if possible.
00729 void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles);
00730 static GAME_INLINE void G_G2NPCAngles(gentity_t *ent, vec3_t legs[3], vec3_t angles)
00731 {
00732         char *craniumBone = "cranium";
00733         char *thoracicBone = "thoracic"; //only used by atst so doesn't need a case
00734         qboolean looking = qfalse;
00735         vec3_t viewAngles;
00736         vec3_t lookAngles;
00737 
00738         if ( ent->client )
00739         {
00740                 if ( (ent->client->NPC_class == CLASS_PROBE ) 
00741                         || (ent->client->NPC_class == CLASS_R2D2 ) 
00742                         || (ent->client->NPC_class == CLASS_R5D2) 
00743                         || (ent->client->NPC_class == CLASS_ATST) )
00744                 {
00745                         vec3_t  trailingLegsAngles;
00746 
00747                         if (ent->s.eType == ET_NPC &&
00748                                 ent->s.m_iVehicleNum &&
00749                                 ent->s.NPC_class != CLASS_VEHICLE )
00750                         { //an NPC bolted to a vehicle should use the full angles
00751                                 VectorCopy(ent->r.currentAngles, angles);
00752                         }
00753                         else
00754                         {
00755                                 VectorCopy( ent->client->ps.viewangles, angles );
00756                                 angles[PITCH] = 0;
00757                         }
00758 
00759                         //FIXME: use actual swing/clamp tolerances?
00760                         /*
00761                         if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
00762                         {//on the ground
00763                                 CG_PlayerLegsYawFromMovement( cent, ent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
00764                         }
00765                         else
00766                         {//face legs to front
00767                                 CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
00768                         }
00769                         */
00770 
00771                         VectorCopy( ent->client->ps.viewangles, viewAngles );
00772         //                      viewAngles[YAW] = viewAngles[ROLL] = 0;
00773                         viewAngles[PITCH] *= 0.5;
00774                         VectorCopy( viewAngles, lookAngles );
00775 
00776                         lookAngles[1] = 0;
00777 
00778                         if ( ent->client->NPC_class == CLASS_ATST )
00779                         {//body pitch
00780                                 NPC_SetBoneAngles(ent, thoracicBone, lookAngles);
00781                                 //BG_G2SetBoneAngles( cent, ent, ent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT,POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
00782                         }
00783 
00784                         VectorCopy( viewAngles, lookAngles );
00785 
00786                         if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST )
00787                         {
00788                                 //CG_ATSTLegsYaw( cent, trailingLegsAngles );
00789                                 AnglesToAxis( trailingLegsAngles, legs );
00790                         }
00791                         else
00792                         {
00793                                 //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
00794                                 /*
00795                                 if ( angles[YAW] == cent->pe.legs.yawAngle )
00796                                 {
00797                                         cent->pe.legs.yawing = qfalse;
00798                                 }
00799                                 else
00800                                 {
00801                                         cent->pe.legs.yawing = qtrue;
00802                                 }
00803 
00804                                 cent->pe.legs.yawAngle = angles[YAW];
00805                                 if ( ent->client )
00806                                 {
00807                                         ent->client->renderInfo.legsYaw = angles[YAW];
00808                                 }
00809                                 AnglesToAxis( angles, legs );
00810                                 */
00811                         }
00812 
00813         //                      if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST )
00814         //                      {
00815         //                              looking = qfalse;
00816         //                      }
00817         //                      else
00818                         {       //look at lookTarget!
00819                                 //FIXME: snaps to side when lets go of lookTarget... ?
00820                                 float   lookingSpeed = 0.3f;
00821                                 looking = G_CheckLookTarget( ent, lookAngles, &lookingSpeed );
00822                                 lookAngles[PITCH] = lookAngles[ROLL] = 0;//droids can't pitch or roll their heads
00823                                 if ( looking )
00824                                 {//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap)
00825                                         ent->client->renderInfo.lookingDebounceTime = level.time + 1000;
00826                                 }
00827                         }
00828                         if ( ent->client->renderInfo.lookingDebounceTime > level.time )
00829                         {       //adjust for current body orientation
00830                                 vec3_t  oldLookAngles;
00831 
00832                                 lookAngles[YAW] -= 0;//ent->client->ps.viewangles[YAW];//cent->pe.torso.yawAngle;
00833                                 //lookAngles[YAW] -= cent->pe.legs.yawAngle;
00834 
00835                                 //normalize
00836                                 lookAngles[YAW] = AngleNormalize180( lookAngles[YAW] );
00837 
00838                                 //slowly lerp to this new value
00839                                 //Remember last headAngles
00840                                 VectorCopy( ent->client->renderInfo.lastHeadAngles, oldLookAngles );
00841                                 if( VectorCompare( oldLookAngles, lookAngles ) == qfalse )
00842                                 {
00843                                         //FIXME: This clamp goes off viewAngles,
00844                                         //but really should go off the tag_torso's axis[0] angles, no?
00845                                         lookAngles[YAW] = oldLookAngles[YAW]+(lookAngles[YAW]-oldLookAngles[YAW])*0.4f;
00846                                 }
00847                                 //Remember current lookAngles next time
00848                                 VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles );
00849                         }
00850                         else
00851                         {//Remember current lookAngles next time
00852                                 VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles );
00853                         }
00854                         if ( ent->client->NPC_class == CLASS_ATST )
00855                         {
00856                                 VectorCopy( ent->client->ps.viewangles, lookAngles );
00857                                 lookAngles[0] = lookAngles[2] = 0;
00858                                 lookAngles[YAW] -= trailingLegsAngles[YAW];
00859                         }
00860                         else
00861                         {
00862                                 lookAngles[PITCH] = lookAngles[ROLL] = 0;
00863                                 lookAngles[YAW] -= ent->client->ps.viewangles[YAW];
00864                         }
00865 
00866                         NPC_SetBoneAngles(ent, craniumBone, lookAngles);
00867                         //BG_G2SetBoneAngles( cent, ent, ent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw); 
00868                         //return;
00869                 }
00870                 else//if ( (ent->client->NPC_class == CLASS_GONK ) || (ent->client->NPC_class == CLASS_INTERROGATOR) || (ent->client->NPC_class == CLASS_SENTRY) )
00871                 {
00872                 //      VectorCopy( ent->client->ps.viewangles, angles );
00873                 //      AnglesToAxis( angles, legs );
00874                         //return;
00875                 }
00876         }
00877 }
00878 
00879 static GAME_INLINE void G_G2PlayerAngles( gentity_t *ent, vec3_t legs[3], vec3_t legsAngles)
00880 {
00881         qboolean tPitching = qfalse,
00882                          tYawing = qfalse,
00883                          lYawing = qfalse;
00884         float tYawAngle = ent->client->ps.viewangles[YAW],
00885                   tPitchAngle = 0,
00886                   lYawAngle = ent->client->ps.viewangles[YAW];
00887 
00888         int ciLegs = ent->client->ps.legsAnim;
00889         int ciTorso = ent->client->ps.torsoAnim;
00890 
00891         vec3_t turAngles;
00892         vec3_t lerpOrg, lerpAng;
00893 
00894         if (ent->s.eType == ET_NPC && ent->client)
00895         { //sort of hacky, but it saves a pretty big load off the server
00896                 int i = 0;
00897                 gentity_t *clEnt;
00898 
00899                 //If no real clients are in the same PVS then don't do any of this stuff, no one can see him anyway!
00900                 while (i < MAX_CLIENTS)
00901                 {
00902                         clEnt = &g_entities[i];
00903 
00904                         if (clEnt && clEnt->inuse && clEnt->client &&
00905                                 trap_InPVS(clEnt->client->ps.origin, ent->client->ps.origin))
00906                         { //this client can see him
00907                                 break;
00908                         }
00909 
00910                         i++;
00911                 }
00912 
00913                 if (i == MAX_CLIENTS)
00914                 { //no one can see him, just return
00915                         return;
00916                 }
00917         }
00918 
00919         VectorCopy(ent->client->ps.origin, lerpOrg);
00920         VectorCopy(ent->client->ps.viewangles, lerpAng);
00921 
00922         if (ent->localAnimIndex <= 1)
00923         { //don't do these things on non-humanoids
00924                 vec3_t lookAngles;
00925                 entityState_t *emplaced = NULL;
00926 
00927                 if (ent->client->ps.hasLookTarget)
00928                 {
00929                         VectorSubtract(g_entities[ent->client->ps.lookTarget].r.currentOrigin, ent->client->ps.origin, lookAngles);
00930                         vectoangles(lookAngles, lookAngles);
00931                         ent->client->lookTime = level.time + 1000;
00932                 }
00933                 else
00934                 {
00935                         VectorCopy(ent->client->ps.origin, lookAngles);
00936                 }
00937                 lookAngles[PITCH] = 0;
00938 
00939                 if (ent->client->ps.emplacedIndex)
00940                 {
00941                         emplaced = &g_entities[ent->client->ps.emplacedIndex].s;
00942                 }
00943 
00944                 BG_G2PlayerAngles(ent->ghoul2, ent->client->renderInfo.motionBolt, &ent->s, level.time, lerpOrg, lerpAng, legs,
00945                         legsAngles, &tYawing, &tPitching, &lYawing, &tYawAngle, &tPitchAngle, &lYawAngle, FRAMETIME, turAngles,
00946                         ent->modelScale, ciLegs, ciTorso, &ent->client->corrTime, lookAngles, ent->client->lastHeadAngles,
00947                         ent->client->lookTime, emplaced, NULL);
00948 
00949                 if (ent->client->ps.heldByClient && ent->client->ps.heldByClient <= MAX_CLIENTS)
00950                 { //then put our arm in this client's hand
00951                         //is index+1 because index 0 is valid.
00952                         int heldByIndex = ent->client->ps.heldByClient-1;
00953                         gentity_t *other = &g_entities[heldByIndex];
00954                         int lHandBolt = 0;
00955 
00956                         if (other && other->inuse && other->client && other->ghoul2)
00957                         {
00958                                 lHandBolt = trap_G2API_AddBolt(other->ghoul2, 0, "*l_hand");
00959                         }
00960                         else
00961                         { //they left the game, perhaps?
00962                                 ent->client->ps.heldByClient = 0;
00963                                 return;
00964                         }
00965 
00966                         if (lHandBolt)
00967                         {
00968                                 mdxaBone_t boltMatrix;
00969                                 vec3_t boltOrg;
00970                                 vec3_t tAngles;
00971 
00972                                 VectorCopy(other->client->ps.viewangles, tAngles);
00973                                 tAngles[PITCH] = tAngles[ROLL] = 0;
00974 
00975                                 trap_G2API_GetBoltMatrix(other->ghoul2, 0, lHandBolt, &boltMatrix, tAngles, other->client->ps.origin, level.time, 0, other->modelScale);
00976                                 boltOrg[0] = boltMatrix.matrix[0][3];
00977                                 boltOrg[1] = boltMatrix.matrix[1][3];
00978                                 boltOrg[2] = boltMatrix.matrix[2][3];
00979 
00980                                 BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s, ent->client->ps.torsoAnim/*BOTH_DEAD1*/, boltOrg, &ent->client->ikStatus,
00981                                         ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qfalse);
00982                         }
00983                 }
00984                 else if (ent->client->ikStatus)
00985                 { //make sure we aren't IKing if we don't have anyone to hold onto us.
00986                         int lHandBolt = 0;
00987 
00988                         if (ent && ent->inuse && ent->client && ent->ghoul2)
00989                         {
00990                                 lHandBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand");
00991                         }
00992                         else
00993                         { //This shouldn't happen, but just in case it does, we'll have a failsafe.
00994                                 ent->client->ikStatus = qfalse;
00995                         }
00996 
00997                         if (lHandBolt)
00998                         {
00999                                 BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s,
01000                                         ent->client->ps.torsoAnim/*BOTH_DEAD1*/, vec3_origin, &ent->client->ikStatus, ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qtrue);
01001                         }
01002                 }
01003         }
01004         else if ( ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
01005         {
01006                 vec3_t lookAngles;
01007 
01008                 VectorCopy(ent->client->ps.viewangles, legsAngles);
01009                 legsAngles[PITCH] = 0;
01010                 AnglesToAxis( legsAngles, legs );
01011 
01012                 VectorCopy(ent->client->ps.viewangles, lookAngles);
01013                 lookAngles[YAW] = lookAngles[ROLL] = 0;
01014 
01015                 BG_G2ATSTAngles( ent->ghoul2, level.time, lookAngles );
01016         }
01017         else if (ent->NPC)
01018         { //an NPC not using a humanoid skeleton, do special angle stuff.
01019                 if (ent->s.eType == ET_NPC &&
01020                         ent->s.NPC_class == CLASS_VEHICLE &&
01021                         ent->m_pVehicle &&
01022                         ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
01023                 { //fighters actually want to take pitch and roll into account for the axial angles
01024                         VectorCopy(ent->client->ps.viewangles, legsAngles);
01025                         AnglesToAxis( legsAngles, legs );
01026                 }
01027                 else
01028                 {
01029                         G_G2NPCAngles(ent, legs, legsAngles);
01030                 }
01031         }
01032 }
01033 
01034 static GAME_INLINE qboolean SaberAttacking(gentity_t *self)
01035 {
01036         if (PM_SaberInParry(self->client->ps.saberMove))
01037         {
01038                 return qfalse;
01039         }
01040         if (PM_SaberInBrokenParry(self->client->ps.saberMove))
01041         {
01042                 return qfalse;
01043         }
01044         if (PM_SaberInDeflect(self->client->ps.saberMove))
01045         {
01046                 return qfalse;
01047         }
01048         if (PM_SaberInBounce(self->client->ps.saberMove))
01049         {
01050                 return qfalse;
01051         }
01052         if (PM_SaberInKnockaway(self->client->ps.saberMove))
01053         {
01054                 return qfalse;
01055         }
01056 
01057         if (BG_SaberInAttack(self->client->ps.saberMove))
01058         {
01059                 if (self->client->ps.weaponstate == WEAPON_FIRING && self->client->ps.saberBlocked == BLOCKED_NONE)
01060                 { //if we're firing and not blocking, then we're attacking.
01061                         return qtrue;
01062                 }
01063         }
01064 
01065         if (BG_SaberInSpecial(self->client->ps.saberMove))
01066         {
01067                 return qtrue;
01068         }
01069 
01070         return qfalse;
01071 }
01072 
01073 typedef enum
01074 {
01075         LOCK_FIRST = 0,
01076         LOCK_TOP = LOCK_FIRST,
01077         LOCK_DIAG_TR,
01078         LOCK_DIAG_TL,
01079         LOCK_DIAG_BR,
01080         LOCK_DIAG_BL,
01081         LOCK_R,
01082         LOCK_L,
01083         LOCK_RANDOM
01084 } sabersLockMode_t;
01085 
01086 #define LOCK_IDEAL_DIST_TOP 32.0f
01087 #define LOCK_IDEAL_DIST_CIRCLE 48.0f
01088 
01089 #define SABER_HITDAMAGE 35
01090 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
01091 
01092 int G_SaberLockAnim( int attackerSaberStyle, int defenderSaberStyle, int topOrSide, int lockOrBreakOrSuperBreak, int winOrLose )
01093 {
01094         int baseAnim = -1;
01095         if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
01096         {//special case: if we're using the same style and locking
01097                 if ( attackerSaberStyle == defenderSaberStyle 
01098                         || (attackerSaberStyle>=SS_FAST&&attackerSaberStyle<=SS_TAVION&&defenderSaberStyle>=SS_FAST&&defenderSaberStyle<=SS_TAVION) )
01099                 {//using same style
01100                         if ( winOrLose == SABERLOCK_LOSE )
01101                         {//you want the defender's stance...
01102                                 switch ( defenderSaberStyle )
01103                                 {
01104                                 case SS_DUAL:
01105                                         if ( topOrSide == SABERLOCK_TOP )
01106                                         {
01107                                                 baseAnim = BOTH_LK_DL_DL_T_L_2;
01108                                         }
01109                                         else
01110                                         {
01111                                                 baseAnim = BOTH_LK_DL_DL_S_L_2;
01112                                         }
01113                                         break;
01114                                 case SS_STAFF:
01115                                         if ( topOrSide == SABERLOCK_TOP )
01116                                         {
01117                                                 baseAnim = BOTH_LK_ST_ST_T_L_2;
01118                                         }
01119                                         else
01120                                         {
01121                                                 baseAnim = BOTH_LK_ST_ST_S_L_2;
01122                                         }
01123                                         break;
01124                                 default:
01125                                         if ( topOrSide == SABERLOCK_TOP )
01126                                         {
01127                                                 baseAnim = BOTH_LK_S_S_T_L_2;
01128                                         }
01129                                         else
01130                                         {
01131                                                 baseAnim = BOTH_LK_S_S_S_L_2;
01132                                         }
01133                                         break;
01134                                 }
01135                         }
01136                 }
01137         }
01138         if ( baseAnim == -1 )
01139         {
01140                 switch ( attackerSaberStyle )
01141                 {
01142                 case SS_DUAL:
01143                         switch ( defenderSaberStyle )
01144                         {
01145                                 case SS_DUAL:
01146                                         baseAnim = BOTH_LK_DL_DL_S_B_1_L;
01147                                         break;
01148                                 case SS_STAFF:
01149                                         baseAnim = BOTH_LK_DL_ST_S_B_1_L;
01150                                         break;
01151                                 default://single
01152                                         baseAnim = BOTH_LK_DL_S_S_B_1_L;
01153                                         break;
01154                         }
01155                         break;
01156                 case SS_STAFF:
01157                         switch ( defenderSaberStyle )
01158                         {
01159                                 case SS_DUAL:
01160                                         baseAnim = BOTH_LK_ST_DL_S_B_1_L;
01161                                         break;
01162                                 case SS_STAFF:
01163                                         baseAnim = BOTH_LK_ST_ST_S_B_1_L;
01164                                         break;
01165                                 default://single
01166                                         baseAnim = BOTH_LK_ST_S_S_B_1_L;
01167                                         break;
01168                         }
01169                         break;
01170                 default://single
01171                         switch ( defenderSaberStyle )
01172                         {
01173                                 case SS_DUAL:
01174                                         baseAnim = BOTH_LK_S_DL_S_B_1_L;
01175                                         break;
01176                                 case SS_STAFF:
01177                                         baseAnim = BOTH_LK_S_ST_S_B_1_L;
01178                                         break;
01179                                 default://single
01180                                         baseAnim = BOTH_LK_S_S_S_B_1_L;
01181                                         break;
01182                         }
01183                         break;
01184                 }
01185                 //side lock or top lock?
01186                 if ( topOrSide == SABERLOCK_TOP )
01187                 {
01188                         baseAnim += 5;
01189                 }
01190                 //lock, break or superbreak?
01191                 if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
01192                 {
01193                         baseAnim += 2;
01194                 }
01195                 else 
01196                 {//a break or superbreak
01197                         if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK )
01198                         {
01199                                 baseAnim += 3;
01200                         }
01201                         //winner or loser?
01202                         if ( winOrLose == SABERLOCK_WIN )
01203                         {
01204                                 baseAnim += 1;
01205                         }
01206                 }
01207         }
01208         return baseAnim;
01209 }
01210 
01211 #include "../namespace_begin.h"
01212 extern qboolean BG_CheckIncrementLockAnim( int anim, int winOrLose ); //bg_saber.c
01213 #include "../namespace_end.h"
01214 #define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
01215 
01216 static GAME_INLINE qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
01217 {
01218         int             attAnim, defAnim = 0;
01219         float   attStart = 0.5f, defStart = 0.5f;
01220         float   idealDist = 48.0f;
01221         vec3_t  attAngles, defAngles, defDir;
01222         vec3_t  newOrg;
01223         vec3_t  attDir;
01224         float   diff = 0;
01225         trace_t trace;
01226 
01227         //MATCH ANIMS
01228         if ( lockMode == LOCK_RANDOM )
01229         {
01230                 lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
01231         }
01232         if ( attacker->client->ps.fd.saberAnimLevel >= SS_FAST
01233                 && attacker->client->ps.fd.saberAnimLevel <= SS_TAVION
01234                 && defender->client->ps.fd.saberAnimLevel >= SS_FAST
01235                 && defender->client->ps.fd.saberAnimLevel <= SS_TAVION )
01236         {//2 single sabers?  Just do it the old way...
01237                 switch ( lockMode )
01238                 {
01239                 case LOCK_TOP:
01240                         attAnim = BOTH_BF2LOCK;
01241                         defAnim = BOTH_BF1LOCK;
01242                         attStart = defStart = 0.5f;
01243                         idealDist = LOCK_IDEAL_DIST_TOP;
01244                         break;
01245                 case LOCK_DIAG_TR:
01246                         attAnim = BOTH_CCWCIRCLELOCK;
01247                         defAnim = BOTH_CWCIRCLELOCK;
01248                         attStart = defStart = 0.5f;
01249                         idealDist = LOCK_IDEAL_DIST_CIRCLE;
01250                         break;
01251                 case LOCK_DIAG_TL:
01252                         attAnim = BOTH_CWCIRCLELOCK;
01253                         defAnim = BOTH_CCWCIRCLELOCK;
01254                         attStart = defStart = 0.5f;
01255                         idealDist = LOCK_IDEAL_DIST_CIRCLE;
01256                         break;
01257                 case LOCK_DIAG_BR:
01258                         attAnim = BOTH_CWCIRCLELOCK;
01259                         defAnim = BOTH_CCWCIRCLELOCK;
01260                         attStart = defStart = 0.85f;
01261                         idealDist = LOCK_IDEAL_DIST_CIRCLE;
01262                         break;
01263                 case LOCK_DIAG_BL:
01264                         attAnim = BOTH_CCWCIRCLELOCK;
01265                         defAnim = BOTH_CWCIRCLELOCK;
01266                         attStart = defStart = 0.85f;
01267                         idealDist = LOCK_IDEAL_DIST_CIRCLE;
01268                         break;
01269                 case LOCK_R:
01270                         attAnim = BOTH_CCWCIRCLELOCK;
01271                         defAnim = BOTH_CWCIRCLELOCK;
01272                         attStart = defStart = 0.75f;
01273                         idealDist = LOCK_IDEAL_DIST_CIRCLE;
01274                         break;
01275                 case LOCK_L:
01276                         attAnim = BOTH_CWCIRCLELOCK;
01277                         defAnim = BOTH_CCWCIRCLELOCK;
01278                         attStart = defStart = 0.75f;
01279                         idealDist = LOCK_IDEAL_DIST_CIRCLE;
01280                         break;
01281                 default:
01282                         return qfalse;
01283                         break;
01284                 }
01285         }
01286         else
01287         {//use the new system
01288                 idealDist = LOCK_IDEAL_DIST_JKA;//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
01289                 if ( lockMode == LOCK_TOP )
01290                 {//top lock
01291                         attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN );
01292                         defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_LOSE );
01293                         attStart = defStart = 0.5f;
01294                 }
01295                 else
01296                 {//side lock
01297                         switch ( lockMode )
01298                         {
01299                         case LOCK_DIAG_TR:
01300                                 attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
01301                                 defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
01302                                 attStart = defStart = 0.5f;
01303                                 break;
01304                         case LOCK_DIAG_TL:
01305                                 attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
01306                                 defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
01307                                 attStart = defStart = 0.5f;
01308                                 break;
01309                         case LOCK_DIAG_BR:
01310                                 attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
01311                                 defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
01312                                 if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
01313                                 {
01314                                         attStart = 0.85f;//move to end of anim
01315                                 }
01316                                 else
01317                                 {
01318                                         attStart = 0.15f;//start at beginning of anim
01319                                 }
01320                                 if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
01321                                 {
01322                                         defStart = 0.85f;//start at end of anim
01323                                 }
01324                                 else
01325                                 {
01326                                         defStart = 0.15f;//start at beginning of anim
01327                                 }
01328                                 break;
01329                         case LOCK_DIAG_BL:
01330                                 attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
01331                                 defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
01332                                 if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
01333                                 {
01334                                         attStart = 0.85f;//move to end of anim
01335                                 }
01336                                 else
01337                                 {
01338                                         attStart = 0.15f;//start at beginning of anim
01339                                 }
01340                                 if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
01341                                 {
01342                                         defStart = 0.85f;//start at end of anim
01343                                 }
01344                                 else
01345                                 {
01346                                         defStart = 0.15f;//start at beginning of anim
01347                                 }
01348                                 break;
01349                         case LOCK_R:
01350                                 attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
01351                                 defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
01352                                 if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
01353                                 {
01354                                         attStart = 0.75f;//move to end of anim
01355                                 }
01356                                 else
01357                                 {
01358                                         attStart = 0.25f;//start at beginning of anim
01359                                 }
01360                                 if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
01361                                 {
01362                                         defStart = 0.75f;//start at end of anim
01363                                 }
01364                                 else
01365                                 {
01366                                         defStart = 0.25f;//start at beginning of anim
01367                                 }
01368                                 break;
01369                         case LOCK_L:
01370                                 attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
01371                                 defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
01372                                 //attacker starts with advantage
01373                                 if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
01374                                 {
01375                                         attStart = 0.75f;//move to end of anim
01376                                 }
01377                                 else
01378                                 {
01379                                         attStart = 0.25f;//start at beginning of anim
01380                                 }
01381                                 if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
01382                                 {
01383                                         defStart = 0.75f;//start at end of anim
01384                                 }
01385                                 else
01386                                 {
01387                                         defStart = 0.25f;//start at beginning of anim
01388                                 }
01389                                 break;
01390                         default:
01391                                 return qfalse;
01392                                 break;
01393                         }
01394                 }
01395         }
01396 
01397         G_SetAnim(attacker, NULL, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
01398         attacker->client->ps.saberLockFrame = bgAllAnims[attacker->localAnimIndex].anims[attAnim].firstFrame+(bgAllAnims[attacker->localAnimIndex].anims[attAnim].numFrames*attStart);
01399 
01400         G_SetAnim(defender, NULL, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
01401         defender->client->ps.saberLockFrame = bgAllAnims[defender->localAnimIndex].anims[defAnim].firstFrame+(bgAllAnims[defender->localAnimIndex].anims[defAnim].numFrames*defStart);
01402 
01403         attacker->client->ps.saberLockHits = 0;
01404         defender->client->ps.saberLockHits = 0;
01405 
01406         attacker->client->ps.saberLockAdvance = qfalse;
01407         defender->client->ps.saberLockAdvance = qfalse;
01408 
01409         VectorClear( attacker->client->ps.velocity );
01410         VectorClear( defender->client->ps.velocity );
01411         attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + 10000;
01412         attacker->client->ps.saberLockEnemy = defender->s.number;
01413         defender->client->ps.saberLockEnemy = attacker->s.number;
01414         attacker->client->ps.weaponTime = defender->client->ps.weaponTime = Q_irand( 1000, 3000 );//delay 1 to 3 seconds before pushing
01415 
01416         VectorSubtract( defender->r.currentOrigin, attacker->r.currentOrigin, defDir );
01417         VectorCopy( attacker->client->ps.viewangles, attAngles );
01418         attAngles[YAW] = vectoyaw( defDir );
01419         SetClientViewAngle( attacker, attAngles );
01420         defAngles[PITCH] = attAngles[PITCH]*-1;
01421         defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
01422         defAngles[ROLL] = 0;
01423         SetClientViewAngle( defender, defAngles );
01424         
01425         //MATCH POSITIONS
01426         diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
01427         //try to move attacker half the diff towards the defender
01428         VectorMA( attacker->r.currentOrigin, diff*0.5f, defDir, newOrg );
01429 
01430         trap_Trace( &trace, attacker->r.currentOrigin, attacker->r.mins, attacker->r.maxs, newOrg, attacker->s.number, attacker->clipmask );
01431         if ( !trace.startsolid && !trace.allsolid )
01432         {
01433                 G_SetOrigin( attacker, trace.endpos );
01434                 if (attacker->client)
01435                 {
01436                         VectorCopy(trace.endpos, attacker->client->ps.origin);
01437                 }
01438                 trap_LinkEntity( attacker );
01439         }
01440         //now get the defender's dist and do it for him too
01441         VectorSubtract( attacker->r.currentOrigin, defender->r.currentOrigin, attDir );
01442         diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
01443         //try to move defender all of the remaining diff towards the attacker
01444         VectorMA( defender->r.currentOrigin, diff, attDir, newOrg );
01445         trap_Trace( &trace, defender->r.currentOrigin, defender->r.mins, defender->r.maxs, newOrg, defender->s.number, defender->clipmask );
01446         if ( !trace.startsolid && !trace.allsolid )
01447         {
01448                 if (defender->client)
01449                 {
01450                         VectorCopy(trace.endpos, defender->client->ps.origin);
01451                 }
01452                 G_SetOrigin( defender, trace.endpos );
01453                 trap_LinkEntity( defender );
01454         }
01455 
01456         //DONE!
01457         return qtrue;
01458 }
01459 
01460 qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
01461 {
01462         float dist;
01463         qboolean        ent1BlockingPlayer = qfalse;
01464         qboolean        ent2BlockingPlayer = qfalse;
01465 
01466         if ( g_debugSaberLocks.integer )
01467         {
01468                 WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
01469                 return qtrue;
01470         }
01471         //for now.. it's not fair to the lone duelist.
01472         //we need dual saber lock animations.
01473         if (g_gametype.integer == GT_POWERDUEL)
01474         {
01475                 return qfalse;
01476         }
01477 
01478         if (!g_saberLocking.integer)
01479         {
01480                 return qfalse;
01481         }
01482 
01483         if (!ent1->client || !ent2->client)
01484         {
01485                 return qfalse;
01486         }
01487 
01488         if (ent1->s.eType == ET_NPC ||
01489                 ent2->s.eType == ET_NPC)
01490         { //if either ents is NPC, then never let an NPC lock with someone on the same playerTeam
01491                 if (ent1->client->playerTeam == ent2->client->playerTeam)
01492                 {
01493                         return qfalse;
01494                 }
01495         }
01496 
01497         if (!ent1->client->ps.saberEntityNum ||
01498                 !ent2->client->ps.saberEntityNum ||
01499                 ent1->client->ps.saberInFlight ||
01500                 ent2->client->ps.saberInFlight)
01501         { //can't get in lock if one of them has had the saber knocked out of his hand
01502                 return qfalse;
01503         }
01504 
01505         if (ent1->s.eType != ET_NPC && ent2->s.eType != ET_NPC)
01506         { //can always get into locks with NPCs
01507                 if (!ent1->client->ps.duelInProgress ||
01508                         !ent2->client->ps.duelInProgress ||
01509                         ent1->client->ps.duelIndex != ent2->s.number ||
01510                         ent2->client->ps.duelIndex != ent1->s.number)
01511                 { //only allow saber locking if two players are dueling with each other directly
01512                         if (g_gametype.integer != GT_DUEL && g_gametype.integer != GT_POWERDUEL)
01513                         {
01514                                 return qfalse;
01515                         }
01516                 }
01517         }
01518 
01519         if ( fabs( ent1->r.currentOrigin[2]-ent2->r.currentOrigin[2] ) > 16 )
01520         {
01521                 return qfalse;
01522         }
01523         if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
01524                 ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
01525         {
01526                 return qfalse;
01527         }
01528         dist = DistanceSquared(ent1->r.currentOrigin,ent2->r.currentOrigin);
01529         if ( dist < 64 || dist > 6400 )
01530         {//between 8 and 80 from each other
01531                 return qfalse;
01532         }
01533 
01534         if (BG_InSpecialJump(ent1->client->ps.legsAnim))
01535         {
01536                 return qfalse;
01537         }
01538         if (BG_InSpecialJump(ent2->client->ps.legsAnim))
01539         {
01540                 return qfalse;
01541         }
01542 
01543         if (BG_InRoll(&ent1->client->ps, ent1->client->ps.legsAnim))
01544         {
01545                 return qfalse;
01546         }
01547         if (BG_InRoll(&ent2->client->ps, ent2->client->ps.legsAnim))
01548         {
01549                 return qfalse;
01550         }
01551 
01552         if (ent1->client->ps.forceHandExtend != HANDEXTEND_NONE ||
01553                 ent2->client->ps.forceHandExtend != HANDEXTEND_NONE)
01554         {
01555                 return qfalse;
01556         }
01557 
01558         if ((ent1->client->ps.pm_flags & PMF_DUCKED) ||
01559                 (ent2->client->ps.pm_flags & PMF_DUCKED))
01560         {
01561                 return qfalse;
01562         }
01563 
01564         if ( (ent1->client->saber[0].saberFlags&SFL_NOT_LOCKABLE)
01565                 || (ent2->client->saber[0].saberFlags&SFL_NOT_LOCKABLE) )
01566         {
01567                 return qfalse;
01568         }
01569         if ( ent1->client->saber[1].model
01570                 && ent1->client->saber[1].model[0]
01571                 && !ent1->client->ps.saberHolstered
01572                 && (ent1->client->saber[1].saberFlags&SFL_NOT_LOCKABLE) )
01573         {
01574                 return qfalse;
01575         }
01576         if ( ent2->client->saber[1].model
01577                 && ent2->client->saber[1].model[0]
01578                 && !ent2->client->ps.saberHolstered
01579                 && (ent2->client->saber[1].saberFlags&SFL_NOT_LOCKABLE) )
01580         {
01581                 return qfalse;
01582         }
01583 
01584         if (!InFront( ent1->client->ps.origin, ent2->client->ps.origin, ent2->client->ps.viewangles, 0.4f ))
01585         {
01586                 return qfalse;
01587         }
01588         if (!InFront( ent2->client->ps.origin, ent1->client->ps.origin, ent1->client->ps.viewangles, 0.4f ))
01589         {
01590                 return qfalse;
01591         }
01592 
01593         //T to B lock
01594         if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
01595                 ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
01596                 ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
01597                 ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
01598                 ent1->client->ps.torsoAnim == BOTH_A5_T__B_ ||
01599                 ent1->client->ps.torsoAnim == BOTH_A6_T__B_ ||
01600                 ent1->client->ps.torsoAnim == BOTH_A7_T__B_)
01601         {//ent1 is attacking top-down
01602                 return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
01603         }
01604 
01605         if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
01606                 ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
01607                 ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
01608                 ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
01609                 ent2->client->ps.torsoAnim == BOTH_A5_T__B_ ||
01610                 ent2->client->ps.torsoAnim == BOTH_A6_T__B_ ||
01611                 ent2->client->ps.torsoAnim == BOTH_A7_T__B_)
01612         {//ent2 is attacking top-down
01613                 return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
01614         }
01615 
01616         if ( ent1->s.number == 0 &&
01617                 ent1->client->ps.saberBlocking == BLK_WIDE && ent1->client->ps.weaponTime <= 0 )
01618         {
01619                 ent1BlockingPlayer = qtrue;
01620         }
01621         if ( ent2->s.number == 0 &&
01622                 ent2->client->ps.saberBlocking == BLK_WIDE && ent2->client->ps.weaponTime <= 0 )
01623         {
01624                 ent2BlockingPlayer = qtrue;
01625         }
01626 
01627         //TR to BL lock
01628         if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
01629                 ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
01630                 ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
01631                 ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
01632                 ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
01633                 ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
01634                 ent1->client->ps.torsoAnim == BOTH_A7_TR_BL)
01635         {//ent1 is attacking diagonally
01636                 if ( ent2BlockingPlayer )
01637                 {//player will block this anyway
01638                         return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
01639                 }
01640                 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
01641                         ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
01642                         ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
01643                         ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
01644                         ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
01645                         ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
01646                         ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
01647                         ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
01648                 {//ent2 is attacking in the opposite diagonal
01649                         return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
01650                 }
01651                 if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
01652                         ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
01653                         ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
01654                         ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
01655                         ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
01656                         ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
01657                         ent2->client->ps.torsoAnim == BOTH_A7_BR_TL ||
01658                         ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
01659                 {//ent2 is attacking in the opposite diagonal
01660                         return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
01661                 }
01662                 return qfalse;
01663         }
01664 
01665         if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
01666                 ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
01667                 ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
01668                 ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
01669                 ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
01670                 ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
01671                 ent2->client->ps.torsoAnim == BOTH_A7_TR_BL)
01672         {//ent2 is attacking diagonally
01673                 if ( ent1BlockingPlayer )
01674                 {//player will block this anyway
01675                         return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
01676                 }
01677                 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
01678                         ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
01679                         ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
01680                         ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
01681                         ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
01682                         ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
01683                         ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
01684                         ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
01685                 {//ent1 is attacking in the opposite diagonal
01686                         return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
01687                 }
01688                 if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
01689                         ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
01690                         ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
01691                         ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
01692                         ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
01693                         ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
01694                         ent1->client->ps.torsoAnim == BOTH_A7_BR_TL ||
01695                         ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
01696                 {//ent1 is attacking in the opposite diagonal
01697                         return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
01698                 }
01699                 return qfalse;
01700         }
01701 
01702         //TL to BR lock
01703         if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
01704                 ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
01705                 ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
01706                 ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
01707                 ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
01708                 ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
01709                 ent1->client->ps.torsoAnim == BOTH_A7_TL_BR)
01710         {//ent1 is attacking diagonally
01711                 if ( ent2BlockingPlayer )
01712                 {//player will block this anyway
01713                         return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
01714                 }
01715                 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
01716                         ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
01717                         ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
01718                         ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
01719                         ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
01720                         ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
01721                         ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
01722                         ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
01723                 {//ent2 is attacking in the opposite diagonal
01724                         return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
01725                 }
01726                 if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
01727                         ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
01728                         ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
01729                         ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
01730                         ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
01731                         ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
01732                         ent2->client->ps.torsoAnim == BOTH_A7_BL_TR ||
01733                         ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
01734                 {//ent2 is attacking in the opposite diagonal
01735                         return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
01736                 }
01737                 return qfalse;
01738         }
01739 
01740         if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
01741                 ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
01742                 ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
01743                 ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
01744                 ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
01745                 ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
01746                 ent2->client->ps.torsoAnim == BOTH_A7_TL_BR)
01747         {//ent2 is attacking diagonally
01748                 if ( ent1BlockingPlayer )
01749                 {//player will block this anyway
01750                         return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
01751                 }
01752                 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
01753                         ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
01754                         ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
01755                         ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
01756                         ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
01757                         ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
01758                         ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
01759                         ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
01760                 {//ent1 is attacking in the opposite diagonal
01761                         return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
01762                 }
01763                 if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
01764                         ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
01765                         ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
01766                         ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
01767                         ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
01768                         ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
01769                         ent1->client->ps.torsoAnim == BOTH_A7_BL_TR ||
01770                         ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
01771                 {//ent1 is attacking in the opposite diagonal
01772                         return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
01773                 }
01774                 return qfalse;
01775         }
01776         //L to R lock
01777         if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
01778                 ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
01779                 ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
01780                 ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
01781                 ent1->client->ps.torsoAnim == BOTH_A5__L__R ||
01782                 ent1->client->ps.torsoAnim == BOTH_A6__L__R ||
01783                 ent1->client->ps.torsoAnim == BOTH_A7__L__R)
01784         {//ent1 is attacking l to r
01785                 if ( ent2BlockingPlayer )
01786                 {//player will block this anyway
01787                         return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
01788                 }
01789                 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
01790                         ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
01791                         ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
01792                         ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
01793                         ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
01794                         ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
01795                         ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
01796                         ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
01797                         ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
01798                 {//ent2 is attacking or blocking on the r
01799                         return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
01800                 }
01801                 return qfalse;
01802         }
01803         if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
01804                 ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
01805                 ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
01806                 ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
01807                 ent2->client->ps.torsoAnim == BOTH_A5__L__R ||
01808                 ent2->client->ps.torsoAnim == BOTH_A6__L__R ||
01809                 ent2->client->ps.torsoAnim == BOTH_A7__L__R)
01810         {//ent2 is attacking l to r
01811                 if ( ent1BlockingPlayer )
01812                 {//player will block this anyway
01813                         return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
01814                 }
01815                 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
01816                         ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
01817                         ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
01818                         ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
01819                         ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
01820                         ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
01821                         ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
01822                         ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
01823                         ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
01824                 {//ent1 is attacking or blocking on the r
01825                         return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
01826                 }
01827                 return qfalse;
01828         }
01829         //R to L lock
01830         if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
01831                 ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
01832                 ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
01833                 ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
01834                 ent1->client->ps.torsoAnim == BOTH_A5__R__L ||
01835                 ent1->client->ps.torsoAnim == BOTH_A6__R__L ||
01836                 ent1->client->ps.torsoAnim == BOTH_A7__R__L)
01837         {//ent1 is attacking r to l
01838                 if ( ent2BlockingPlayer )
01839                 {//player will block this anyway
01840                         return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
01841                 }
01842                 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
01843                         ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
01844                         ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
01845                         ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
01846                         ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
01847                         ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
01848                         ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
01849                         ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
01850                         ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
01851                 {//ent2 is attacking or blocking on the l
01852                         return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
01853                 }
01854                 return qfalse;
01855         }
01856         if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
01857                 ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
01858                 ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
01859                 ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
01860                 ent2->client->ps.torsoAnim == BOTH_A5__R__L ||
01861                 ent2->client->ps.torsoAnim == BOTH_A6__R__L ||
01862                 ent2->client->ps.torsoAnim == BOTH_A7__R__L)
01863         {//ent2 is attacking r to l
01864                 if ( ent1BlockingPlayer )
01865                 {//player will block this anyway
01866                         return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
01867                 }
01868                 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
01869                         ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
01870                         ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
01871                         ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
01872                         ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
01873                         ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
01874                         ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
01875                         ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
01876                         ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
01877                 {//ent1 is attacking or blocking on the l
01878                         return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
01879                 }
01880                 return qfalse;
01881         }
01882         if ( !Q_irand( 0, 10 ) )
01883         {
01884                 return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
01885         }
01886         return qfalse;
01887 }
01888 
01889 static GAME_INLINE int G_GetParryForBlock(int block)
01890 {
01891         switch (block)
01892         {
01893                 case BLOCKED_UPPER_RIGHT:
01894                         return LS_PARRY_UR;
01895                         break;
01896                 case BLOCKED_UPPER_RIGHT_PROJ:
01897                         return LS_REFLECT_UR;
01898                         break;
01899                 case BLOCKED_UPPER_LEFT:
01900                         return LS_PARRY_UL;
01901                         break;
01902                 case BLOCKED_UPPER_LEFT_PROJ:
01903                         return LS_REFLECT_UL;
01904                         break;
01905                 case BLOCKED_LOWER_RIGHT:
01906                         return LS_PARRY_LR;
01907                         break;
01908                 case BLOCKED_LOWER_RIGHT_PROJ:
01909                         return LS_REFLECT_LR;
01910                         break;
01911                 case BLOCKED_LOWER_LEFT:
01912                         return LS_PARRY_LL;
01913                         break;
01914                 case BLOCKED_LOWER_LEFT_PROJ:
01915                         return LS_REFLECT_LL;
01916                         break;
01917                 case BLOCKED_TOP:
01918                         return LS_PARRY_UP;
01919                         break;
01920                 case BLOCKED_TOP_PROJ:
01921                         return LS_REFLECT_UP;
01922                         break;
01923                 default:
01924                         break;
01925         }
01926 
01927         return LS_NONE;
01928 }
01929 
01930 #include "../namespace_begin.h"
01931 int PM_SaberBounceForAttack( int move );
01932 int PM_SaberDeflectionForQuad( int quad );
01933 #include "../namespace_end.h"
01934 
01935 extern stringID_table_t animTable[MAX_ANIMATIONS+1];
01936 static GAME_INLINE qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender, float saberHitFraction )
01937 {
01938         qboolean animBasedDeflection = qtrue;
01939         int attSaberLevel, defSaberLevel;
01940 
01941         if ( !attacker || !attacker->client || !attacker->ghoul2 )
01942         {
01943                 return qfalse;
01944         }
01945         if ( !defender || !defender->client || !defender->ghoul2 )
01946         {
01947                 return qfalse;
01948         }
01949 
01950         if ((level.time - attacker->client->lastSaberStorageTime) > 500)
01951         { //last update was too long ago, something is happening to this client to prevent his saber from updating
01952                 return qfalse;
01953         }
01954         if ((level.time - defender->client->lastSaberStorageTime) > 500)
01955         { //ditto
01956                 return qfalse;
01957         }
01958 
01959         attSaberLevel = G_SaberAttackPower(attacker, SaberAttacking(attacker));
01960         defSaberLevel = G_SaberAttackPower(defender, SaberAttacking(defender));
01961 
01962         if ( animBasedDeflection )
01963         {
01964                 //Hmm, let's try just basing it off the anim
01965                 int attQuadStart = saberMoveData[attacker->client->ps.saberMove].startQuad;
01966                 int attQuadEnd = saberMoveData[attacker->client->ps.saberMove].endQuad;
01967                 int defQuad = saberMoveData[defender->client->ps.saberMove].endQuad;
01968                 int quadDiff = fabs((float)(defQuad-attQuadStart));
01969 
01970                 if ( defender->client->ps.saberMove == LS_READY )
01971                 {
01972                         //FIXME: we should probably do SOMETHING here...
01973                         //I have this return qfalse here in the hopes that
01974                         //the defender will pick a parry and the attacker
01975                         //will hit the defender's saber again.
01976                         //But maybe this func call should come *after*
01977                         //it's decided whether or not the defender is
01978                         //going to parry.
01979                         return qfalse;
01980                 }
01981 
01982                 //reverse the left/right of the defQuad because of the mirrored nature of facing each other in combat
01983                 switch ( defQuad )
01984                 {
01985                 case Q_BR:
01986                         defQuad = Q_BL;
01987                         break;
01988                 case Q_R:
01989                         defQuad = Q_L;
01990                         break;
01991                 case Q_TR:
01992                         defQuad = Q_TL;
01993                         break;
01994                 case Q_TL:
01995                         defQuad = Q_TR;
01996                         break;
01997                 case Q_L:
01998                         defQuad = Q_R;
01999                         break;
02000                 case Q_BL:
02001                         defQuad = Q_BR;
02002                         break;
02003                 }
02004 
02005                 if ( quadDiff > 4 )
02006                 {//wrap around so diff is never greater than 180 (4 * 45)
02007                         quadDiff = 4 - (quadDiff - 4);
02008                 }
02009                 //have the quads, find a good anim to use
02010                 if ( (!quadDiff || (quadDiff == 1 && Q_irand(0,1))) //defender pretty much stopped the attack at a 90 degree angle
02011                         && (defSaberLevel == attSaberLevel || Q_irand( 0, defSaberLevel-attSaberLevel ) >= 0) )//and the defender's style is stronger
02012                 {
02013                         //bounce straight back
02014 #ifndef FINAL_BUILD
02015                         int attMove = attacker->client->ps.saberMove;
02016 #endif
02017                         attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
02018 #ifndef FINAL_BUILD
02019                         if (g_saberDebugPrint.integer)
02020                         {
02021                                 Com_Printf( "attack %s vs. parry %s bounced to %s\n", 
02022                                         animTable[saberMoveData[attMove].animToUse].name, 
02023                                         animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
02024                                         animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
02025                         }
02026 #endif
02027                         attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
02028                         return qfalse;
02029                 }
02030                 else
02031                 {//attack hit at an angle, figure out what angle it should bounce off att
02032                         int newQuad;
02033                         quadDiff = defQuad - attQuadEnd;
02034                         //add half the diff of between the defense and attack end to the attack end
02035                         if ( quadDiff > 4 )
02036                         {
02037                                 quadDiff = 4 - (quadDiff - 4);
02038                         }
02039                         else if ( quadDiff < -4 )
02040                         {
02041                                 quadDiff = -4 + (quadDiff + 4);
02042                         }
02043                         newQuad = attQuadEnd + ceil( ((float)quadDiff)/2.0f );
02044                         if ( newQuad < Q_BR )
02045                         {//less than zero wraps around
02046                                 newQuad = Q_B + newQuad;
02047                         }
02048                         if ( newQuad == attQuadStart )
02049                         {//never come off at the same angle that we would have if the attack was not interrupted
02050                                 if ( Q_irand(0, 1) )
02051                                 {
02052                                         newQuad--;
02053                                 }
02054                                 else
02055                                 {
02056                                         newQuad++;
02057                                 }
02058                                 if ( newQuad < Q_BR )
02059                                 {
02060                                         newQuad = Q_B;
02061                                 }
02062                                 else if ( newQuad > Q_B )
02063                                 {
02064                                         newQuad = Q_BR;
02065                                 }
02066                         }
02067                         if ( newQuad == defQuad )
02068                         {//bounce straight back
02069 #ifndef FINAL_BUILD
02070                                 int attMove = attacker->client->ps.saberMove;
02071 #endif
02072                                 attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
02073 #ifndef FINAL_BUILD
02074                                 if (g_saberDebugPrint.integer)
02075                                 {
02076                                         Com_Printf( "attack %s vs. parry %s bounced to %s\n", 
02077                                                 animTable[saberMoveData[attMove].animToUse].name, 
02078                                                 animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
02079                                                 animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
02080                                 }
02081 #endif
02082                                 attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
02083                                 return qfalse;
02084                         }
02085                         //else, pick a deflection
02086                         else
02087                         {
02088 #ifndef FINAL_BUILD
02089                                 int attMove = attacker->client->ps.saberMove;
02090 #endif
02091                                 attacker->client->ps.saberMove = PM_SaberDeflectionForQuad( newQuad );
02092 #ifndef FINAL_BUILD
02093                                 if (g_saberDebugPrint.integer)
02094                                 {
02095                                         Com_Printf( "attack %s vs. parry %s deflected to %s\n", 
02096                                                 animTable[saberMoveData[attMove].animToUse].name, 
02097                                                 animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
02098                                                 animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
02099                                 }
02100 #endif
02101                                 attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
02102                                 return qtrue;
02103                         }
02104                 }
02105         }
02106         else
02107         { //old math-based method (probably broken)
02108                 vec3_t  att_HitDir, def_BladeDir, temp;
02109                 float   hitDot;
02110 
02111                 VectorCopy(attacker->client->lastSaberBase_Always, temp);
02112 
02113                 AngleVectors(attacker->client->lastSaberDir_Always, att_HitDir, 0, 0);
02114 
02115                 AngleVectors(defender->client->lastSaberDir_Always, def_BladeDir, 0, 0);
02116 
02117                 //now compare
02118                 hitDot = DotProduct( att_HitDir, def_BladeDir );
02119                 if ( hitDot < 0.25f && hitDot > -0.25f )
02120                 {//hit pretty much perpendicular, pop straight back
02121                         attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
02122                         attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
02123                         return qfalse;
02124                 }
02125                 else 
02126                 {//a deflection
02127                         vec3_t  att_Right, att_Up, att_DeflectionDir;
02128                         float   swingRDot, swingUDot;
02129 
02130                         //get the direction of the deflection
02131                         VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
02132                         //get our bounce straight back direction
02133                         VectorScale( att_HitDir, -1.0f, temp );
02134                         //add the bounce back and deflection
02135                         VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
02136                         //normalize the result to determine what direction our saber should bounce back toward
02137                         VectorNormalize( att_DeflectionDir );
02138 
02139                         //need to know the direction of the deflectoin relative to the attacker's facing
02140                         VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
02141                         AngleVectors( temp, NULL, att_Right, att_Up );
02142                         swingRDot = DotProduct( att_Right, att_DeflectionDir );
02143                         swingUDot = DotProduct( att_Up, att_DeflectionDir );
02144 
02145                         if ( swingRDot > 0.25f )
02146                         {//deflect to right
02147                                 if ( swingUDot > 0.25f )
02148                                 {//deflect to top
02149                                         attacker->client->ps.saberMove = LS_D1_TR;
02150                                 }
02151                                 else if ( swingUDot < -0.25f )
02152                                 {//deflect to bottom
02153                                         attacker->client->ps.saberMove = LS_D1_BR;
02154                                 }
02155                                 else
02156                                 {//deflect horizontally
02157                                         attacker->client->ps.saberMove = LS_D1__R;
02158                                 }
02159                         }
02160                         else if ( swingRDot < -0.25f )
02161                         {//deflect to left
02162                                 if ( swingUDot > 0.25f )
02163                                 {//deflect to top
02164                                         attacker->client->ps.saberMove = LS_D1_TL;
02165                                 }
02166                                 else if ( swingUDot < -0.25f )
02167                                 {//deflect to bottom
02168                                         attacker->client->ps.saberMove = LS_D1_BL;
02169                                 }
02170                                 else
02171                                 {//deflect horizontally
02172                                         attacker->client->ps.saberMove = LS_D1__L;
02173                                 }
02174                         }
02175                         else
02176                         {//deflect in middle
02177                                 if ( swingUDot > 0.25f )
02178                                 {//deflect to top
02179                                         attacker->client->ps.saberMove = LS_D1_T_;
02180                                 }
02181                                 else if ( swingUDot < -0.25f )
02182                                 {//deflect to bottom
02183                                         attacker->client->ps.saberMove = LS_D1_B_;
02184                                 }
02185                                 else
02186                                 {//deflect horizontally?  Well, no such thing as straight back in my face, so use top
02187                                         if ( swingRDot > 0 )
02188                                         {
02189                                                 attacker->client->ps.saberMove = LS_D1_TR;
02190                                         }
02191                                         else if ( swingRDot < 0 )
02192                                         {
02193                                                 attacker->client->ps.saberMove = LS_D1_TL;
02194                                         }
02195                                         else
02196                                         {
02197                                                 attacker->client->ps.saberMove = LS_D1_T_;
02198                                         }
02199                                 }
02200                         }
02201 
02202                         attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
02203                         return qtrue;
02204                 }
02205         }
02206 }
02207 
02208 int G_KnockawayForParry( int move )
02209 {
02210         //FIXME: need actual anims for this
02211         //FIXME: need to know which side of the saber was hit!  For now, we presume the saber gets knocked away from the center
02212         switch ( move )
02213         {
02214         case LS_PARRY_UP:
02215                 return LS_K1_T_;//push up
02216                 break;
02217         case LS_PARRY_UR:
02218         default://case LS_READY:
02219                 return LS_K1_TR;//push up, slightly to right
02220                 break;
02221         case LS_PARRY_UL:
02222                 return LS_K1_TL;//push up and to left
02223                 break;
02224         case LS_PARRY_LR:
02225                 return LS_K1_BR;//push down and to left
02226                 break;
02227         case LS_PARRY_LL:
02228                 return LS_K1_BL;//push down and to right
02229                 break;
02230         }
02231 }
02232 
02233 #define SABER_NONATTACK_DAMAGE 1
02234 
02235 //For strong attacks, we ramp damage based on the point in the attack animation
02236 static GAME_INLINE int G_GetAttackDamage(gentity_t *self, int minDmg, int maxDmg, float multPoint)
02237 {
02238         int peakDif = 0;
02239         int speedDif = 0;
02240         int totalDamage = maxDmg;
02241         float peakPoint = 0;
02242         float attackAnimLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].frameLerp));
02243         float currentPoint = 0;
02244         float damageFactor = 0;
02245         float animSpeedFactor = 1.0f;
02246 
02247         //Be sure to scale by the proper anim speed just as if we were going to play the animation
02248         BG_SaberStartTransAnim(self->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs);
02249         speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor);
02250         attackAnimLength += speedDif;
02251         peakPoint = attackAnimLength;
02252         peakPoint -= attackAnimLength*multPoint;
02253 
02254         //we treat torsoTimer as the point in the animation (closer it is to attackAnimLength, closer it is to beginning)
02255         currentPoint = self->client->ps.torsoTimer;
02256 
02257         if (peakPoint > currentPoint)
02258         {
02259                 peakDif = (peakPoint - currentPoint);
02260         }
02261         else
02262         {
02263                 peakDif = (currentPoint - peakPoint);
02264         }
02265 
02266         damageFactor = (float)((currentPoint/peakPoint));
02267         if (damageFactor > 1)
02268         {
02269                 damageFactor = (2.0f - damageFactor);
02270         }
02271 
02272         totalDamage *= damageFactor;
02273         if (totalDamage < minDmg)
02274         {
02275                 totalDamage = minDmg;
02276         }
02277         if (totalDamage > maxDmg)
02278         {
02279                 totalDamage = maxDmg;
02280         }
02281 
02282         //Com_Printf("%i\n", totalDamage);
02283 
02284         return totalDamage;
02285 }
02286 
02287 //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)
02288 static GAME_INLINE float G_GetAnimPoint(gentity_t *self)
02289 {
02290         int speedDif = 0;
02291         float attackAnimLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].frameLerp));
02292         float currentPoint = 0;
02293         float animSpeedFactor = 1.0f;
02294         float animPercentage = 0;
02295 
02296         //Be sure to scale by the proper anim speed just as if we were going to play the animation
02297         BG_SaberStartTransAnim(self->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs);
02298         speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor);
02299         attackAnimLength += speedDif;
02300 
02301         currentPoint = self->client->ps.torsoTimer;
02302 
02303         animPercentage = currentPoint/attackAnimLength;
02304 
02305         //Com_Printf("%f\n", animPercentage);
02306 
02307         return animPercentage;
02308 }
02309 
02310 static GAME_INLINE qboolean G_ClientIdleInWorld(gentity_t *ent)
02311 {
02312         if (ent->s.eType == ET_NPC)
02313         {
02314                 return qfalse;
02315         }
02316 
02317         if (!ent->client->pers.cmd.upmove &&
02318                 !ent->client->pers.cmd.forwardmove &&
02319                 !ent->client->pers.cmd.rightmove &&
02320                 !(ent->client->pers.cmd.buttons & BUTTON_GESTURE) &&
02321                 !(ent->client->pers.cmd.buttons & BUTTON_FORCEGRIP) &&
02322                 !(ent->client->pers.cmd.buttons & BUTTON_ALT_ATTACK) &&
02323                 !(ent->client->pers.cmd.buttons & BUTTON_FORCEPOWER) &&
02324                 !(ent->client->pers.cmd.buttons & BUTTON_FORCE_LIGHTNING) &&
02325                 !(ent->client->pers.cmd.buttons & BUTTON_FORCE_DRAIN) &&
02326                 !(ent->client->pers.cmd.buttons & BUTTON_ATTACK))
02327         {
02328                 return qtrue;
02329         }
02330 
02331         return qfalse;
02332 }
02333 
02334 static GAME_INLINE qboolean G_G2TraceCollide(trace_t *tr, vec3_t lastValidStart, vec3_t lastValidEnd, vec3_t traceMins, vec3_t traceMaxs)
02335 { //Hit the ent with the normal trace, try the collision trace.
02336         G2Trace_t               G2Trace;
02337         gentity_t               *g2Hit;
02338         vec3_t                  angles;
02339         int                             tN = 0;
02340         float                   fRadius = 0;
02341 
02342         if (!d_saberGhoul2Collision.integer)
02343         {
02344                 return qfalse;
02345         }
02346 
02347         if (!g_entities[tr->entityNum].inuse /*||
02348                 (g_entities[tr->entityNum].s.eFlags & EF_DEAD)*/)
02349         { //don't do perpoly on corpses.
02350                 return qfalse;
02351         }
02352 
02353         if (traceMins[0] ||
02354                 traceMins[1] ||
02355                 traceMins[2] ||
02356                 traceMaxs[0] ||
02357                 traceMaxs[1] ||
02358                 traceMaxs[2])
02359         {
02360                 fRadius=(traceMaxs[0]-traceMins[0])/2.0f;
02361         }
02362 
02363         memset (&G2Trace, 0, sizeof(G2Trace));
02364 
02365         while (tN < MAX_G2_COLLISIONS)
02366         {
02367                 G2Trace[tN].mEntityNum = -1;
02368                 tN++;
02369         }
02370         g2Hit = &g_entities[tr->entityNum];
02371 
02372         if (g2Hit && g2Hit->inuse && g2Hit->ghoul2)
02373         {
02374                 vec3_t g2HitOrigin;
02375 
02376                 angles[ROLL] = angles[PITCH] = 0;
02377 
02378                 if (g2Hit->client)
02379                 {
02380                         VectorCopy(g2Hit->client->ps.origin, g2HitOrigin);
02381                         angles[YAW] = g2Hit->client->ps.viewangles[YAW];
02382                 }
02383                 else
02384                 {
02385                         VectorCopy(g2Hit->r.currentOrigin, g2HitOrigin);
02386                         angles[YAW] = g2Hit->r.currentAngles[YAW];
02387                 }
02388 
02389                 if (g_optvehtrace.integer &&
02390                         g2Hit->s.eType == ET_NPC &&
02391                         g2Hit->s.NPC_class == CLASS_VEHICLE &&
02392                         g2Hit->m_pVehicle)
02393                 {
02394                         trap_G2API_CollisionDetectCache ( G2Trace, g2Hit->ghoul2, angles, g2HitOrigin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, g_g2TraceLod.integer, fRadius );
02395                 }
02396                 else
02397                 {
02398                         trap_G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2HitOrigin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, g_g2TraceLod.integer, fRadius );
02399                 }
02400 
02401                 if (G2Trace[0].mEntityNum != g2Hit->s.number)
02402                 {
02403                         tr->fraction = 1.0f;
02404                         tr->entityNum = ENTITYNUM_NONE;
02405                         tr->startsolid = 0;
02406                         tr->allsolid = 0;
02407                         return qfalse;
02408                 }
02409                 else
02410                 { //The ghoul2 trace result matches, so copy the collision position into the trace endpos and send it back.
02411                         VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos);
02412                         VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal);
02413 
02414                         if (g2Hit->client)
02415                         {
02416                                 g2Hit->client->g2LastSurfaceHit = G2Trace[0].mSurfaceIndex;
02417                                 g2Hit->client->g2LastSurfaceTime = level.time;
02418                         }
02419                         return qtrue;
02420                 }
02421         }
02422 
02423         return qfalse;
02424 }
02425 
02426 static GAME_INLINE qboolean G_SaberInBackAttack(int move)
02427 {
02428         switch (move)
02429         {
02430         case LS_A_BACK:
02431         case LS_A_BACK_CR:
02432         case LS_A_BACKSTAB:
02433                 return qtrue;
02434         }
02435 
02436         return qfalse;
02437 }
02438 
02439 qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
02440 qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage);
02441 qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
02442 
02443 
02444 typedef struct saberFace_s
02445 {
02446         vec3_t v1;
02447         vec3_t v2;
02448         vec3_t v3;
02449 } saberFace_t;
02450 
02451 //build faces around blade for collision checking -rww
02452 static GAME_INLINE void G_BuildSaberFaces(vec3_t base, vec3_t tip, float radius, vec3_t fwd,
02453                                                                                   vec3_t right, int *fNum, saberFace_t **fList)
02454 {
02455         static saberFace_t faces[12];
02456         int i = 0;
02457         float *d1 = NULL, *d2 = NULL;
02458         vec3_t invFwd;
02459         vec3_t invRight;
02460 
02461         VectorCopy(fwd, invFwd);
02462         VectorInverse(invFwd);
02463         VectorCopy(right, invRight);
02464         VectorInverse(invRight);
02465 
02466         while (i < 8)
02467         {
02468                 //yeah, this part is kind of a hack, but eh
02469                 if (i < 2)
02470                 { //"left" surface
02471                         d1 = &fwd[0];
02472                         d2 = &invRight[0];
02473                 }
02474                 else if (i < 4)
02475                 { //"right" surface
02476                         d1 = &fwd[0];
02477                         d2 = &right[0];
02478                 }
02479                 else if (i < 6)
02480                 { //"front" surface
02481                         d1 = &right[0];
02482                         d2 = &fwd[0];
02483                 }
02484                 else if (i < 8)
02485                 { //"back" surface
02486                         d1 = &right[0];
02487                         d2 = &invFwd[0];
02488                 }
02489 
02490                 //first triangle for this surface
02491                 VectorMA(base, radius/2.0f, d1, faces[i].v1);
02492                 VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1);
02493 
02494                 VectorMA(tip, radius/2.0f, d1, faces[i].v2);
02495                 VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2);
02496 
02497                 VectorMA(tip, -radius/2.0f, d1, faces[i].v3);
02498                 VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3);
02499 
02500                 i++;
02501 
02502                 //second triangle for this surface
02503                 VectorMA(tip, -radius/2.0f, d1, faces[i].v1);
02504                 VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1);
02505 
02506                 VectorMA(base, radius/2.0f, d1, faces[i].v2);
02507                 VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2);
02508 
02509                 VectorMA(base, -radius/2.0f, d1, faces[i].v3);
02510                 VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3);
02511 
02512                 i++;
02513         }
02514 
02515         //top surface
02516         //face 1
02517         VectorMA(tip, radius/2.0f, fwd, faces[i].v1);
02518         VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1);
02519 
02520         VectorMA(tip, radius/2.0f, fwd, faces[i].v2);
02521         VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2);
02522 
02523         VectorMA(tip, -radius/2.0f, fwd, faces[i].v3);
02524         VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3);
02525 
02526         i++;
02527 
02528         //face 2
02529         VectorMA(tip, radius/2.0f, fwd, faces[i].v1);
02530         VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1);
02531 
02532         VectorMA(tip, -radius/2.0f, fwd, faces[i].v2);
02533         VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2);
02534 
02535         VectorMA(tip, -radius/2.0f, fwd, faces[i].v3);
02536         VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3);
02537 
02538         i++;
02539 
02540         //bottom surface
02541         //face 1
02542         VectorMA(base, radius/2.0f, fwd, faces[i].v1);
02543         VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1);
02544 
02545         VectorMA(base, radius/2.0f, fwd, faces[i].v2);
02546         VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2);
02547 
02548         VectorMA(base, -radius/2.0f, fwd, faces[i].v3);
02549         VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3);
02550 
02551         i++;
02552 
02553         //face 2
02554         VectorMA(base, radius/2.0f, fwd, faces[i].v1);
02555         VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1);
02556 
02557         VectorMA(base, -radius/2.0f, fwd, faces[i].v2);
02558         VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2);
02559 
02560         VectorMA(base, -radius/2.0f, fwd, faces[i].v3);
02561         VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3);
02562 
02563         i++;
02564 
02565         //yeah.. always going to be 12 I suppose.
02566         *fNum = i;
02567         *fList = &faces[0];
02568 }
02569 
02570 //collision utility function -rww
02571 static GAME_INLINE void G_SabCol_CalcPlaneEq(vec3_t x, vec3_t y, vec3_t z, float *planeEq)
02572 {
02573         planeEq[0] = x[1]*(y[2]-z[2]) + y[1]*(z[2]-x[2]) + z[1]*(x[2]-y[2]);
02574         planeEq[1] = x[2]*(y[0]-z[0]) + y[2]*(z[0]-x[0]) + z[2]*(x[0]-y[0]);
02575         planeEq[2] = x[0]*(y[1]-z[1]) + y[0]*(z[1]-x[1]) + z[0]*(x[1]-y[1]);
02576         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]) );
02577 }
02578 
02579 //collision utility function -rww
02580 static GAME_INLINE int G_SabCol_PointRelativeToPlane(vec3_t pos, float *side, float *planeEq)
02581 {
02582         *side = planeEq[0]*pos[0] + planeEq[1]*pos[1] + planeEq[2]*pos[2] + planeEq[3];
02583 
02584         if (*side > 0.0f)
02585         {
02586                 return 1;
02587         }
02588         else if (*side < 0.0f)
02589         {
02590                 return -1;
02591         }
02592 
02593         return 0;
02594 }
02595 
02596 //do actual collision check using generated saber "faces"
02597 static GAME_INLINE qboolean G_SaberFaceCollisionCheck(int fNum, saberFace_t *fList, vec3_t atkStart,
02598                                                                                          vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint)
02599 {
02600         static float planeEq[4];
02601         static float side, side2, dist;
02602         static vec3_t dir;
02603         static vec3_t point;
02604         int i = 0;
02605 
02606         if (VectorCompare(atkMins, vec3_origin) && VectorCompare(atkMaxs, vec3_origin))
02607         {
02608                 VectorSet(atkMins, -1.0f, -1.0f, -1.0f);
02609                 VectorSet(atkMaxs, 1.0f, 1.0f, 1.0f);
02610         }
02611 
02612         VectorSubtract(atkEnd, atkStart, dir);
02613 
02614         while (i < fNum)
02615         {
02616                 G_SabCol_CalcPlaneEq(fList->v1, fList->v2, fList->v3, planeEq);
02617 
02618                 if (G_SabCol_PointRelativeToPlane(atkStart, &side, planeEq) !=
02619                         G_SabCol_PointRelativeToPlane(atkEnd, &side2, planeEq))
02620                 { //start/end points intersect with the plane
02621                         static vec3_t extruded;
02622                         static vec3_t minPoint, maxPoint;
02623                         static vec3_t planeNormal;
02624                         static int facing;
02625 
02626                         VectorCopy(&planeEq[0], planeNormal);
02627                         side2 = planeNormal[0]*dir[0] + planeNormal[1]*dir[1] + planeNormal[2]*dir[2];
02628 
02629                         dist = side/side2;
02630                         VectorMA(atkStart, -dist, dir, point);
02631 
02632                         VectorAdd(point, atkMins, minPoint);
02633                         VectorAdd(point, atkMaxs, maxPoint);
02634 
02635                         //point is now the point at which we intersect on the plane.
02636                         //see if that point is within the edges of the face.
02637             VectorMA(fList->v1, -2.0f, planeNormal, extruded);
02638                         G_SabCol_CalcPlaneEq(fList->v1, fList->v2, extruded, planeEq);
02639                         facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
02640 
02641                         if (facing < 0)
02642                         { //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
02643                                 facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
02644                                 if (facing < 0)
02645                                 {
02646                                         facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
02647                                 }
02648                         }
02649 
02650                         if (facing >= 0)
02651                         { //first edge is facing...
02652                                 VectorMA(fList->v2, -2.0f, planeNormal, extruded);
02653                                 G_SabCol_CalcPlaneEq(fList->v2, fList->v3, extruded, planeEq);
02654                                 facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
02655 
02656                                 if (facing < 0)
02657                                 { //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
02658                                         facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
02659                                         if (facing < 0)
02660                                         {
02661                                                 facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
02662                                         }
02663                                 }
02664 
02665                                 if (facing >= 0)
02666                                 { //second edge is facing...
02667                                         VectorMA(fList->v3, -2.0f, planeNormal, extruded);
02668                                         G_SabCol_CalcPlaneEq(fList->v3, fList->v1, extruded, planeEq);
02669                                         facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
02670 
02671                                         if (facing < 0)
02672                                         { //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
02673                                                 facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
02674                                                 if (facing < 0)
02675                                                 {
02676                                                         facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
02677                                                 }
02678                                         }
02679 
02680                                         if (facing >= 0)
02681                                         { //third edge is facing.. success
02682                                                 VectorCopy(point, impactPoint);
02683                                                 return qtrue;
02684                                         }
02685                                 }
02686                         }
02687                 }
02688 
02689                 i++;
02690                 fList++;
02691         }
02692         
02693         //did not hit anything
02694         return qfalse;
02695 }
02696 
02697 //check for collision of 2 blades -rww
02698 static GAME_INLINE qboolean G_SaberCollide(gentity_t *atk, gentity_t *def, vec3_t atkStart,
02699                                                 vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint)
02700 {
02701         static int i, j;
02702 
02703         if (!g_saberBladeFaces.integer)
02704         { //detailed check not enabled
02705                 return qtrue;
02706         }
02707 
02708         if (!atk->inuse || !atk->client || !def->inuse || !def->client)
02709         { //must have 2 clients and a valid saber entity
02710                 return qfalse;
02711         }
02712 
02713         i = 0;
02714         while (i < MAX_SABERS)
02715         {
02716                 j = 0;
02717                 if (def->client->saber[i].model && def->client->saber[i].model[0])
02718                 { //valid saber on the defender
02719                         bladeInfo_t *blade;
02720                         vec3_t v, fwd, right, base, tip;
02721                         int fNum;
02722                         saberFace_t *fList;
02723 
02724                         //go through each blade on the defender's sabers
02725                         while (j < def->client->saber[i].numBlades)
02726                         {
02727                                 blade = &def->client->saber[i].blade[j];
02728 
02729                                 if ((level.time-blade->storageTime) < 200)
02730                                 { //recently updated
02731                                         //first get base and tip of blade
02732                                         VectorCopy(blade->muzzlePoint, base);
02733                                         VectorMA(base, blade->lengthMax, blade->muzzleDir, tip);
02734 
02735                                         //Now get relative angles between the points
02736                                         VectorSubtract(tip, base, v);
02737                                         vectoangles(v, v);
02738                                         AngleVectors(v, NULL, right, fwd);
02739 
02740                                         //now build collision faces for this blade
02741                                         G_BuildSaberFaces(base, tip, blade->radius*3.0f, fwd, right, &fNum, &fList);
02742                                         if (fNum > 0)
02743                                         {
02744 #if 0
02745                                                 if (atk->s.number == 0)
02746                                                 {
02747                                                         int x = 0;
02748                                                         saberFace_t *l = fList;
02749                                                         while (x < fNum)
02750                                                         {
02751                                                                 G_TestLine(fList->v1, fList->v2, 0x0000ff, 100);
02752                                                                 G_TestLine(fList->v2, fList->v3, 0x0000ff, 100);
02753                                                                 G_TestLine(fList->v3, fList->v1, 0x0000ff, 100);
02754 
02755                                                                 fList++;
02756                                                                 x++;
02757                                                         }
02758                                                         fList = l;
02759                                                 }
02760 #endif
02761 
02762                                                 if (G_SaberFaceCollisionCheck(fNum, fList, atkStart, atkEnd, atkMins, atkMaxs, impactPoint))
02763                                                 { //collided
02764                                                         return qtrue;
02765                                                 }
02766                                         }
02767                                 }
02768                                 j++;
02769                         }
02770                 }
02771                 i++;
02772         }
02773 
02774         return qfalse;
02775 }
02776 
02777 float WP_SaberBladeLength( saberInfo_t *saber )
02778 {//return largest length
02779         int     i;
02780         float len = 0.0f;
02781         for ( i = 0; i < saber->numBlades; i++ )
02782         {
02783                 if ( saber->blade[i].lengthMax > len )
02784                 {
02785                         len = saber->blade[i].lengthMax; 
02786                 }
02787         }
02788         return len;
02789 }
02790 
02791 float WP_SaberLength( gentity_t *ent )
02792 {//return largest length
02793         if ( !ent || !ent->client )
02794         {
02795                 return 0.0f;
02796         }
02797         else
02798         {
02799                 int i;
02800                 float len, bestLen = 0.0f;
02801                 for ( i = 0; i < MAX_SABERS; i++ )
02802                 {
02803                         len = WP_SaberBladeLength( &ent->client->saber[i] );
02804                         if ( len > bestLen )
02805                         {
02806                                 bestLen = len;
02807                         }
02808                 }
02809                 return bestLen;
02810         }
02811 }
02812 int WPDEBUG_SaberColor( saber_colors_t saberColor )
02813 {
02814         switch( (int)(saberColor) )
02815         {
02816                 case SABER_RED:
02817                         return 0x000000ff;
02818                         break;
02819                 case SABER_ORANGE:
02820                         return 0x000088ff;
02821                         break;
02822                 case SABER_YELLOW:
02823                         return 0x0000ffff;
02824                         break;
02825                 case SABER_GREEN:
02826                         return 0x0000ff00;
02827                         break;
02828                 case SABER_BLUE:
02829                         return 0x00ff0000;
02830                         break;
02831                 case SABER_PURPLE:
02832                         return 0x00ff00ff;
02833                         break;
02834                 default:
02835                         return 0x00ffffff;//white
02836                         break;
02837         }
02838 }
02839 /*
02840 WP_SabersIntersect
02841 
02842 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
02843 
02844 FIXME: subdivide the arc into a consistant increment
02845 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)?
02846 */
02847 extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2);
02848 #define SABER_EXTRAPOLATE_DIST 16.0f
02849 qboolean WP_SabersIntersect( gentity_t *ent1, int ent1SaberNum, int ent1BladeNum, gentity_t *ent2, qboolean checkDir )
02850 {
02851         vec3_t  saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
02852         vec3_t  saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
02853         int             ent2SaberNum = 0, ent2BladeNum = 0;
02854         vec3_t  dir;
02855 
02856         if ( !ent1 || !ent2 )
02857         {
02858                 return qfalse;
02859         }
02860         if ( !ent1->client || !ent2->client )
02861         {
02862                 return qfalse;
02863         }
02864         if ( BG_SabersOff( &ent1->client->ps )
02865                 || BG_SabersOff( &ent2->client->ps ) )
02866         {
02867                 return qfalse;
02868         }
02869 
02870         for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ )
02871         {
02872                 if ( ent2->client->saber[ent2SaberNum].type != SABER_NONE )
02873                 {
02874                         for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->saber[ent2SaberNum].numBlades; ent2BladeNum++ )
02875                         {
02876                                 if ( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax > 0 )
02877                                 {//valid saber and this blade is on
02878                                         //if ( ent1->client->saberInFlight )
02879                                         {
02880                                                 VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 );
02881                                                 VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 );
02882 
02883                                                 VectorSubtract( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir );
02884                                                 VectorNormalize( dir );
02885                                                 VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
02886 
02887                                                 VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 );
02888                                                 VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 );
02889 
02890                                                 VectorSubtract( saberTipNext1, saberTip1, dir );
02891                                                 VectorNormalize( dir );
02892                                                 VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
02893                                         }
02894                                         /*
02895                                         else
02896                                         {
02897                                                 VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 );
02898                                                 VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 );
02899                                                 VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 );
02900                                                 VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 );
02901                                         }
02902                                         */
02903 
02904                                         //if ( ent2->client->saberInFlight )
02905                                         {
02906                                                 VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 );
02907                                                 VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 );
02908 
02909                                                 VectorSubtract( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir );
02910                                                 VectorNormalize( dir );
02911                                                 VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
02912 
02913                                                 VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 );
02914                                                 VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 );
02915 
02916                                                 VectorSubtract( saberTipNext2, saberTip2, dir );
02917                                                 VectorNormalize( dir );
02918                                                 VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
02919                                         }
02920                                         /*
02921                                         else
02922                                         {
02923                                                 VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 );
02924                                                 VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 );
02925                                                 VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 );
02926                                                 VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirNext, saberTipNext2 );
02927                                         }
02928                                         */
02929                                         if ( checkDir )
02930                                         {//check the direction of the two swings to make sure the sabers are swinging towards each other
02931                                                 vec3_t saberDir1, saberDir2;
02932                                                 float dot = 0.0f;
02933 
02934                                                 VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
02935                                                 VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
02936                                                 VectorNormalize( saberDir1 );
02937                                                 VectorNormalize( saberDir2 );
02938                                                 if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
02939                                                 {//sabers moving in same dir, probably didn't actually hit
02940                                                         continue;
02941                                                 }
02942                                                 //now check orientation of sabers, make sure they're not parallel or close to it
02943                                                 dot = DotProduct( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir );
02944                                                 if ( dot > 0.9f || dot < -0.9f )
02945                                                 {//too parallel to really block effectively?
02946                                                         continue;
02947                                                 }
02948                                         }
02949 
02950 #ifdef DEBUG_SABER_BOX
02951                                         if ( g_saberDebugBox.integer == 2 || g_saberDebugBox.integer == 4 )
02952                                         {
02953                                                 G_TestLine(saberBase1, saberTip1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
02954                                                 G_TestLine(saberTip1, saberTipNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
02955                                                 G_TestLine(saberTipNext1, saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
02956 
02957                                                 G_TestLine(saberBase2, saberTip2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
02958                                                 G_TestLine(saberTip2, saberTipNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
02959                                                 G_TestLine(saberTipNext2, saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
02960                                         }
02961 #endif
02962                                         if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
02963                                         {
02964                                                 return qtrue;
02965                                         }
02966                                         if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
02967                                         {
02968                                                 return qtrue;
02969                                         }
02970                                         if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
02971                                         {
02972                                                 return qtrue;
02973                                         }
02974                                         if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
02975                                         {
02976                                                 return qtrue;
02977                                         }
02978                                 }
02979                         }
02980                 }
02981         }
02982         return qfalse;
02983 }
02984 
02985 static GAME_INLINE int G_PowerLevelForSaberAnim( gentity_t *ent, int saberNum, qboolean mySaberHit )
02986 {
02987         if ( !ent || !ent->client || saberNum >= MAX_SABERS )
02988         {
02989                 return FORCE_LEVEL_0;
02990         }
02991         else
02992         {
02993                 int anim = ent->client->ps.torsoAnim;
02994                 int     animTimer = ent->client->ps.torsoTimer;
02995                 int     animTimeElapsed = BG_AnimLength( ent->localAnimIndex, (animNumber_t)anim ) - animTimer;
02996                 saberInfo_t *saber = &ent->client->saber[saberNum];
02997                 if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_D1_B____ )
02998                 {
02999                         //FIXME: these two need their own style
03000                         if ( saber->type == SABER_LANCE )
03001                         {
03002                                 return FORCE_LEVEL_4;
03003                         }
03004                         else if ( saber->type == SABER_TRIDENT )
03005                         {
03006                                 return FORCE_LEVEL_3;
03007                         }
03008                         return FORCE_LEVEL_1;
03009                 }
03010                 if ( anim >= BOTH_A2_T__B_ && anim <= BOTH_D2_B____ )
03011                 {
03012                         return FORCE_LEVEL_2;
03013                 }
03014                 if ( anim >= BOTH_A3_T__B_ && anim <= BOTH_D3_B____ )
03015                 {
03016                         return FORCE_LEVEL_3;
03017                 }
03018                 if ( anim >= BOTH_A4_T__B_ && anim <= BOTH_D4_B____ )
03019                 {//desann
03020                         return FORCE_LEVEL_4;
03021                 }
03022                 if ( anim >= BOTH_A5_T__B_ && anim <= BOTH_D5_B____ )
03023                 {//tavion
03024                         return FORCE_LEVEL_2;
03025                 }
03026                 if ( anim >= BOTH_A6_T__B_ && anim <= BOTH_D6_B____ )
03027                 {//dual
03028                         return FORCE_LEVEL_2;
03029                 }
03030                 if ( anim >= BOTH_A7_T__B_ && anim <= BOTH_D7_B____ )
03031                 {//staff
03032                         return FORCE_LEVEL_2;
03033                 }
03034                 if ( anim >= BOTH_P1_S1_T_ && anim <= BOTH_H1_S1_BR )
03035                 {//parries, knockaways and broken parries
03036                         return FORCE_LEVEL_1;//FIXME: saberAnimLevel?
03037                 }
03038                 switch ( anim )
03039                 {
03040                 case BOTH_A2_STABBACK1:
03041                         if ( mySaberHit )
03042                         {//someone else hit my saber, not asking for damage level, but defense strength
03043                                 return FORCE_LEVEL_1;
03044                         }
03045                         if ( animTimer < 450 )
03046                         {//end of anim
03047                                 return FORCE_LEVEL_0;
03048                         }
03049                         else if ( animTimeElapsed < 400 )
03050                         {//beginning of anim
03051                                 return FORCE_LEVEL_0;
03052                         }
03053                         return FORCE_LEVEL_3;
03054                         break;
03055                 case BOTH_ATTACK_BACK:
03056                         if ( animTimer < 500 )
03057                         {//end of anim
03058                                 return FORCE_LEVEL_0;
03059                         }
03060                         return FORCE_LEVEL_3;
03061                         break;
03062                 case BOTH_CROUCHATTACKBACK1:
03063                         if ( animTimer < 800 )
03064                         {//end of anim
03065                                 return FORCE_LEVEL_0;
03066                         }
03067                         return FORCE_LEVEL_3;
03068                         break;
03069                 case BOTH_BUTTERFLY_LEFT:
03070                 case BOTH_BUTTERFLY_RIGHT:
03071                 case BOTH_BUTTERFLY_FL1:
03072                 case BOTH_BUTTERFLY_FR1:
03073                         //FIXME: break up?
03074                         return FORCE_LEVEL_3;
03075                         break;
03076                 case BOTH_FJSS_TR_BL:
03077                 case BOTH_FJSS_TL_BR:
03078                         //FIXME: break up?
03079                         return FORCE_LEVEL_3;
03080                         break;
03081                 case BOTH_K1_S1_T_:     //# knockaway saber top
03082                 case BOTH_K1_S1_TR:     //# knockaway saber top right
03083                 case BOTH_K1_S1_TL:     //# knockaway saber top left
03084                 case BOTH_K1_S1_BL:     //# knockaway saber bottom left
03085                 case BOTH_K1_S1_B_:     //# knockaway saber bottom
03086                 case BOTH_K1_S1_BR:     //# knockaway saber bottom right
03087                         //FIXME: break up?
03088                         return FORCE_LEVEL_3;
03089                         break;
03090                 case BOTH_LUNGE2_B__T_:
03091                         if ( mySaberHit )
03092                         {//someone else hit my saber, not asking for damage level, but defense strength
03093                                 return FORCE_LEVEL_1;
03094                         }
03095                         if ( animTimer < 400 )
03096                         {//end of anim
03097                                 return FORCE_LEVEL_0;
03098                         }
03099                         else if ( animTimeElapsed < 150 )
03100                         {//beginning of anim
03101                                 return FORCE_LEVEL_0;
03102                         }
03103                         return FORCE_LEVEL_3;
03104                         break;
03105                 case BOTH_FORCELEAP2_T__B_:
03106                         if ( animTimer < 400 )
03107                         {//end of anim
03108                                 return FORCE_LEVEL_0;
03109                         }
03110                         else if ( animTimeElapsed < 550 )
03111                         {//beginning of anim
03112                                 return FORCE_LEVEL_0;
03113                         }
03114                         return FORCE_LEVEL_3;
03115                         break;
03116                 case BOTH_VS_ATR_S:
03117                 case BOTH_VS_ATL_S:
03118                 case BOTH_VT_ATR_S:
03119                 case BOTH_VT_ATL_S:
03120                         return FORCE_LEVEL_3;//???
03121                         break;
03122                 case BOTH_JUMPFLIPSLASHDOWN1:
03123                         if ( animTimer <= 1000 )
03124                         {//end of anim
03125                                 return FORCE_LEVEL_0;
03126                         }
03127                         else if ( animTimeElapsed < 600 )
03128                         {//beginning of anim
03129                                 return FORCE_LEVEL_0;
03130                         }
03131                         return FORCE_LEVEL_3;
03132                         break;
03133                 case BOTH_JUMPFLIPSTABDOWN:
03134                         if ( animTimer <= 1300 )
03135                         {//end of anim
03136                                 return FORCE_LEVEL_0;
03137                         }
03138                         else if ( animTimeElapsed <= 300 )
03139                         {//beginning of anim
03140                                 return FORCE_LEVEL_0;
03141                         }
03142                         return FORCE_LEVEL_3;
03143                         break;
03144                 case BOTH_JUMPATTACK6:
03145                         /*
03146                         if (pm->ps)
03147                         {
03148                                 if ( ( pm->ps->legsAnimTimer >= 1450
03149                                                 && BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 400 ) 
03150                                         ||(pm->ps->legsAnimTimer >= 400
03151                                                 && BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 1100 ) )
03152                                 {//pretty much sideways
03153                                         return FORCE_LEVEL_3;
03154                                 }
03155                         }
03156                         */
03157                         if ( ( animTimer >= 1450
03158                                         && animTimeElapsed >= 400 )
03159                                 ||(animTimer >= 400
03160                                         && animTimeElapsed >= 1100 ) )
03161                         {//pretty much sideways
03162                                 return FORCE_LEVEL_3;
03163                         }
03164                         return FORCE_LEVEL_0;
03165                         break;
03166                 case BOTH_JUMPATTACK7:
03167                         if ( animTimer <= 1200 )
03168                         {//end of anim
03169                                 return FORCE_LEVEL_0;
03170                         }
03171                         else if ( animTimeElapsed < 200 )
03172                         {//beginning of anim
03173                                 return FORCE_LEVEL_0;
03174                         }
03175                         return FORCE_LEVEL_3;
03176                         break;
03177                 case BOTH_SPINATTACK6:
03178                         if ( animTimeElapsed <= 200 )
03179                         {//beginning of anim
03180                                 return FORCE_LEVEL_0;
03181                         }
03182                         return FORCE_LEVEL_2;//FORCE_LEVEL_3;
03183                         break;
03184                 case BOTH_SPINATTACK7:
03185                         if ( animTimer <= 500 )
03186                         {//end of anim
03187                                 return FORCE_LEVEL_0;
03188                         }
03189                         else if ( animTimeElapsed < 500 )
03190                         {//beginning of anim
03191                                 return FORCE_LEVEL_0;
03192                         }
03193                         return FORCE_LEVEL_2;//FORCE_LEVEL_3;
03194                         break;
03195                 case BOTH_FORCELONGLEAP_ATTACK:
03196                         if ( animTimeElapsed <= 200 )
03197                         {//1st four frames of anim
03198                                 return FORCE_LEVEL_3;
03199                         }
03200                         break;
03201                 /*
03202                 case BOTH_A7_KICK_F://these kicks attack, too
03203                 case BOTH_A7_KICK_B:
03204                 case BOTH_A7_KICK_R:
03205                 case BOTH_A7_KICK_L:
03206                         //FIXME: break up
03207                         return FORCE_LEVEL_3;
03208                         break;
03209                 */
03210                 case BOTH_STABDOWN:
03211                         if ( animTimer <= 900 )
03212                         {//end of anim
03213                                 return FORCE_LEVEL_3;
03214                         }
03215                         break;
03216                 case BOTH_STABDOWN_STAFF:
03217                         if ( animTimer <= 850 )
03218                         {//end of anim
03219                                 return FORCE_LEVEL_3;
03220                         }
03221                         break;
03222                 case BOTH_STABDOWN_DUAL:
03223                         if ( animTimer <= 900 )
03224                         {//end of anim
03225                                 return FORCE_LEVEL_3;
03226                         }
03227                         break;
03228                 case BOTH_A6_SABERPROTECT:
03229                         if ( animTimer < 650 )
03230                         {//end of anim
03231                                 return FORCE_LEVEL_0;
03232                         }
03233                         else if ( animTimeElapsed < 250 )
03234                         {//start of anim
03235                                 return FORCE_LEVEL_0;
03236                         }
03237                         return FORCE_LEVEL_3;
03238                         break;
03239                 case BOTH_A7_SOULCAL:
03240                         if ( animTimer < 650 )
03241                         {//end of anim
03242                                 return FORCE_LEVEL_0;
03243                         }
03244                         else if ( animTimeElapsed < 600 )
03245                         {//beginning of anim
03246                                 return FORCE_LEVEL_0;
03247                         }
03248                         return FORCE_LEVEL_3;
03249                         break;
03250                 case BOTH_A1_SPECIAL:
03251                         if ( animTimer < 600 )
03252                         {//end of anim
03253                                 return FORCE_LEVEL_0;
03254                         }
03255                         else if ( animTimeElapsed < 200 )
03256                         {//beginning of anim
03257                                 return FORCE_LEVEL_0;
03258                         }
03259                         return FORCE_LEVEL_3;
03260                         break;
03261                 case BOTH_A2_SPECIAL:
03262                         if ( animTimer < 300 )
03263                         {//end of anim
03264                                 return FORCE_LEVEL_0;
03265                         }
03266                         else if ( animTimeElapsed < 200 )
03267                         {//beginning of anim
03268                                 return FORCE_LEVEL_0;
03269                         }
03270                         return FORCE_LEVEL_3;
03271                         break;
03272                 case BOTH_A3_SPECIAL:
03273                         if ( animTimer < 700 )
03274                         {//end of anim
03275                                 return FORCE_LEVEL_0;
03276                         }
03277                         else if ( animTimeElapsed < 200 )
03278                         {//beginning of anim
03279                                 return FORCE_LEVEL_0;
03280                         }
03281                         return FORCE_LEVEL_3;
03282                         break;
03283                 case BOTH_FLIP_ATTACK7:
03284                         return FORCE_LEVEL_3;
03285                         break;
03286                 case BOTH_PULL_IMPALE_STAB:
03287                         if ( mySaberHit )
03288                         {//someone else hit my saber, not asking for damage level, but defense strength
03289                                 return FORCE_LEVEL_1;
03290                         }
03291                         if ( animTimer < 1000 )
03292                         {//end of anim
03293                                 return FORCE_LEVEL_0;
03294                         }
03295                         return FORCE_LEVEL_3;
03296                         break;
03297                 case BOTH_PULL_IMPALE_SWING:
03298                         if ( animTimer < 500 )
03299                         {//end of anim
03300                                 return FORCE_LEVEL_0;
03301                         }
03302                         else if ( animTimeElapsed < 650 )
03303                         {//beginning of anim
03304                                 return FORCE_LEVEL_0;
03305                         }
03306                         return FORCE_LEVEL_3;
03307                         break;
03308                 case BOTH_ALORA_SPIN_SLASH:
03309                         if ( animTimer < 900 )
03310                         {//end of anim
03311                                 return FORCE_LEVEL_0;
03312                         }
03313                         else if ( animTimeElapsed < 250 )
03314                         {//beginning of anim
03315                                 return FORCE_LEVEL_0;
03316                         }
03317                         return FORCE_LEVEL_3;
03318                         break;
03319                 case BOTH_A6_FB:
03320                         if ( mySaberHit )
03321                         {//someone else hit my saber, not asking for damage level, but defense strength
03322                                 return FORCE_LEVEL_1;
03323                         }
03324                         if ( animTimer < 250 )
03325                         {//end of anim
03326                                 return FORCE_LEVEL_0;
03327                         }
03328                         else if ( animTimeElapsed < 250 )
03329                         {//beginning of anim
03330                                 return FORCE_LEVEL_0;
03331                         }
03332                         return FORCE_LEVEL_3;
03333                         break;
03334                 case BOTH_A6_LR:        
03335                         if ( mySaberHit )
03336                         {//someone else hit my saber, not asking for damage level, but defense strength
03337                                 return FORCE_LEVEL_1;
03338                         }
03339                         if ( animTimer < 250 )
03340                         {//end of anim
03341                                 return FORCE_LEVEL_0;
03342                         }
03343                         else if ( animTimeElapsed < 250 )
03344                         {//beginning of anim
03345                                 return FORCE_LEVEL_0;
03346                         }
03347                         return FORCE_LEVEL_3;
03348                         break;
03349                 case BOTH_A7_HILT:
03350                         return FORCE_LEVEL_0;
03351                         break;
03352         //===SABERLOCK SUPERBREAKS START===========================================================================
03353                 case BOTH_LK_S_DL_T_SB_1_W:
03354                         if ( animTimer < 700 )
03355                         {//end of anim
03356                                 return FORCE_LEVEL_0;
03357                         }
03358                         return FORCE_LEVEL_5;
03359                         break;
03360                 case BOTH_LK_S_ST_S_SB_1_W:
03361                         if ( animTimer < 300 )
03362                         {//end of anim
03363                                 return FORCE_LEVEL_0;
03364                         }
03365                         return FORCE_LEVEL_5;
03366                         break;
03367                 case BOTH_LK_S_DL_S_SB_1_W:
03368                 case BOTH_LK_S_S_S_SB_1_W:
03369                         if ( animTimer < 700 )
03370                         {//end of anim
03371                                 return FORCE_LEVEL_0;
03372                         }
03373                         else if ( animTimeElapsed < 400 )
03374                         {//beginning of anim
03375                                 return FORCE_LEVEL_0;
03376                         }
03377                         return FORCE_LEVEL_5;
03378                         break;
03379                 case BOTH_LK_S_ST_T_SB_1_W:
03380                 case BOTH_LK_S_S_T_SB_1_W:
03381                         if ( animTimer < 150 )
03382                         {//end of anim
03383                                 return FORCE_LEVEL_0;
03384                         }
03385                         else if ( animTimeElapsed < 400 )
03386                         {//beginning of anim
03387                                 return FORCE_LEVEL_0;
03388                         }
03389                         return FORCE_LEVEL_5;
03390                         break;
03391                 case BOTH_LK_DL_DL_T_SB_1_W:
03392                         return FORCE_LEVEL_5;
03393                         break;
03394                 case BOTH_LK_DL_DL_S_SB_1_W:
03395                 case BOTH_LK_DL_ST_S_SB_1_W:
03396                         if ( animTimeElapsed < 1000 )
03397                         {//beginning of anim
03398                                 return FORCE_LEVEL_0;
03399                         }
03400                         return FORCE_LEVEL_5;
03401                         break;
03402                 case BOTH_LK_DL_ST_T_SB_1_W:
03403                         if ( animTimer < 950 )
03404                         {//end of anim
03405                                 return FORCE_LEVEL_0;
03406                         }
03407                         else if ( animTimeElapsed < 650 )
03408                         {//beginning of anim
03409                                 return FORCE_LEVEL_0;
03410                         }
03411                         return FORCE_LEVEL_5;
03412                         break;
03413                 case BOTH_LK_DL_S_S_SB_1_W:
03414                         if ( saberNum != 0 )
03415                         {//only right hand saber does damage in this suberbreak
03416                                 return FORCE_LEVEL_0;
03417                         }
03418                         if ( animTimer < 900 )
03419                         {//end of anim
03420                                 return FORCE_LEVEL_0;
03421                         }
03422                         else if ( animTimeElapsed < 450 )
03423                         {//beginning of anim
03424                                 return FORCE_LEVEL_0;
03425                         }
03426                         return FORCE_LEVEL_5;
03427                         break;
03428                 case BOTH_LK_DL_S_T_SB_1_W:
03429                         if ( saberNum != 0 )
03430                         {//only right hand saber does damage in this suberbreak
03431                                 return FORCE_LEVEL_0;
03432                         }
03433                         if ( animTimer < 250 )
03434                         {//end of anim
03435                                 return FORCE_LEVEL_0;
03436                         }
03437                         else if ( animTimeElapsed < 150 )
03438                         {//beginning of anim
03439                                 return FORCE_LEVEL_0;
03440                         }
03441                         return FORCE_LEVEL_5;
03442                         break;
03443                 case BOTH_LK_ST_DL_S_SB_1_W:
03444                         return FORCE_LEVEL_5;
03445                         break;
03446                 case BOTH_LK_ST_DL_T_SB_1_W:
03447                         //special suberbreak - doesn't kill, just kicks them backwards
03448                         return FORCE_LEVEL_0;
03449                         break;
03450                 case BOTH_LK_ST_ST_S_SB_1_W:
03451                 case BOTH_LK_ST_S_S_SB_1_W:
03452                         if ( animTimer < 800 )
03453                         {//end of anim
03454                                 return FORCE_LEVEL_0;
03455                         }
03456                         else if ( animTimeElapsed < 350 )
03457                         {//beginning of anim
03458                                 return FORCE_LEVEL_0;
03459                         }
03460                         return FORCE_LEVEL_5;
03461                         break;
03462                 case BOTH_LK_ST_ST_T_SB_1_W:
03463                 case BOTH_LK_ST_S_T_SB_1_W:
03464                         return FORCE_LEVEL_5;
03465                         break;
03466         //===SABERLOCK SUPERBREAKS START===========================================================================
03467                 case BOTH_HANG_ATTACK:
03468                         //FIME: break up
03469                         if ( animTimer < 1000 )
03470                         {//end of anim
03471                                 return FORCE_LEVEL_0;
03472                         }
03473                         else if ( animTimeElapsed < 250 )
03474                         {//beginning of anim
03475                                 return FORCE_LEVEL_0;
03476                         }
03477                         else
03478                         {//sweet spot
03479                                 return FORCE_LEVEL_5;
03480                         }
03481                         break;
03482                 case BOTH_ROLL_STAB:
03483                         if ( mySaberHit )
03484                         {//someone else hit my saber, not asking for damage level, but defense strength
03485                                 return FORCE_LEVEL_1;
03486                         }
03487                         if ( animTimeElapsed > 400 )
03488                         {//end of anim
03489                                 return FORCE_LEVEL_0;
03490                         }
03491                         else
03492                         {
03493                                 return FORCE_LEVEL_3;
03494                         }
03495                         break;
03496                 }
03497                 return FORCE_LEVEL_0;
03498         }
03499 }
03500 
03501 #define MAX_SABER_VICTIMS 16
03502 static int              victimEntityNum[MAX_SABER_VICTIMS];
03503 static qboolean victimHitEffectDone[MAX_SABER_VICTIMS];
03504 static float    totalDmg[MAX_SABER_VICTIMS];
03505 static vec3_t   dmgDir[MAX_SABER_VICTIMS];
03506 static vec3_t   dmgSpot[MAX_SABER_VICTIMS];
03507 static qboolean dismemberDmg[MAX_SABER_VICTIMS];
03508 static int saberKnockbackFlags[MAX_SABER_VICTIMS];
03509 static int numVictims = 0;
03510 void WP_SaberClearDamage( void )
03511 {
03512         int ven;
03513         for ( ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
03514         {
03515                 victimEntityNum[ven] = ENTITYNUM_NONE;
03516         }
03517         memset( victimHitEffectDone, 0, sizeof( victimHitEffectDone ) );
03518         memset( totalDmg, 0, sizeof( totalDmg ) );
03519         memset( dmgDir, 0, sizeof( dmgDir ) );
03520         memset( dmgSpot, 0, sizeof( dmgSpot ) );
03521         memset( dismemberDmg, 0, sizeof( dismemberDmg ) );
03522         memset( saberKnockbackFlags, 0, sizeof( saberKnockbackFlags ) );
03523         numVictims = 0;
03524 }
03525 
03526 void WP_SaberDamageAdd( int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot, 
03527                                            int trDmg, qboolean doDismemberment, int knockBackFlags )
03528 {
03529         if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
03530         {
03531                 return;
03532         }
03533         
03534         if ( trDmg )
03535         {//did some damage to something
03536                 int curVictim = 0;
03537                 int i;
03538 
03539                 for ( i = 0; i < numVictims; i++ )
03540                 {
03541                         if ( victimEntityNum[i] == trVictimEntityNum )
03542                         {//already hit this guy before
03543                                 curVictim = i;
03544                                 break;
03545                         }
03546                 }
03547                 if ( i == numVictims )
03548                 {//haven't hit his guy before
03549                         if ( numVictims + 1 >= MAX_SABER_VICTIMS )
03550                         {//can't add another victim at this time
03551                                 return;
03552                         }
03553                         //add a new victim to the list
03554                         curVictim = numVictims;
03555                         victimEntityNum[numVictims++] = trVictimEntityNum;
03556                 }
03557 
03558                 totalDmg[curVictim] += trDmg;
03559                 if ( VectorCompare( dmgDir[curVictim], vec3_origin ) )
03560                 {
03561                         VectorCopy( trDmgDir, dmgDir[curVictim] );
03562                 }
03563                 if ( VectorCompare( dmgSpot[curVictim], vec3_origin ) )
03564                 {
03565                         VectorCopy( trDmgSpot, dmgSpot[curVictim] );
03566                 }
03567                 if ( doDismemberment )
03568                 {
03569                         dismemberDmg[curVictim] = qtrue;
03570                 }
03571                 saberKnockbackFlags[curVictim] |= knockBackFlags;
03572         }
03573 }
03574 
03575 void WP_SaberApplyDamage( gentity_t *self )
03576 {
03577         int i;
03578         if ( !numVictims )
03579         {
03580                 return;
03581         }
03582         for ( i = 0; i < numVictims; i++ )
03583         {
03584                 gentity_t *victim = NULL;
03585                 int dflags = 0;
03586 
03587                 victim = &g_entities[victimEntityNum[i]];
03588 
03589 // nmckenzie: SABER_DAMAGE_WALLS
03590                 if ( !victim->client )
03591                 {
03592                         totalDmg[i] *= g_saberWallDamageScale.value;
03593                 }
03594 
03595                 if ( !dismemberDmg[i] )
03596                 {//don't do dismemberment!
03597                         dflags |= DAMAGE_NO_DISMEMBER;
03598                 }
03599                 dflags |= saberKnockbackFlags[i];
03600 
03601                 G_Damage( victim, self, self, dmgDir[i], dmgSpot[i], totalDmg[i], dflags, MOD_SABER );
03602         }
03603 }
03604 
03605 
03606 void WP_SaberDoHit( gentity_t *self, int saberNum, int bladeNum )
03607 {
03608         int i;
03609         if ( !numVictims )
03610         {
03611                 return;
03612         }
03613         for ( i = 0; i < numVictims; i++ )
03614         {
03615                 gentity_t *te = NULL, *victim = NULL;
03616                 qboolean isDroid = qfalse;
03617 
03618                 if ( victimHitEffectDone[i] )
03619                 {
03620                         continue;
03621                 }
03622 
03623                 victimHitEffectDone[i] = qtrue;
03624 
03625                 victim = &g_entities[victimEntityNum[i]];
03626 
03627                 if ( victim->client )
03628                 {
03629                         class_t npc_class = victim->client->NPC_class;
03630 
03631                         if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
03632                                         npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
03633                                         npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
03634                                         npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )  
03635                         { //don't make "blood" sparks for droids.
03636                                 isDroid = qtrue;
03637                         }
03638                 }
03639 
03640                 te = G_TempEntity( dmgSpot[i], EV_SABER_HIT );
03641                 if ( te )
03642                 {
03643                         te->s.otherEntityNum = victimEntityNum[i];
03644                         te->s.otherEntityNum2 = self->s.number;
03645                         te->s.weapon = saberNum;
03646                         te->s.legsAnim = bladeNum;
03647 
03648                         VectorCopy(dmgSpot[i], te->s.origin);
03649                         //VectorCopy(tr.plane.normal, te->s.angles);
03650                         VectorScale( dmgDir[i], -1, te->s.angles);
03651                         
03652                         if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
03653                         { //don't let it play with no direction
03654                                 te->s.angles[1] = 1;
03655                         }
03656 
03657                         if (!isDroid && (victim->client || victim->s.eType == ET_NPC ||
03658                                 victim->s.eType == ET_BODY))
03659                         {
03660                                 if ( totalDmg[i] < 5 )
03661                                 {
03662                                         te->s.eventParm = 3;
03663                                 }
03664                                 else if (totalDmg[i] < 20 )
03665                                 {
03666                                         te->s.eventParm = 2;
03667                                 }
03668                                 else
03669                                 {
03670                                         te->s.eventParm = 1;
03671                                 }
03672                         }
03673                         else
03674                         {
03675                                 if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[saberNum], bladeNum )
03676                                         && (self->client->saber[saberNum].saberFlags2&SFL2_NO_CLASH_FLARE) )
03677                                 {//don't do clash flare
03678                                 }
03679                                 else if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[saberNum], bladeNum )
03680                                         && (self->client->saber[saberNum].saberFlags2&SFL2_NO_CLASH_FLARE2) )
03681                                 {//don't do clash flare
03682                                 }
03683                                 else
03684                                 {
03685                                         if (totalDmg[i] > SABER_NONATTACK_DAMAGE)
03686                                         { //I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing.
03687                                                 gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE );
03688                                                 VectorCopy(te->s.origin, teS->s.origin);
03689                                         }
03690                                         te->s.eventParm = 0;
03691                                 }
03692                         }
03693                 }
03694         }
03695 }
03696 
03697 extern qboolean G_EntIsBreakable( int entityNum );
03698 extern void G_Knockdown( gentity_t *victim );
03699 void WP_SaberRadiusDamage( gentity_t *ent, vec3_t point, float radius, int damage, float knockBack )
03700 {
03701         if ( !ent || !ent->client )
03702         {
03703                 return;
03704         }
03705         else if ( radius <= 0.0f || (damage <= 0 && knockBack <= 0) )
03706         {
03707                 return;
03708         }
03709         else
03710         {
03711                 vec3_t          mins, maxs, entDir;
03712                 int                     radiusEnts[128];
03713                 gentity_t       *radiusEnt = NULL;
03714                 int                     numEnts, i;
03715                 float           dist;
03716 
03717                 //Setup the bbox to search in
03718                 for ( i = 0; i < 3; i++ )
03719                 {
03720                         mins[i] = point[i] - radius;
03721                         maxs[i] = point[i] + radius;
03722                 }
03723 
03724                 //Get the number of entities in a given space
03725                 numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, 128 );
03726 
03727                 for ( i = 0; i < numEnts; i++ )
03728                 {
03729                         radiusEnt = &g_entities[radiusEnts[i]];
03730                         if ( !radiusEnt->inuse )
03731                         {
03732                                 continue;
03733                         }
03734                         
03735                         if ( radiusEnt == ent )
03736                         {//Skip myself
03737                                 continue;
03738                         }
03739                         
03740                         if ( radiusEnt->client == NULL )
03741                         {//must be a client
03742                                 if ( G_EntIsBreakable( radiusEnt->s.number ) )
03743                                 {//damage breakables within range, but not as much
03744                                         G_Damage( radiusEnt, ent, ent, vec3_origin, radiusEnt->r.currentOrigin, 10, 0, MOD_MELEE );
03745                                 }
03746                                 continue;
03747                         }
03748 
03749                         if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
03750                         {//can't be one being held
03751                                 continue;
03752                         }
03753                         
03754                         VectorSubtract( radiusEnt->r.currentOrigin, point, entDir );
03755                         dist = VectorNormalize( entDir );
03756                         if ( dist <= radius )
03757                         {//in range
03758                                 if ( damage > 0 )
03759                                 {//do damage
03760                                         int points = ceil((float)damage*dist/radius);
03761                                         G_Damage( radiusEnt, ent, ent, vec3_origin, radiusEnt->r.currentOrigin, points, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
03762                                 }
03763                                 if ( knockBack > 0 )
03764                                 {//do knockback
03765                                         if ( radiusEnt->client
03766                                                 && radiusEnt->client->NPC_class != CLASS_RANCOR
03767                                                 && radiusEnt->client->NPC_class != CLASS_ATST
03768                                                 && !(radiusEnt->flags&FL_NO_KNOCKBACK) )//don't throw them back
03769                                         {
03770                                                 float knockbackStr = knockBack*dist/radius;
03771                                                 entDir[2] += 0.1f;
03772                                                 VectorNormalize( entDir );
03773                                                 G_Throw( radiusEnt, entDir, knockbackStr );
03774                                                 if ( radiusEnt->health > 0 )
03775                                                 {//still alive
03776                                                         if ( knockbackStr > 50 )
03777                                                         {//close enough and knockback high enough to possibly knock down
03778                                                                 if ( dist < (radius*0.5f)
03779                                                                         || radiusEnt->client->ps.groundEntityNum != ENTITYNUM_NONE )
03780                                                                 {//within range of my fist or within ground-shaking range and not in the air
03781                                                                         G_Knockdown( radiusEnt );//, ent, entDir, 500, qtrue );
03782                                                                 }
03783                                                         }
03784                                                 }
03785                                         }
03786                                 }
03787                         }
03788                 }
03789         }
03790 }
03791 
03792 static qboolean saberDoClashEffect = qfalse;
03793 static vec3_t saberClashPos = {0};
03794 static vec3_t saberClashNorm = {0};
03795 static int saberClashEventParm = 1;
03796 void WP_SaberDoClash( gentity_t *self, int saberNum, int bladeNum )
03797 {
03798         if ( saberDoClashEffect )
03799         {
03800                 gentity_t *te = G_TempEntity( saberClashPos, EV_SABER_BLOCK );
03801                 VectorCopy(saberClashPos, te->s.origin);
03802                 VectorCopy(saberClashNorm, te->s.angles);
03803                 te->s.eventParm = saberClashEventParm;
03804                 te->s.otherEntityNum2 = self->s.number;
03805                 te->s.weapon = saberNum;
03806                 te->s.legsAnim = bladeNum;
03807         }
03808 }
03809 
03810 void WP_SaberBounceSound( gentity_t *ent, int saberNum, int bladeNum )
03811 {
03812         int index = 1;
03813         if ( !ent || !ent->client )
03814         {
03815                 return;
03816         }
03817         index = Q_irand( 1, 9 );
03818         if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
03819                 && ent->client->saber[saberNum].bounceSound[0] )
03820         {
03821                 G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].bounceSound[Q_irand( 0, 2 )] );
03822         }
03823         else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
03824                 && ent->client->saber[saberNum].bounce2Sound[0] )
03825         {
03826                 G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].bounce2Sound[Q_irand( 0, 2 )] );
03827         }
03828         else if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
03829                 && ent->client->saber[saberNum].blockSound[0] )
03830         {
03831                 G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].blockSound[Q_irand( 0, 2 )] );
03832         }
03833         else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
03834                 && ent->client->saber[saberNum].block2Sound[0] )
03835         {
03836                 G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
03837         }
03838         else
03839         {       
03840                 G_Sound( ent, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
03841         }
03842 }
03843 
03844 static qboolean saberHitWall = qfalse;
03845 static qboolean saberHitSaber = qfalse;
03846 static float saberHitFraction = 1.0f;
03847 //rww - MP version of the saber damage function. This is where all the things like blocking, triggering a parry,
03848 //triggering a broken parry, doing actual damage, etc. are done for the saber. It doesn't resemble the SP
03849 //version very much, but functionality is (hopefully) about the same.
03850 //This is a large function. I feel sort of bad inlining it. But it does get called tons of times per frame.
03851 #include "../namespace_begin.h"
03852 qboolean BG_SuperBreakWinAnim( int anim );
03853 #include "../namespace_end.h"
03854 
03855 static GAME_INLINE qboolean CheckSaberDamage(gentity_t *self, int rSaberNum, int rBladeNum, vec3_t saberStart, vec3_t saberEnd, qboolean doInterpolate, int trMask, qboolean extrapolate )
03856 {
03857         static trace_t tr;
03858         static vec3_t dir;
03859         static vec3_t saberTrMins, saberTrMaxs;
03860         static vec3_t lastValidStart;
03861         static vec3_t lastValidEnd;
03862         static int selfSaberLevel;
03863         static int otherSaberLevel;
03864         int dmg = 0;
03865         int attackStr = 0;
03866         float saberBoxSize = d_saberBoxTraceSize.value;
03867         qboolean idleDamage = qfalse;
03868         qboolean didHit = qfalse;
03869         qboolean sabersClashed = qfalse;
03870         qboolean unblockable = qfalse;
03871         qboolean didDefense = qfalse;
03872         qboolean didOffense = qfalse;
03873         qboolean saberTraceDone = qfalse;
03874         qboolean otherUnblockable = qfalse;
03875         qboolean tryDeflectAgain = qfalse;
03876 
03877         gentity_t *otherOwner;
03878 
03879         if (BG_SabersOff( &self->client->ps ))
03880         {
03881                 return qfalse;
03882         }
03883 
03884         selfSaberLevel = G_SaberAttackPower(self, SaberAttacking(self));
03885 
03886         //Add the standard radius into the box size
03887         saberBoxSize += (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.5f);
03888 
03889         if (self->client->ps.weaponTime <= 0)
03890         { //if not doing any attacks or anything, just use point traces.
03891                 VectorClear(saberTrMins);
03892                 VectorClear(saberTrMaxs);
03893         }
03894         else if (d_saberGhoul2Collision.integer)
03895         {
03896                 if ( d_saberSPStyleDamage.integer )
03897                 {//SP-size saber damage traces
03898                         VectorSet(saberTrMins, -2, -2, -2 );
03899                         VectorSet(saberTrMaxs, 2, 2, 2 );
03900                 }
03901                 else
03902                 {
03903                         VectorSet(saberTrMins, -saberBoxSize*3, -saberBoxSize*3, -saberBoxSize*3);
03904                         VectorSet(saberTrMaxs, saberBoxSize*3, saberBoxSize*3, saberBoxSize*3);
03905                 }
03906         }
03907         else if (self->client->ps.fd.saberAnimLevel < FORCE_LEVEL_2)
03908         { //box trace for fast, because it doesn't get updated so often
03909                 VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
03910                 VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
03911         }
03912         else if (d_saberAlwaysBoxTrace.integer)
03913         {
03914                 VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
03915                 VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
03916         }
03917         else
03918         { //just trace the minimum blade radius
03919                 saberBoxSize = (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.4f);
03920 
03921                 VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
03922                 VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
03923         }
03924 
03925         while (!saberTraceDone)
03926         {
03927                 if ( doInterpolate
03928                         && !d_saberSPStyleDamage.integer )
03929                 { //This didn't quite work out like I hoped. But it's better than nothing. Sort of.
03930                         vec3_t oldSaberStart, oldSaberEnd, saberDif, oldSaberDif;
03931                         int traceTests = 0;
03932                         float trDif = 8;
03933 
03934                         if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
03935                         {//no valid last pos, use current
03936                                 VectorCopy(saberStart, oldSaberStart);
03937                                 VectorCopy(saberEnd, oldSaberEnd);
03938                         }
03939                         else
03940                         {//trace from last pos
03941                                 VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
03942                                 VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd);
03943                         }
03944 
03945                         VectorSubtract(saberStart, saberEnd, saberDif);
03946                         VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif);
03947 
03948                         VectorNormalize(saberDif);
03949                         VectorNormalize(oldSaberDif);
03950 
03951                         saberEnd[0] = saberStart[0] - (saberDif[0]*trDif);
03952                         saberEnd[1] = saberStart[1] - (saberDif[1]*trDif);
03953                         saberEnd[2] = saberStart[2] - (saberDif[2]*trDif);
03954 
03955                         oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif);
03956                         oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif);
03957                         oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif);
03958 
03959                         trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask);
03960 
03961                         VectorCopy(saberEnd, lastValidStart);
03962                         VectorCopy(saberStart, lastValidEnd);
03963                         if (tr.entityNum < MAX_CLIENTS)
03964                         {
03965                                 G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
03966                         }
03967                         else if (tr.entityNum < ENTITYNUM_WORLD)
03968                         {
03969                                 gentity_t *trHit = &g_entities[tr.entityNum];
03970 
03971                                 if (trHit->inuse && trHit->ghoul2)
03972                                 { //hit a non-client entity with a g2 instance
03973                                         G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
03974                                 }
03975                         }
03976 
03977                         trDif++;
03978 
03979                         while (tr.fraction == 1.0 && traceTests < 4 && tr.entityNum >= ENTITYNUM_NONE)
03980                         {
03981                                 if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
03982                                 {//no valid last pos, use current
03983                                         VectorCopy(saberStart, oldSaberStart);
03984                                         VectorCopy(saberEnd, oldSaberEnd);
03985                                 }
03986                                 else
03987                                 {//trace from last pos
03988                                         VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
03989                                         VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd);
03990                                 }
03991 
03992                                 VectorSubtract(saberStart, saberEnd, saberDif);
03993                                 VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif);
03994 
03995                                 VectorNormalize(saberDif);
03996                                 VectorNormalize(oldSaberDif);
03997 
03998                                 saberEnd[0] = saberStart[0] - (saberDif[0]*trDif);
03999                                 saberEnd[1] = saberStart[1] - (saberDif[1]*trDif);
04000                                 saberEnd[2] = saberStart[2] - (saberDif[2]*trDif);
04001 
04002                                 oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif);
04003                                 oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif);
04004                                 oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif);
04005 
04006                                 trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask);
04007                                 
04008                                 VectorCopy(saberEnd, lastValidStart);
04009                                 VectorCopy(saberStart, lastValidEnd);
04010                                 if (tr.entityNum < MAX_CLIENTS)
04011                                 {
04012                                         G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
04013                                 }
04014                                 else if (tr.entityNum < ENTITYNUM_WORLD)
04015                                 {
04016                                         gentity_t *trHit = &g_entities[tr.entityNum];
04017 
04018                                         if (trHit->inuse && trHit->ghoul2)
04019                                         { //hit a non-client entity with a g2 instance
04020                                                 G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
04021                                         }
04022                                 }
04023 
04024                                 traceTests++;
04025                                 trDif += 8;
04026                         }
04027                 }
04028                 else
04029                 {
04030                         vec3_t saberEndExtrapolated;
04031                         if ( extrapolate )
04032                         {//extrapolate 16
04033                                 vec3_t diff;
04034                                 VectorSubtract( saberEnd, saberStart, diff );
04035                                 VectorNormalize( diff );
04036                                 VectorMA( saberStart, SABER_EXTRAPOLATE_DIST, diff, saberEndExtrapolated );
04037                         }
04038                         else
04039                         {
04040                                 VectorCopy( saberEnd, saberEndExtrapolated );
04041                         }
04042                         trap_Trace(&tr, saberStart, saberTrMins, saberTrMaxs, saberEndExtrapolated, self->s.number, trMask);
04043 
04044                         VectorCopy(saberStart, lastValidStart);
04045                         VectorCopy(saberEndExtrapolated, lastValidEnd);
04046                         /*
04047                         if ( tr.allsolid || tr.startsolid )
04048                         {
04049                                 if ( tr.entityNum == ENTITYNUM_NONE )
04050                                 {
04051                                         qboolean whah = qtrue;
04052                                 }
04053                                 Com_Printf( "saber trace start/all solid - ent is %d\n", tr.entityNum );
04054                         }
04055                         */
04056                         if (tr.entityNum < MAX_CLIENTS)
04057                         {
04058                                 G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
04059                         }
04060                         else if (tr.entityNum < ENTITYNUM_WORLD)
04061                         {
04062                                 gentity_t *trHit = &g_entities[tr.entityNum];
04063 
04064                                 if (trHit->inuse && trHit->ghoul2)
04065                                 { //hit a non-client entity with a g2 instance
04066                                         G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
04067                                 }
04068                         }
04069                 }
04070 
04071                 saberTraceDone = qtrue;
04072         }
04073 
04074         if ( self->client->ps.saberAttackWound < level.time
04075                 && (SaberAttacking(self) 
04076                         || BG_SuperBreakWinAnim(self->client->ps.torsoAnim)
04077                         || (d_saberSPStyleDamage.integer&&self->client->ps.saberInFlight&&rSaberNum==0)
04078                         || (WP_SaberBladeDoTransitionDamage( &self->client->saber[rSaberNum], rBladeNum )&&BG_SaberInTransitionAny(self->client->ps.saberMove))
04079                         || (self->client->ps.m_iVehicleNum && self->client->ps.saberMove > LS_READY) )
04080            )
04081         { //this animation is that of the last attack movement, and so it should do full damage
04082                 qboolean saberInSpecial = BG_SaberInSpecial(self->client->ps.saberMove);
04083                 qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove);
04084 
04085                 if ( d_saberSPStyleDamage.integer )
04086                 {
04087                         float fDmg = 0.0f;
04088                         if ( self->client->ps.saberInFlight )
04089                         {
04090                                 gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum];
04091                                 if ( !saberEnt 
04092                                         || !saberEnt->s.saberInFlight )
04093                                 {//does less damage on the way back
04094                                         fDmg = 1.0f;
04095                                         attackStr = FORCE_LEVEL_0;
04096                                 }
04097                                 else
04098                                 {
04099                                         fDmg = 2.5f*self->client->ps.fd.forcePowerLevel[FP_SABERTHROW];
04100                                         attackStr = FORCE_LEVEL_1;
04101                                 }
04102                         }
04103                         else
04104                         {
04105                                 attackStr = G_PowerLevelForSaberAnim( self, rSaberNum, qfalse );
04106                                 if ( g_saberRealisticCombat.integer )
04107                                 {
04108                                         switch ( attackStr )
04109                                         {
04110                                         default:
04111                                         case FORCE_LEVEL_3:
04112                                                 fDmg = 10.0f;
04113                                                 break;
04114                                         case FORCE_LEVEL_2:
04115                                                 fDmg = 5.0f;
04116                                                 break;
04117                                         case FORCE_LEVEL_1:
04118                                         case FORCE_LEVEL_0:
04119                                                 fDmg = 2.5f;
04120                                                 break;
04121                                         }
04122                                 }
04123                                 else
04124                                 {
04125                                         if ( self->client->ps.torsoAnim == BOTH_SPINATTACK6
04126                                                 || self->client->ps.torsoAnim == BOTH_SPINATTACK7 )
04127                                         {//too easy to do, lower damage
04128                                                 fDmg = 2.5f;
04129                                         }
04130                                         else
04131                                         {
04132                                                 fDmg = 2.5f * (float)attackStr;
04133                                         }
04134                                 }
04135                         }
04136                         if ( g_saberRealisticCombat.integer > 1 )
04137                         {//always do damage, and lots of it
04138                                 if ( g_saberRealisticCombat.integer > 2 )
04139                                 {//always do damage, and lots of it
04140                                         fDmg = 25.0f;
04141                                 }
04142                                 else if ( fDmg > 0.1f )
04143                                 {//only do super damage if we would have done damage according to normal rules
04144                                         fDmg = 25.0f;
04145                                 }
04146                         }
04147                         /*
04148                         if ( dmg > 0.1f )
04149                         {
04150                                 if ( (self->client->ps.forcePowersActive&(1<<FP_RAGE)) )
04151                                 {//add some damage if raged
04152                                         dmg += self->client->ps.forcePowerLevel[FP_RAGE] * 5.0f;
04153                                 }
04154                                 else if ( self->client->ps.forceRageRecoveryTime )
04155                                 {//halve it if recovering
04156                                         dmg *= 0.5f;
04157                                 }
04158                         }
04159                         */
04160                         if ( g_gametype.integer != GT_DUEL
04161                                 && g_gametype.integer != GT_POWERDUEL
04162                                 && g_gametype.integer != GT_SIEGE )
04163                         {//in faster-paced games, sabers do more damage
04164                                 fDmg *= 2.0f;
04165                         }
04166                         if ( fDmg )
04167                         {//the longer the trace, the more damage it does
04168                                 //FIXME: in SP, we only use the part of the trace that's actually *inside* the hit ent...
04169                                 float traceLength = Distance( saberEnd, saberStart );
04170                                 if ( tr.fraction >= 1.0f )
04171                                 {//allsolid?
04172                                         dmg = ceil( fDmg*traceLength*0.1f*0.33f );
04173                                 }
04174                                 else
04175                                 {//fractional hit, the sooner you hit in the trace, the more damage you did
04176                                         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
04177                                 }
04178 #ifdef DEBUG_SABER_BOX
04179                                 if ( g_saberDebugBox.integer == 3 || g_saberDebugBox.integer == 4 )
04180                                 {
04181                                         G_TestLine( saberStart, saberEnd, 0x0000ff, 50 );
04182                                 }
04183 #endif
04184                         }
04185                         /*
04186                         if ( dmg )
04187                         {
04188                                 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 );
04189                         }
04190                         */
04191                         if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL
04192                                 || self->client->ps.torsoAnim == BOTH_A2_SPECIAL
04193                                 || self->client->ps.torsoAnim == BOTH_A3_SPECIAL )
04194                         {//parry/block/break-parry bonus for single-style kata moves
04195                                 attackStr++;
04196                         }
04197                         if ( BG_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
04198                         {
04199                                 trMask &= ~CONTENTS_LIGHTSABER;
04200                         }
04201                 }
04202                 else
04203                 {
04204                         dmg = SABER_HITDAMAGE;
04205 
04206                         if (self->client->ps.fd.saberAnimLevel == SS_STAFF ||
04207                                 self->client->ps.fd.saberAnimLevel == SS_DUAL)
04208                         {
04209                                 if (saberInSpecial)
04210                                 {
04211                                         //it will get auto-ramped based on the point in the attack, later on
04212                                         if (self->client->ps.saberMove == LS_SPINATTACK ||
04213                                                 self->client->ps.saberMove == LS_SPINATTACK_DUAL)
04214                                         { //these attacks are long and have the potential to hit a lot so they will do less damage.
04215                                                 dmg = 10;
04216                                         }
04217                                         else
04218                                         {
04219                                                 if ( BG_KickingAnim( self->client->ps.legsAnim ) ||
04220                                                         BG_KickingAnim( self->client->ps.torsoAnim ) )
04221                                                 { //saber shouldn't do more than min dmg during kicks
04222                                                         dmg = 2;
04223                                                 }
04224                                                 else if (BG_SaberInKata(self->client->ps.saberMove))
04225                                                 { //special kata move
04226                                                         if (self->client->ps.fd.saberAnimLevel == SS_DUAL)
04227                                                         { //this is the nasty saber twirl, do big damage cause it makes you vulnerable
04228                                                                 dmg = 90;
04229                                                         }
04230                                                         else
04231                                                         { //staff kata
04232                                                                 dmg = G_GetAttackDamage(self, 60, 70, 0.5f);
04233                                                         }
04234                                                 }
04235                                                 else
04236                                                 {
04237                                                         //dmg = 90;
04238                                                         //ramp from 2 to 90 by default for other specials
04239                                                         dmg = G_GetAttackDamage(self, 2, 90, 0.5f);
04240                                                 }
04241                                         }
04242                                 }
04243                                 else
04244                                 { //otherwise we'll ramp up to 70 I guess, for both dual and staff
04245                                         dmg = G_GetAttackDamage(self, 2, 70, 0.5f);
04246                                 }
04247                         }
04248                         else if (self->client->ps.fd.saberAnimLevel == 3)
04249                         {
04250                                 //new damage-ramping system
04251                                 if (!saberInSpecial && !inBackAttack)
04252                                 {
04253                                         dmg = G_GetAttackDamage(self, 2, 120, 0.5f);
04254                                 }
04255                                 else if (saberInSpecial &&
04256                                                 (self->client->ps.saberMove == LS_A_JUMP_T__B_))
04257                                 {
04258                                         dmg = G_GetAttackDamage(self, 2, 180, 0.65f);
04259                                 }
04260                                 else if (inBackAttack)
04261                                 {
04262                                         dmg = G_GetAttackDamage(self, 2, 30, 0.5f); //can hit multiple times (and almost always does), so..
04263                                 }
04264                                 else
04265                                 {
04266                                         dmg = 100;
04267                                 }
04268                         }
04269                         else if (self->client->ps.fd.saberAnimLevel == 2)
04270                         {
04271                                 if (saberInSpecial &&
04272                                         (self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH))
04273                                 { //a well-timed hit with this can do a full 85
04274                                         dmg = G_GetAttackDamage(self, 2, 80, 0.5f);
04275                                 }
04276                                 else if (inBackAttack)
04277                                 {
04278                                         dmg = G_GetAttackDamage(self, 2, 25, 0.5f);
04279                                 }
04280                                 else
04281                                 {
04282                                         dmg = 60;
04283                                 }
04284                         }
04285                         else if (self->client->ps.fd.saberAnimLevel == 1)
04286                         {
04287                                 if (saberInSpecial &&
04288                                         (self->client->ps.saberMove == LS_A_LUNGE))
04289                                 {
04290                                         dmg = G_GetAttackDamage(self, 2, SABER_HITDAMAGE-5, 0.3f);
04291                                 }
04292                                 else if (inBackAttack)
04293                                 {
04294                                         dmg = G_GetAttackDamage(self, 2, 30, 0.5f);
04295                                 }
04296                                 else
04297                                 {
04298                                         dmg = SABER_HITDAMAGE;
04299                                 }
04300                         }
04301 
04302                         attackStr = self->client->ps.fd.saberAnimLevel;
04303                 }
04304         }
04305         else if (self->client->ps.saberAttackWound < level.time &&
04306                 self->client->ps.saberIdleWound < level.time)
04307         { //just touching, do minimal damage and only check for it every 200ms (mainly to cut down on network traffic for hit events)
04308                 if ( (self->client->saber[0].saberFlags2&SFL2_NO_IDLE_EFFECT) )
04309                 {//no idle damage or effects
04310                         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.
04311                 }
04312                 trMask &= ~CONTENTS_LIGHTSABER;
04313                 if ( d_saberSPStyleDamage.integer )
04314                 {
04315                         if ( BG_SaberInReturn( self->client->ps.saberMove ) )
04316                         {
04317                                 dmg = SABER_NONATTACK_DAMAGE;
04318                         }
04319                         else
04320                         {
04321                                 if (d_saberSPStyleDamage.integer == 2)
04322                                 {
04323                                         dmg = SABER_NONATTACK_DAMAGE;
04324                                 }
04325                                 else
04326                                 {
04327                                         dmg = 0;
04328                                 }
04329                         }
04330                 }
04331                 else
04332                 {
04333                         dmg = SABER_NONATTACK_DAMAGE;
04334                 }
04335                 idleDamage = qtrue;
04336         }
04337         else
04338         {
04339                 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.
04340         }
04341 
04342         if (BG_SaberInSpecial(self->client->ps.saberMove))
04343         {
04344                 qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove);
04345 
04346                 unblockable = qtrue;
04347                 self->client->ps.saberBlocked = 0;
04348 
04349                 if ( d_saberSPStyleDamage.integer )
04350                 {
04351                 }
04352                 else if (!inBackAttack)
04353                 {
04354                         if (self->client->ps.saberMove == LS_A_JUMP_T__B_)
04355                         { //do extra damage for special unblockables
04356                                 dmg += 5; //This is very tiny, because this move has a huge damage ramp
04357                         }
04358                         else if (self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH)
04359                         {
04360                                 dmg += 5; //ditto
04361                                 if (dmg <= 40 || G_GetAnimPoint(self) <= 0.4f)
04362                                 { //sort of a hack, don't want it doing big damage in the off points of the anim
04363                                         dmg = 2;
04364                                 }
04365                         }
04366                         else if (self->client->ps.saberMove == LS_A_LUNGE)
04367                         {
04368                                 dmg += 2; //and ditto again
04369                                 if (G_GetAnimPoint(self) <= 0.4f)
04370                                 { //same as above
04371                                         dmg = 2;
04372                                 }
04373                         }
04374                         else if (self->client->ps.saberMove == LS_SPINATTACK ||
04375                                 self->client->ps.saberMove == LS_SPINATTACK_DUAL)
04376                         { //do a constant significant amount of damage but ramp up a little to the mid-point
04377                                 dmg = G_GetAttackDamage(self, 2, dmg+3, 0.5f);
04378                                 dmg += 10;
04379                         }
04380                         else
04381                         {
04382                                 //dmg += 20;
04383                                 if ( BG_KickingAnim( self->client->ps.legsAnim ) ||
04384                                         BG_KickingAnim( self->client->ps.torsoAnim ) )
04385                                 { //saber shouldn't do more than min dmg during kicks
04386                                         dmg = 2;
04387                                 }
04388                                 else
04389                                 { //auto-ramp it I guess since it's a special we don't have a special case for.
04390                                         dmg = G_GetAttackDamage(self, 5, dmg+5, 0.5f);
04391                                 }
04392                         }
04393                 }
04394         }
04395 
04396         if (!dmg)
04397         {
04398                 if (tr.entityNum < MAX_CLIENTS ||
04399                         (g_entities[tr.entityNum].inuse && (g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER)))
04400                 {
04401                         return qtrue;
04402                 }
04403                 return qfalse;
04404         }
04405 
04406         if (dmg > SABER_NONATTACK_DAMAGE)
04407         {
04408                 dmg *= g_saberDamageScale.value;
04409 
04410                 //see if this specific saber has a damagescale
04411                 if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
04412                         && self->client->saber[rSaberNum].damageScale != 1.0f )
04413                 {
04414                         dmg = ceil( (float)dmg*self->client->saber[rSaberNum].damageScale );
04415                 }
04416                 else if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
04417                         && self->client->saber[rSaberNum].damageScale2 != 1.0f )
04418                 {
04419                         dmg = ceil( (float)dmg*self->client->saber[rSaberNum].damageScale2 );
04420                 }
04421 
04422                 if ((self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) ||
04423                         (self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
04424                 { //weaken it if an arm is broken
04425                         dmg *= 0.3;
04426                         if (dmg <= SABER_NONATTACK_DAMAGE)
04427                         {
04428                                 dmg = SABER_NONATTACK_DAMAGE+1;
04429                         }
04430                 }
04431         }
04432 
04433         if (dmg > SABER_NONATTACK_DAMAGE && self->client->ps.isJediMaster)
04434         { //give the Jedi Master more saber attack power
04435                 dmg *= 2;
04436         }
04437 
04438         if (dmg > SABER_NONATTACK_DAMAGE && g_gametype.integer == GT_SIEGE &&
04439                 self->client->siegeClass != -1 && (bgSiegeClasses[self->client->siegeClass].classflags & (1<<CFL_MORESABERDMG)))
04440         { //this class is flagged to do extra saber damage. I guess 2x will do for now.
04441                 dmg *= 2;
04442         }
04443 
04444         if (g_gametype.integer == GT_POWERDUEL &&
04445                 self->client->sess.duelTeam == DUELTEAM_LONE)
04446         { //always x2 when we're powerdueling alone... er, so, we apparently no longer want this?  So they say.
04447                 if ( g_duel_fraglimit.integer )
04448                 {
04449                         //dmg *= 1.5 - (.4 * (float)self->client->sess.wins / (float)g_duel_fraglimit.integer);
04450                                 
04451                 }
04452                 //dmg *= 2;
04453         }
04454 
04455 #ifndef FINAL_BUILD
04456         if (g_saberDebugPrint.integer > 2 && dmg > 1)
04457         {
04458                 Com_Printf("CL %i SABER DMG: %i\n", self->s.number, dmg);
04459         }
04460 #endif
04461 
04462         VectorSubtract(saberEnd, saberStart, dir);
04463         VectorNormalize(dir);
04464 
04465         if (tr.entityNum == ENTITYNUM_WORLD ||
04466                 g_entities[tr.entityNum].s.eType == ET_TERRAIN)
04467         { //register this as a wall hit for jedi AI
04468         self->client->ps.saberEventFlags |= SEF_HITWALL;
04469                 saberHitWall = qtrue;
04470         }
04471 
04472         if (saberHitWall 
04473                 && (self->client->saber[rSaberNum].saberFlags & SFL_BOUNCE_ON_WALLS)
04474                 && (BG_SaberInAttackPure( self->client->ps.saberMove ) //only in a normal attack anim
04475                         || self->client->ps.saberMove == LS_A_JUMP_T__B_ ) //or in the strong jump-fwd-attack "death from above" move
04476                 )
04477         { //then bounce off
04478                 /*
04479                 qboolean onlyIfNotSpecial = qfalse;
04480                 qboolean skipIt = qfalse;
04481                 if (tr.plane.normal[2] >= 0.8f ||
04482                         tr.plane.normal[2] <= -0.8f ||
04483                         VectorCompare(tr.plane.normal, vec3_origin))
04484                 {
04485                         if ((tr.plane.normal[2] >= 0.8f || VectorCompare(tr.plane.normal, vec3_origin)) &&
04486                                 self->client->ps.viewangles[PITCH] <= 30.0f &&
04487                                 self->client->pers.cmd.upmove >= 0)
04488                         { //don't hit the ground if we are not looking down a lot/crouched
04489                                 skipIt = qtrue;
04490                         }
04491                         else
04492                         {
04493                                 onlyIfNotSpecial = qtrue;
04494                         }
04495                 }
04496                 if (!skipIt && (!onlyIfNotSpecial || !BG_SaberInSpecial(self->client->ps.saberMove)))
04497                 */
04498                 {
04499                         gentity_t *te = NULL;
04500                         /*
04501                         qboolean pre = saberDoClashEffect;
04502 
04503                         VectorCopy( tr.endpos, saberClashPos );
04504                         VectorCopy( tr.plane.normal, saberClashNorm );
04505                         saberClashEventParm = 1;
04506                         saberDoClashEffect = qtrue;
04507                         WP_SaberDoClash( self, rSaberNum, rBladeNum );
04508                         saberDoClashEffect = pre;
04509                         */
04510 
04511                         self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove);
04512                         self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
04513                         if (self->client->ps.torsoAnim == self->client->ps.legsAnim)
04514                         { //set anim now on both parts
04515                                 int anim = saberMoveData[self->client->ps.saberMove].animToUse;
04516                                 G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
04517                         }
04518 
04519                         //do bounce sound & force feedback
04520                         WP_SaberBounceSound( self, rSaberNum, rBladeNum );
04521                         //do hit effect
04522                         te = G_TempEntity( tr.endpos, EV_SABER_HIT );
04523                         te->s.otherEntityNum = ENTITYNUM_NONE;//we didn't hit anyone in particular
04524                         te->s.otherEntityNum2 = self->s.number;//send this so it knows who we are
04525                         te->s.weapon = rSaberNum;
04526                         te->s.legsAnim = rBladeNum;
04527                         VectorCopy(tr.endpos, te->s.origin);
04528                         VectorCopy(tr.plane.normal, te->s.angles);
04529                         if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
04530                         { //don't let it play with no direction
04531                                 te->s.angles[1] = 1;
04532                         }
04533                         //do radius damage/knockback, if any
04534                         if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum ) )
04535                         {
04536                                 WP_SaberRadiusDamage( self, tr.endpos, self->client->saber[rSaberNum].splashRadius, self->client->saber[rSaberNum].splashDamage, self->client->saber[rSaberNum].splashKnockback );
04537                         }
04538                         else
04539                         {
04540                                 WP_SaberRadiusDamage( self, tr.endpos, self->client->saber[rSaberNum].splashRadius2, self->client->saber[rSaberNum].splashDamage2, self->client->saber[rSaberNum].splashKnockback2 );
04541                         }
04542         
04543                         return qtrue;
04544                 }
04545         }
04546 
04547         //rww - I'm saying || tr.startsolid here, because otherwise your saber tends to skip positions and go through
04548         //people, and the compensation traces start in their bbox too. Which results in the saber passing through people
04549         //when you visually cut right through them. Which sucks.
04550 
04551         if ((tr.fraction != 1 || tr.startsolid) &&
04552                 g_entities[tr.entityNum].takedamage &&
04553                 (g_entities[tr.entityNum].health > 0 || !(g_entities[tr.entityNum].s.eFlags & EF_DISINTEGRATION)) &&
04554                 tr.entityNum != self->s.number &&
04555                 g_entities[tr.entityNum].inuse)
04556         {//hit something that had health and takes damage
04557                 if (idleDamage &&
04558                         g_entities[tr.entityNum].client &&
04559                         OnSameTeam(self, &g_entities[tr.entityNum]) &&
04560                         !g_friendlySaber.integer)
04561                 {
04562                         return qfalse;
04563                 }
04564 
04565                 if (g_entities[tr.entityNum].client &&
04566                         g_entities[tr.entityNum].client->ps.duelInProgress &&
04567                         g_entities[tr.entityNum].client->ps.duelIndex != self->s.number)
04568                 {
04569                         return qfalse;
04570                 }
04571 
04572                 if (g_entities[tr.entityNum].client &&
04573                         self->client->ps.duelInProgress &&
04574                         self->client->ps.duelIndex != g_entities[tr.entityNum].s.number)
04575                 {
04576                         return qfalse;
04577                 }
04578 
04579                 if ( BG_StabDownAnim( self->client->ps.torsoAnim )
04580                         && g_entities[tr.entityNum].client 
04581                         && !BG_InKnockDownOnGround( &g_entities[tr.entityNum].client->ps ) )
04582                 {//stabdowns only damage people who are actually on the ground...
04583                         return qfalse;
04584                 }
04585                 self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
04586 
04587                 didHit = qtrue;
04588 
04589                 if ( !d_saberSPStyleDamage.integer//let's trying making blocks have to be blocked by a saber
04590                         && g_entities[tr.entityNum].client 
04591                         && !unblockable 
04592                         && WP_SaberCanBlock(&g_entities[tr.entityNum], tr.endpos, 0, MOD_SABER, qfalse, attackStr))
04593                 {//hit a client who blocked the attack (fake: didn't actually hit their saber)
04594                         if (dmg <= SABER_NONATTACK_DAMAGE)
04595                         {
04596                                 self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
04597                         }
04598                         saberDoClashEffect = qtrue;
04599                         VectorCopy( tr.endpos, saberClashPos );
04600                         VectorCopy( tr.plane.normal, saberClashNorm );
04601                         saberClashEventParm = 1;
04602 
04603                         if (dmg > SABER_NONATTACK_DAMAGE)
04604                         {
04605                                 int lockFactor = g_saberLockFactor.integer;
04606 
04607                                 if ((g_entities[tr.entityNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] - self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) > 1 &&
04608                                         Q_irand(1, 10) < lockFactor*2)
04609                                 { //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)
04610                                         if (!G_ClientIdleInWorld(&g_entities[tr.entityNum]))
04611                                         {
04612                                                 if ( (trMask&CONTENTS_LIGHTSABER)
04613                                                         && WP_SabersCheckLock(self, &g_entities[tr.entityNum]))
04614                                                 {       
04615                                                         self->client->ps.saberBlocked = BLOCKED_NONE;
04616                                                         g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE;
04617                                                         return didHit;
04618                                                 }
04619                                         }
04620                                 }
04621                                 else if (Q_irand(1, 20) < lockFactor)
04622                                 {
04623                                         if (!G_ClientIdleInWorld(&g_entities[tr.entityNum]))
04624                                         {
04625                                                 if ((trMask&CONTENTS_LIGHTSABER)
04626                                                         && WP_SabersCheckLock(self, &g_entities[tr.entityNum]))
04627                                                 {       
04628                                                         self->client->ps.saberBlocked = BLOCKED_NONE;
04629                                                         g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE;
04630                                                         return didHit;
04631                                                 }
04632                                         }
04633                                 }
04634                         }
04635                         otherOwner = &g_entities[tr.entityNum];
04636                         goto blockStuff;
04637                 }
04638                 else
04639                 {//damage the thing we hit
04640                         qboolean doDismemberment = qfalse;
04641                         int     knockbackFlags = 0;
04642 
04643                         if (g_entities[tr.entityNum].client)
04644                         { //not a "jedi", so make them suffer more
04645                                 if ( dmg > SABER_NONATTACK_DAMAGE )
04646                                 { //don't bother increasing just for idle touch damage
04647                                         dmg *= 1.5;
04648                                 }
04649                         }
04650                         /*
04651                         if (g_entities[tr.entityNum].client 
04652                                 && g_entities[tr.entityNum].client->ps.weapon != WP_SABER )//fd.forcePowerLevel[FP_SABER_OFFENSE])
04653                         { //not a "jedi", so make them suffer more
04654                                 if ( dmg > SABER_NONATTACK_DAMAGE )
04655                                 { //don't bother increasing just for idle touch damage
04656                                         dmg *= 1.5;
04657                                 }
04658                         }
04659                         */
04660 
04661                         if ( !d_saberSPStyleDamage.integer )
04662                         {
04663                                 if (g_entities[tr.entityNum].client && g_entities[tr.entityNum].client->ps.weapon == WP_SABER)
04664                                 { //for jedi using the saber, half the damage (this comes with the increased default dmg debounce time)
04665                                         if (g_gametype.integer != GT_SIEGE)
04666                                         { //unless siege..
04667                                                 if (dmg > SABER_NONATTACK_DAMAGE && !unblockable)
04668                                                 { //don't reduce damage if it's only 1, or if this is an unblockable attack
04669                                                         if (dmg == SABER_HITDAMAGE)
04670                                                         { //level 1 attack
04671                                                                 dmg *= 0.7;
04672                                                         }
04673                                                         else
04674                                                         {
04675                                                                 dmg *= 0.5;
04676                                                         }
04677                                                 }
04678                                         }
04679                                 }
04680                         }
04681 
04682                         if (self->s.eType == ET_NPC &&
04683                                 g_entities[tr.entityNum].client &&
04684                                 self->client->playerTeam == g_entities[tr.entityNum].client->playerTeam)
04685                         { //Oops. Since he's an NPC, we'll be forgiving and cut the damage down.
04686                                 dmg *= 0.2f;
04687                         }
04688 
04689                         //store the damage, we'll apply it later
04690                         if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
04691                                 && !(self->client->saber[rSaberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
04692                         {
04693                                 doDismemberment = qtrue;
04694                         }
04695                         if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
04696                                 && !(self->client->saber[rSaberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
04697                         {
04698                                 doDismemberment = qtrue;
04699                         }
04700 
04701                         if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
04702                                 && self->client->saber[rSaberNum].knockbackScale > 0.0f )
04703                         {
04704                                 if ( rSaberNum < 1 )
04705                                 {
04706                                         knockbackFlags = DAMAGE_SABER_KNOCKBACK1;
04707                                 }
04708                                 else
04709                                 {
04710                                         knockbackFlags = DAMAGE_SABER_KNOCKBACK2;
04711                                 }
04712                         }
04713 
04714                         if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
04715                                 && self->client->saber[rSaberNum].knockbackScale > 0.0f )
04716                         {
04717                                 if ( rSaberNum < 1 )
04718                                 {
04719                                         knockbackFlags = DAMAGE_SABER_KNOCKBACK1_B2;
04720                                 }
04721                                 else
04722                                 {
04723                                         knockbackFlags = DAMAGE_SABER_KNOCKBACK2_B2;
04724                                 }
04725                         }
04726 
04727                         WP_SaberDamageAdd( tr.entityNum, dir, tr.endpos, dmg, doDismemberment, knockbackFlags );
04728 
04729                         if (g_entities[tr.entityNum].client)
04730                         {
04731                                 //Let jedi AI know if it hit an enemy
04732                                 if ( self->enemy && self->enemy == &g_entities[tr.entityNum] )
04733                                 {
04734                                         self->client->ps.saberEventFlags |= SEF_HITENEMY;
04735                                 }
04736                                 else
04737                                 {
04738                     self->client->ps.saberEventFlags |= SEF_HITOBJECT;
04739                                 }
04740                         }
04741 
04742                         if ( d_saberSPStyleDamage.integer )
04743                         {
04744                         }
04745                         else
04746                         {
04747                                 self->client->ps.saberAttackWound = level.time + 100;
04748                         }
04749                 }
04750         }
04751         else if ((tr.fraction != 1 || tr.startsolid) &&
04752                 (g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER) &&
04753                 g_entities[tr.entityNum].r.contents != -1 &&
04754                 g_entities[tr.entityNum].inuse)
04755         { //saber clash
04756                 otherOwner = &g_entities[g_entities[tr.entityNum].r.ownerNum];
04757 
04758                 if (!otherOwner->inuse || !otherOwner->client)
04759                 {
04760                         return qfalse;
04761                 }
04762 
04763                 if ( otherOwner 
04764                         && otherOwner->client 
04765                         && otherOwner->client->ps.saberInFlight )
04766                 {//don't do extra collision checking vs sabers in air
04767                 }
04768                 else
04769                 {//hit an in-hand saber, do extra collision check against it
04770                         if ( d_saberSPStyleDamage.integer )
04771                         {//use SP-style blade-collision test
04772                                 if ( !WP_SabersIntersect( self, rSaberNum, rBladeNum, otherOwner, qfalse ) )
04773                                 {//sabers did not actually intersect
04774                                         return qfalse;
04775                                 }
04776                         }
04777                         else
04778                         {//MP-style
04779                                 if (!G_SaberCollide(self, otherOwner, lastValidStart,
04780                                         lastValidEnd, saberTrMins, saberTrMaxs, tr.endpos))
04781                                 { //detailed collision did not produce results...
04782                                         return qfalse;
04783                                 }
04784                         }
04785                 }
04786 
04787                 if (OnSameTeam(self, otherOwner) &&
04788                         !g_friendlySaber.integer)
04789                 {
04790                         return qfalse;
04791                 }
04792 
04793                 if ((self->s.eType == ET_NPC || otherOwner->s.eType == ET_NPC) && //just make sure one of us is an npc
04794                         self->client->playerTeam == otherOwner->client->playerTeam &&
04795                         g_gametype.integer != GT_SIEGE)
04796                 { //don't hit your teammate's sabers if you are an NPC. It can be rather annoying.
04797                         return qfalse;
04798                 }
04799 
04800                 if (otherOwner->client->ps.duelInProgress &&
04801                         otherOwner->client->ps.duelIndex != self->s.number)
04802                 {
04803                         return qfalse;
04804                 }
04805 
04806                 if (self->client->ps.duelInProgress &&
04807                         self->client->ps.duelIndex != otherOwner->s.number)
04808                 {
04809                         return qfalse;
04810                 }
04811 
04812                 if ( g_debugSaberLocks.integer )
04813                 {
04814                         WP_SabersCheckLock2( self, otherOwner, LOCK_RANDOM );
04815                         return qtrue;
04816                 }
04817                 didHit = qtrue;
04818                 self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
04819 
04820                 if (dmg <= SABER_NONATTACK_DAMAGE)
04821                 {
04822                         self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
04823                 }
04824 
04825                 saberDoClashEffect = qtrue;
04826                 VectorCopy( tr.endpos, saberClashPos );
04827                 VectorCopy( tr.plane.normal, saberClashNorm );
04828                 saberClashEventParm = 1;
04829 
04830                 sabersClashed = qtrue;
04831                 saberHitSaber = qtrue;
04832                 saberHitFraction = tr.fraction;
04833 
04834                 if (saberCheckKnockdown_Smashed(&g_entities[tr.entityNum], otherOwner, self, dmg))
04835                 { //smashed it out of the air
04836                         return qfalse;
04837                 }
04838 
04839                 //is this my thrown saber?
04840                 if ( self->client->ps.saberEntityNum
04841                         && self->client->ps.saberInFlight
04842                         && rSaberNum == 0
04843                         && saberCheckKnockdown_Smashed( &g_entities[self->client->ps.saberEntityNum], self, otherOwner, dmg))
04844                 { //they smashed it out of the air
04845                         return qfalse;
04846                 }
04847 
04848 blockStuff:
04849                 otherUnblockable = qfalse;
04850 
04851                 if (otherOwner && otherOwner->client && otherOwner->client->ps.saberInFlight)
04852                 {
04853                         return qfalse;
04854                 }
04855 
04856                 //this is a thrown saber, don't do any fancy saber-saber collision stuff
04857                 if ( self->client->ps.saberEntityNum
04858                         && self->client->ps.saberInFlight
04859                         && rSaberNum == 0 )
04860                 {
04861                         return qfalse;
04862                 }
04863 
04864                 otherSaberLevel = G_SaberAttackPower(otherOwner, SaberAttacking(otherOwner));
04865 
04866                 if (dmg > SABER_NONATTACK_DAMAGE && !unblockable && !otherUnblockable)
04867                 {
04868                         int lockFactor = g_saberLockFactor.integer;
04869 
04870                         if (sabersClashed && Q_irand(1, 20) <= lockFactor)
04871                         {
04872                                 if (!G_ClientIdleInWorld(otherOwner))
04873                                 {
04874                                         if (WP_SabersCheckLock(self, otherOwner))
04875                                         {
04876                                                 self->client->ps.saberBlocked = BLOCKED_NONE;
04877                                                 otherOwner->client->ps.saberBlocked = BLOCKED_NONE;
04878                                                 return didHit;
04879                                         }
04880                                 }
04881                         }
04882                 }
04883 
04884                 if (!otherOwner || !otherOwner->client)
04885                 {
04886                         return didHit;
04887                 }
04888 
04889                 if (BG_SaberInSpecial(otherOwner->client->ps.saberMove))
04890                 {
04891                         otherUnblockable = qtrue;
04892                         otherOwner->client->ps.saberBlocked = 0;
04893                 }
04894 
04895                 if ( sabersClashed &&
04896                         dmg > SABER_NONATTACK_DAMAGE &&
04897                          selfSaberLevel < FORCE_LEVEL_3 &&
04898                          !PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
04899                          !PM_SaberInParry(self->client->ps.saberMove) &&
04900                          !PM_SaberInBrokenParry(self->client->ps.saberMove) &&
04901                          !BG_SaberInSpecial(self->client->ps.saberMove) &&
04902                          !PM_SaberInBounce(self->client->ps.saberMove) &&
04903                          !PM_SaberInDeflect(self->client->ps.saberMove) &&
04904                          !PM_SaberInReflect(self->client->ps.saberMove) &&
04905                          !unblockable )
04906                 {
04907                         //if (Q_irand(1, 10) <= 6)
04908                         if (1) //for now, just always try a deflect. (deflect func can cause bounces too)
04909                         {
04910                                 if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction))
04911                                 {
04912                                         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
04913                                 }
04914                                 else
04915                                 {
04916                                         self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
04917                                         didOffense = qtrue;
04918                                 }
04919                         }
04920                         else
04921                         {
04922                                 self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
04923                                 didOffense = qtrue;
04924 
04925 #ifndef FINAL_BUILD
04926                                 if (g_saberDebugPrint.integer)
04927                                 {
04928                                         Com_Printf("Client %i clashed into client %i's saber, did BLOCKED_ATK_BOUNCE\n", self->s.number, otherOwner->s.number);
04929                                 }
04930 #endif
04931                         }
04932                 }
04933 
04934                 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))
04935                         && !PM_SaberInBounce(self->client->ps.saberMove)
04936 
04937                         && !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove)
04938                         && !BG_SaberInSpecial(otherOwner->client->ps.saberMove)
04939                         && !PM_SaberInBounce(otherOwner->client->ps.saberMove)
04940                         && !PM_SaberInDeflect(otherOwner->client->ps.saberMove)
04941                         && !PM_SaberInReflect(otherOwner->client->ps.saberMove)
04942 
04943                         && (otherSaberLevel > FORCE_LEVEL_2 || ( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= 3 && Q_irand(0, otherSaberLevel) )) 
04944                         && !unblockable
04945                         && !otherUnblockable
04946                         && dmg > SABER_NONATTACK_DAMAGE
04947                         && !didOffense) //don't allow the person we're attacking to do this if we're making an unblockable attack
04948                 {//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.
04949                         //Going to go ahead and let idle damage do simple knockaways. Looks sort of good that way.
04950                         //turn the parry into a knockaway
04951                         if (self->client->ps.saberEntityNum) //make sure he has his saber still
04952                         {
04953                                 saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner);
04954                         }
04955 
04956                         if (!PM_SaberInParry(otherOwner->client->ps.saberMove))
04957                         {
04958                                 WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
04959                                 otherOwner->client->ps.saberMove = BG_KnockawayForParry( otherOwner->client->ps.saberBlocked );
04960                                 otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
04961                         }
04962                         else
04963                         {
04964                                 otherOwner->client->ps.saberMove = G_KnockawayForParry(otherOwner->client->ps.saberMove); //BG_KnockawayForParry( otherOwner->client->ps.saberBlocked );
04965                                 otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
04966                         }
04967         
04968                         //make them (me) go into a broken parry
04969                         self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove );
04970                         self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
04971 
04972 #ifndef FINAL_BUILD
04973                         if (g_saberDebugPrint.integer)
04974                         {
04975                                 Com_Printf("Client %i sent client %i into a reflected attack with a knockaway\n", otherOwner->s.number, self->s.number);
04976                         }
04977 #endif
04978 
04979                         didDefense = qtrue;
04980                 }
04981                 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)
04982                                  ( 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)) ) &&
04983                                  PM_SaberInParry(otherOwner->client->ps.saberMove) &&
04984                                  !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
04985                                  !PM_SaberInParry(self->client->ps.saberMove) &&
04986                                  !PM_SaberInBrokenParry(self->client->ps.saberMove) &&
04987                                  !PM_SaberInBounce(self->client->ps.saberMove) &&
04988                                  dmg > SABER_NONATTACK_DAMAGE &&
04989                                  !didOffense &&
04990                                  !otherUnblockable)
04991                 { //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.
04992                         if (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still
04993                         {
04994                                 saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self);
04995                         }
04996 
04997 #ifndef FINAL_BUILD
04998                         if (g_saberDebugPrint.integer)
04999                         {
05000                                 Com_Printf("Client %i sent client %i into a broken parry\n", self->s.number, otherOwner->s.number);
05001                         }
05002 #endif
05003 
05004                         otherOwner->client->ps.saberMove = BG_BrokenParryForParry( otherOwner->client->ps.saberMove );
05005                         otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
05006 
05007                         didDefense = qtrue;
05008                 }
05009                 else if ((selfSaberLevel > FORCE_LEVEL_2) && //if we're doing a special attack, we can send them into a broken parry too (MP only)
05010                                  //( 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)) ) &&
05011                                  otherSaberLevel >= FORCE_LEVEL_3 &&
05012                                  PM_SaberInParry(otherOwner->client->ps.saberMove) &&
05013                                  !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
05014                                  !PM_SaberInParry(self->client->ps.saberMove) &&
05015                                  !PM_SaberInBrokenParry(self->client->ps.saberMove) &&
05016                                  !PM_SaberInBounce(self->client->ps.saberMove) &&
05017                                  !PM_SaberInDeflect(self->client->ps.saberMove) &&
05018                                  !PM_SaberInReflect(self->client->ps.saberMove) &&
05019                                  dmg > SABER_NONATTACK_DAMAGE &&
05020                                  !didOffense &&
05021                                  !unblockable)
05022                 { //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.
05023 #ifndef FINAL_BUILD
05024                         if (g_saberDebugPrint.integer)
05025                         {
05026                                 Com_Printf("Client %i bounced off of client %i's saber\n", self->s.number, otherOwner->s.number);
05027                         }
05028 #endif
05029 
05030                         if (!tryDeflectAgain)
05031                         {
05032                                 if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction))
05033                                 {
05034                                         tryDeflectAgain = qtrue;
05035                                 }
05036                         }
05037 
05038                         didOffense = qtrue;
05039                 }
05040                 else if (SaberAttacking(otherOwner) && dmg > SABER_NONATTACK_DAMAGE && !BG_SaberInSpecial(otherOwner->client->ps.saberMove) && !didOffense && !otherUnblockable)
05041                 { //they were attacking and our saber hit their saber, make them bounce. But if they're in a special attack, leave them.
05042                         if (!PM_SaberInBounce(self->client->ps.saberMove) &&
05043                                 !PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
05044                                 !PM_SaberInDeflect(self->client->ps.saberMove) &&
05045                                 !PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
05046 
05047                                 !PM_SaberInReflect(self->client->ps.saberMove) &&
05048                                 !PM_SaberInReflect(otherOwner->client->ps.saberMove))
05049                         {
05050                                 int attackAdv, defendStr = G_PowerLevelForSaberAnim( otherOwner, 0, qtrue ), attackBonus = 0;
05051                                 if ( otherOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL
05052                                         || otherOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL
05053                                         || otherOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL )
05054                                 {//parry/block/break-parry bonus for single-style kata moves
05055                                         defendStr++;
05056                                 }
05057                                 defendStr += Q_irand(0, otherOwner->client->saber[0].parryBonus );
05058                                 if ( otherOwner->client->saber[1].model
05059                                         && otherOwner->client->saber[1].model[0]
05060                                         && !otherOwner->client->ps.saberHolstered )
05061                                 {
05062                                         defendStr += Q_irand(0, otherOwner->client->saber[1].parryBonus );
05063                                 }
05064 
05065 #ifndef FINAL_BUILD
05066                                 if (g_saberDebugPrint.integer)
05067                                 {
05068                                         Com_Printf("Client %i and client %i bounced off of each other's sabers\n", self->s.number, otherOwner->s.number);
05069                                 }
05070 #endif
05071 
05072                                 attackBonus = Q_irand(0, self->client->saber[0].breakParryBonus );
05073                                 if ( self->client->saber[1].model
05074                                         && self->client->saber[1].model[0]
05075                                         && !self->client->ps.saberHolstered )
05076                                 {
05077                                         attackBonus += Q_irand(0, self->client->saber[1].breakParryBonus );
05078                                 }
05079                                 attackAdv = (attackStr+attackBonus+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])-(defendStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]);
05080                                 
05081                                 if ( attackAdv > 1 ) 
05082                                 {//I won, he should knockaway
05083                                         otherOwner->client->ps.saberMove = BG_BrokenParryForAttack( otherOwner->client->ps.saberMove );
05084                                         otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
05085                                 }
05086                                 else if ( attackAdv > 0 )
05087                                 {//I won, he should bounce, I should continue
05088                                         otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
05089                                 }
05090                                 else if ( attackAdv < 1 )
05091                                 {//I lost, I get knocked away
05092                                         self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove );
05093                                         self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
05094                                 }
05095                                 else if ( attackAdv < 0 )
05096                                 {//I lost, I bounce off
05097                                         self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
05098                                 }
05099                                 else
05100                                 {//even, both bounce off
05101                                         self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
05102                                         otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
05103                                 }
05104 
05105                                 didOffense = qtrue;
05106                         }
05107                 }
05108                 
05109                 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
05110                 {
05111                         if (!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
05112                                 !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
05113                                 !BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
05114                                 !PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
05115                                 !PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
05116                                 !PM_SaberInReflect(otherOwner->client->ps.saberMove))
05117                         {
05118                                 WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
05119                                 otherOwner->client->ps.saberEventFlags |= SEF_PARRIED;
05120                         }
05121                 }
05122                 else if (!didDefense && dmg > SABER_NONATTACK_DAMAGE && !otherUnblockable) //if not more than idle damage, don't even bother blocking.
05123                 { //block
05124                         if (!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
05125                                 !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
05126                                 !BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
05127                                 !PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
05128                                 !PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
05129                                 !PM_SaberInReflect(otherOwner->client->ps.saberMove))
05130                         {
05131                                 qboolean crushTheParry = qfalse;
05132 
05133                                 if (unblockable)
05134                                 { //It's unblockable. So send us into a broken parry immediately.
05135                                         crushTheParry = qtrue;
05136                                 }
05137 
05138                                 if (!SaberAttacking(otherOwner))
05139                                 {
05140                                         int otherIdleStr = otherOwner->client->ps.fd.saberAnimLevel;
05141                                         if ( otherIdleStr == SS_DUAL
05142                                                 || otherIdleStr == SS_STAFF )
05143                                         {
05144                                                 otherIdleStr = SS_MEDIUM;
05145                                         }
05146 
05147                                         WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
05148                                         otherOwner->client->ps.saberEventFlags |= SEF_PARRIED;
05149                                         self->client->ps.saberEventFlags |= SEF_BLOCKED;
05150 
05151                                         if ( attackStr+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > otherIdleStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] )
05152                                         {
05153                                                 crushTheParry = qtrue;
05154                                         }
05155                                         else
05156                                         {
05157                                                 tryDeflectAgain = qtrue;
05158                                         }
05159                                 }
05160                                 else if (selfSaberLevel > otherSaberLevel ||
05161                                         (selfSaberLevel == otherSaberLevel && Q_irand(1, 10) <= 2))
05162                                 { //they are attacking, and we managed to make them break
05163                                         //Give them a parry, so we can later break it.
05164                                         WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
05165                                         crushTheParry = qtrue;
05166 
05167                                         if (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still
05168                                         {
05169                                                 saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self);
05170                                         }
05171 
05172 #ifndef FINAL_BUILD
05173                                         if (g_saberDebugPrint.integer)
05174                                         {
05175                                                 Com_Printf("Client %i forced client %i into a broken parry with a stronger attack\n", self->s.number, otherOwner->s.number);
05176                                         }
05177 #endif
05178                                 }
05179                                 else
05180                                 { //They are attacking, so are we, and obviously they have an attack level higher than or equal to ours
05181                                         if (selfSaberLevel == otherSaberLevel)
05182                                         { //equal level, try to bounce off each other's sabers
05183                                                 if (!didOffense &&
05184                                                         !PM_SaberInParry(self->client->ps.saberMove) &&
05185                                                         !PM_SaberInBrokenParry(self->client->ps.saberMove) &&
05186                                                         !BG_SaberInSpecial(self->client->ps.saberMove) &&
05187                                                         !PM_SaberInBounce(self->client->ps.saberMove) &&
05188                                                         !PM_SaberInDeflect(self->client->ps.saberMove) &&
05189                                                         !PM_SaberInReflect(self->client->ps.saberMove) &&
05190                                                         !unblockable)
05191                                                 {
05192                                                         self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
05193                                                         didOffense = qtrue;
05194                                                 }
05195                                                 if (!didDefense &&
05196                                                         !PM_SaberInParry(otherOwner->client->ps.saberMove) &&
05197                                                         !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
05198                                                         !BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
05199                                                         !PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
05200                                                         !PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
05201                                                         !PM_SaberInReflect(otherOwner->client->ps.saberMove) &&
05202                                                         !unblockable)
05203                                                 {
05204                                                         otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
05205                                                 }
05206 #ifndef FINAL_BUILD
05207                                                 if (g_saberDebugPrint.integer)
05208                                                 {
05209                                                         Com_Printf("Equal attack level bounce/deflection for clients %i and %i\n", self->s.number, otherOwner->s.number);
05210                                                 }
05211 #endif
05212 
05213                                                 self->client->ps.saberEventFlags |= SEF_DEFLECTED;
05214                                                 otherOwner->client->ps.saberEventFlags |= SEF_DEFLECTED;
05215                                         }
05216                                         else if ((level.time - otherOwner->client->lastSaberStorageTime) < 500 && !unblockable) //make sure the stored saber data is updated
05217                                         { //They are higher, this means they can actually smash us into a broken parry
05218                                                 //Using reflected anims instead now
05219                                                 self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove);
05220                                                 self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
05221 
05222                                                 if (self->client->ps.saberEntityNum) //make sure he has his saber still
05223                                                 {
05224                                                         saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner);
05225                                                 }
05226 
05227 #ifndef FINAL_BUILD
05228                                                 if (g_saberDebugPrint.integer)
05229                                                 {
05230                                                         Com_Printf("Client %i hit client %i's stronger attack, was forced into a broken parry\n", self->s.number, otherOwner->s.number);
05231                                                 }
05232 #endif
05233 
05234                                                 otherOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
05235 
05236                                                 didOffense = qtrue;
05237                                         }
05238                                 }
05239 
05240                                 if (crushTheParry && PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked)))
05241                                 { //This means that the attack actually hit our saber, and we went to block it.
05242                                   //But, one of the above cases says we actually can't. So we will be smashed into a broken parry instead.
05243                                         otherOwner->client->ps.saberMove = BG_BrokenParryForParry( G_GetParryForBlock(otherOwner->client->ps.saberBlocked) );
05244                                         otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
05245 
05246                                         otherOwner->client->ps.saberEventFlags &= ~SEF_PARRIED;
05247                                         self->client->ps.saberEventFlags &= ~SEF_BLOCKED;
05248 
05249 #ifndef FINAL_BUILD
05250                                         if (g_saberDebugPrint.integer)
05251                                         {
05252                                                 Com_Printf("Client %i broke through %i's parry with a special or stronger attack\n", self->s.number, otherOwner->s.number);
05253                                         }
05254 #endif
05255                                 }
05256                                 else if (PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked)) && !didOffense && tryDeflectAgain)
05257                                 { //We want to try deflecting again because the other is in the parry and we haven't made any new moves
05258                                         int preMove = otherOwner->client->ps.saberMove;
05259 
05260                                         otherOwner->client->ps.saberMove = G_GetParryForBlock(otherOwner->client->ps.saberBlocked);
05261                                         WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction);
05262                                         otherOwner->client->ps.saberMove = preMove;
05263                                 }
05264                         }
05265                 }
05266 
05267                 self->client->ps.saberAttackWound = level.time + g_saberDmgDelay_Wound.integer;
05268         }
05269 
05270         return didHit;
05271 }
05272 
05273 GAME_INLINE int VectorCompare2( const vec3_t v1, const vec3_t v2 ) {
05274         if ( v1[0] > v2[0]+0.0001f || v1[0] < v2[0]-0.0001f
05275                 || v1[1] > v2[1]+0.0001f || v1[1] < v2[1]-0.0001f
05276                 || v1[2] > v2[2]+0.0001f || v1[2] < v2[2]-0.0001f ) {
05277                 return 0;
05278         }                       
05279         return 1;
05280 }
05281 
05282 #define MAX_SABER_SWING_INC 0.33f
05283 void G_SPSaberDamageTraceLerped( gentity_t *self, int saberNum, int bladeNum, vec3_t baseNew, vec3_t endNew, int clipmask )
05284 {
05285         vec3_t baseOld, endOld;
05286         vec3_t mp1, mp2;
05287         vec3_t md1, md2;
05288 
05289         if ( (level.time-self->client->saber[saberNum].blade[bladeNum].trail.lastTime) > 100 )
05290         {//no valid last pos, use current
05291                 VectorCopy(baseNew, baseOld);
05292                 VectorCopy(endNew, endOld);
05293         }
05294         else
05295         {//trace from last pos
05296                 VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.base, baseOld );
05297                 VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.tip, endOld );
05298         }
05299 
05300         VectorCopy( baseOld, mp1 );
05301         VectorCopy( baseNew, mp2 );
05302         VectorSubtract( endOld, baseOld, md1 );
05303         VectorNormalize( md1 );
05304         VectorSubtract( endNew, baseNew, md2 );
05305         VectorNormalize( md2 );
05306 
05307         saberHitWall = qfalse;
05308         saberHitSaber = qfalse;
05309         saberHitFraction = 1.0f;
05310         if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
05311         {//no diff
05312                 CheckSaberDamage( self, saberNum, bladeNum, baseNew, endNew, qfalse, clipmask, qfalse );
05313         }
05314         else
05315         {//saber moved, lerp
05316                 float step = 8, stepsize = 8;//aveLength, 
05317                 vec3_t  ma1, ma2, md2ang, curBase1, curBase2;
05318                 int     xx;
05319                 vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
05320                 float dirInc, curDirFrac;
05321                 vec3_t baseDiff, bladePointOld, bladePointNew;
05322                 qboolean extrapolate = qtrue;
05323 
05324                 //do the trace at the base first
05325                 VectorCopy( baseOld, bladePointOld );
05326                 VectorCopy( baseNew, bladePointNew );
05327                 CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, qtrue );
05328 
05329                 //if hit a saber, shorten rest of traces to match
05330                 if ( saberHitFraction < 1.0f )
05331                 {
05332                         //adjust muzzleDir...
05333                         vec3_t ma1, ma2;
05334                         vectoangles( md1, ma1 );
05335                         vectoangles( md2, ma2 );
05336                         for ( xx = 0; xx < 3; xx++ )
05337                         {
05338                                 md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
05339                         }
05340                         AngleVectors( md2ang, md2, NULL, NULL );
05341                         //shorten the base pos
05342                         VectorSubtract( mp2, mp1, baseDiff );
05343                         VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
05344                         VectorMA( baseNew, self->client->saber[saberNum].blade[bladeNum].lengthMax, md2, endNew );
05345                 }
05346 
05347                 //If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
05348                 if ( BG_SaberInAttack( self->client->ps.saberMove ) 
05349                         || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) 
05350                         || BG_SpinningSaberAnim( self->client->ps.torsoAnim ) 
05351                         || BG_InSpecialJump( self->client->ps.torsoAnim ) )
05352                         //|| (g_timescale->value<1.0f&&BG_SaberInTransitionAny( ent->client->ps.saberMove )) )
05353                 {
05354                         curDirFrac = DotProduct( md1, md2 );
05355                 }
05356                 else
05357                 {
05358                         curDirFrac = 1.0f;
05359                 }
05360                 //NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
05361                 if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
05362                 {//the saber blade spun more than 33 degrees since the last damage trace
05363                         curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
05364                 }
05365                 else
05366                 {
05367                         curDirFrac = 1.0f;
05368                         dirInc = 0.0f;
05369                 }
05370                 //qboolean hit_saber = qfalse;
05371 
05372                 vectoangles( md1, ma1 );
05373                 vectoangles( md2, ma2 );
05374 
05375                 //VectorSubtract( md2, md1, mdDiff );
05376                 VectorCopy( md1, curMD2 );
05377                 VectorCopy( baseOld, curBase2 );
05378 
05379                 while ( 1 )
05380                 {
05381                         VectorCopy( curMD2, curMD1 );
05382                         VectorCopy( curBase2, curBase1 );
05383                         if ( curDirFrac >= 1.0f )
05384                         {
05385                                 VectorCopy( md2, curMD2 );
05386                                 VectorCopy( baseNew, curBase2 );
05387                         }
05388                         else
05389                         {
05390                                 for ( xx = 0; xx < 3; xx++ )
05391                                 {
05392                                         md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
05393                                 }
05394                                 AngleVectors( md2ang, curMD2, NULL, NULL );
05395                                 //VectorMA( md1, curDirFrac, mdDiff, curMD2 );
05396                                 VectorSubtract( baseNew, baseOld, baseDiff );
05397                                 VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
05398                         }
05399                         // Move up the blade in intervals of stepsize
05400                         for ( step = stepsize; step <= self->client->saber[saberNum].blade[bladeNum].lengthMax /*&& step < self->client->saber[saberNum].blade[bladeNum].lengthOld*/; step += stepsize )
05401                         {
05402                                 VectorMA( curBase1, step, curMD1, bladePointOld );
05403                                 VectorMA( curBase2, step, curMD2, bladePointNew );
05404                                 
05405                                 if ( step+stepsize >= self->client->saber[saberNum].blade[bladeNum].lengthMax )
05406                                 {
05407                                         extrapolate = qfalse;
05408                                 }
05409                                 //do the damage trace
05410                                 CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, extrapolate );
05411                                 /*
05412                                 if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2, 
05413                                         qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
05414                                         saberNum, bladeNum ) )
05415                                 {
05416                                         hit_wall = qtrue;
05417                                 }
05418                                 */
05419 
05420                                 //if hit a saber, shorten rest of traces to match
05421                                 if ( saberHitFraction < 1.0f )
05422                                 {
05423                                         vec3_t curMA1, curMA2;
05424                                         //adjust muzzle endpoint
05425                                         VectorSubtract( mp2, mp1, baseDiff );
05426                                         VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
05427                                         VectorMA( baseNew, self->client->saber[saberNum].blade[bladeNum].lengthMax, curMD2, endNew );
05428                                         //adjust muzzleDir...
05429                                         vectoangles( curMD1, curMA1 );
05430                                         vectoangles( curMD2, curMA2 );
05431                                         for ( xx = 0; xx < 3; xx++ )
05432                                         {
05433                                                 md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
05434                                         }
05435                                         AngleVectors( md2ang, curMD2, NULL, NULL );
05436                                         saberHitSaber = qtrue;
05437                                 }
05438                                 if (saberHitWall)
05439                                 {
05440                                         break;
05441                                 }
05442                         }
05443                         if ( saberHitWall || saberHitSaber )
05444                         {
05445                                 break;
05446                         }
05447                         if ( curDirFrac >= 1.0f )
05448                         {
05449                                 break;
05450                         }
05451                         else
05452                         {
05453                                 curDirFrac += dirInc;
05454                                 if ( curDirFrac >= 1.0f )
05455                                 {
05456                                         curDirFrac = 1.0f;
05457                                 }
05458                         }
05459                 }
05460 
05461                 //do the trace at the end last
05462                 //Special check- adjust for length of blade not being a multiple of 12
05463                 /*
05464                 aveLength = (ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld + ent->client->ps.saber[saberNum].blade[bladeNum].length)/2;
05465                 if ( step > aveLength )
05466                 {//less dmg if the last interval was not stepsize
05467                         tipDmgMod = (stepsize-(step-aveLength))/stepsize;
05468                 }
05469                 //NOTE: since this is the tip, we do not extrapolate the extra 16
05470                 if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2, 
05471                         qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
05472                         saberNum, bladeNum ) )
05473                 {
05474                         hit_wall = qtrue;
05475                 }
05476                 */
05477         }
05478 }
05479 
05480 #include "../namespace_begin.h"
05481 qboolean BG_SaberInTransitionAny( int move );
05482 #include "../namespace_end.h"
05483 
05484 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower );
05485 qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV );
05486 qboolean Jedi_WaitingAmbush( gentity_t *self );
05487 void Jedi_Ambush( gentity_t *self );
05488 evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist );
05489 void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
05490 void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd  )
05491 {
05492         float           dist;
05493         gentity_t       *ent, *incoming = NULL;
05494         int                     entityList[MAX_GENTITIES];
05495         int                     numListedEntities;
05496         vec3_t          mins, maxs;
05497         int                     i, e;
05498         float           closestDist, radius = 256;
05499         vec3_t          forward, dir, missile_dir, fwdangles = {0};
05500         trace_t         trace;
05501         vec3_t          traceTo, entDir;
05502         float           dot1, dot2;
05503         float           lookTDist = -1;
05504         gentity_t       *lookT = NULL;
05505         qboolean        doFullRoutine = qtrue;
05506 
05507         //keep this updated even if we don't get below
05508         if ( !(self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
05509         {//lookTarget is set by and to the monster that's holding you, no other operations can change that
05510                 self->client->ps.hasLookTarget = qfalse;
05511         }
05512 
05513         if ( self->client->ps.weapon != WP_SABER && self->client->NPC_class != CLASS_BOBAFETT )
05514         {
05515                 doFullRoutine = qfalse;
05516         }
05517         else if ( self->client->ps.saberInFlight )
05518         {
05519                 doFullRoutine = qfalse;
05520         }
05521         else if ( self->client->ps.fd.forcePowersActive&(1<<FP_LIGHTNING) )
05522         {//can't block while zapping
05523                 doFullRoutine = qfalse;
05524         }
05525         else if ( self->client->ps.fd.forcePowersActive&(1<<FP_DRAIN) )
05526         {//can't block while draining
05527                 doFullRoutine = qfalse;
05528         }
05529         else if ( self->client->ps.fd.forcePowersActive&(1<<FP_PUSH) )
05530         {//can't block while shoving
05531                 doFullRoutine = qfalse;
05532         }
05533         else if ( self->client->ps.fd.forcePowersActive&(1<<FP_GRIP) )
05534         {//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
05535                 doFullRoutine = qfalse;
05536         }
05537         
05538         if (self->client->ps.weaponTime > 0)
05539         { //don't autoblock while busy with stuff
05540                 return;
05541         }
05542 
05543         if ( (self->client->saber[0].saberFlags&SFL_NOT_ACTIVE_BLOCKING) )
05544         {//can't actively block with this saber type
05545                 return;
05546         }
05547 
05548         if ( self->health <= 0 )
05549         {//dead don't try to block (NOTE: actual deflection happens in missile code)
05550                 return;
05551         }
05552         if ( PM_InKnockDown( &self->client->ps ) )
05553         {//can't block when knocked down
05554                 return;
05555         }
05556 
05557         if ( BG_SabersOff( &self->client->ps ) && self->client->NPC_class != CLASS_BOBAFETT )
05558         {
05559                 if ( self->s.eType != ET_NPC )
05560                 {//player doesn't auto-activate
05561                         doFullRoutine = qfalse;
05562                 }
05563         }
05564 
05565         if ( self->s.eType == ET_PLAYER )
05566         {//don't do this if already attacking!
05567                 if ( ucmd->buttons & BUTTON_ATTACK 
05568                         || BG_SaberInAttack( self->client->ps.saberMove )
05569                         || BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
05570                         || BG_SaberInTransitionAny( self->client->ps.saberMove ))
05571                 {
05572                         doFullRoutine = qfalse;
05573                 }
05574         }
05575 
05576         if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
05577         {//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
05578                 doFullRoutine = qfalse;
05579         }
05580 
05581         fwdangles[1] = self->client->ps.viewangles[1];
05582         AngleVectors( fwdangles, forward, NULL, NULL );
05583 
05584         for ( i = 0 ; i < 3 ; i++ ) 
05585         {
05586                 mins[i] = self->r.currentOrigin[i] - radius;
05587                 maxs[i] = self->r.currentOrigin[i] + radius;
05588         }
05589 
05590         numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
05591 
05592         closestDist = radius;
05593 
05594         for ( e = 0 ; e < numListedEntities ; e++ ) 
05595         {
05596                 ent = &g_entities[entityList[ e ]];
05597 
05598                 if (ent == self)
05599                         continue;
05600 
05601                 //as long as we're here I'm going to get a looktarget too, I guess. -rww
05602                 if (self->s.eType == ET_PLAYER &&
05603                         ent->client &&
05604                         (ent->s.eType == ET_NPC || ent->s.eType == ET_PLAYER) &&
05605                         !OnSameTeam(ent, self) &&
05606                         ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
05607                         !(ent->client->ps.pm_flags & PMF_FOLLOW) &&
05608                         (ent->s.eType != ET_NPC || ent->s.NPC_class != CLASS_VEHICLE) && //don't look at vehicle NPCs
05609                         ent->health > 0)
05610                 { //seems like a valid enemy to look at.
05611                         vec3_t vecSub;
05612                         float vecLen;
05613 
05614                         VectorSubtract(self->client->ps.origin, ent->client->ps.origin, vecSub);
05615                         vecLen = VectorLength(vecSub);
05616 
05617                         if (lookTDist == -1 || vecLen < lookTDist)
05618                         {
05619                                 trace_t tr;
05620                                 vec3_t myEyes;
05621 
05622                                 VectorCopy(self->client->ps.origin, myEyes);
05623                                 myEyes[2] += self->client->ps.viewheight;
05624 
05625                                 trap_Trace(&tr, myEyes, NULL, NULL, ent->client->ps.origin, self->s.number, MASK_PLAYERSOLID);
05626 
05627                                 if (tr.fraction == 1.0f || tr.entityNum == ent->s.number)
05628                                 { //we have a clear line of sight to him, so it's all good.
05629                                         lookT = ent;
05630                                         lookTDist = vecLen;
05631                                 }
05632                         }
05633                 }
05634 
05635                 if (!doFullRoutine)
05636                 { //don't care about the rest then
05637                         continue;
05638                 }
05639 
05640                 if (ent->r.ownerNum == self->s.number)
05641                         continue;
05642                 if ( !(ent->inuse) )
05643                         continue;
05644                 if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
05645                 {//not a normal projectile
05646                         gentity_t *pOwner;
05647 
05648                         if (ent->r.ownerNum < 0 || ent->r.ownerNum >= ENTITYNUM_WORLD)
05649                         { //not going to be a client then.
05650                                 continue;
05651                         }
05652                                 
05653                         pOwner = &g_entities[ent->r.ownerNum];
05654 
05655                         if (!pOwner->inuse || !pOwner->client)
05656                         {
05657                                 continue; //not valid cl owner
05658                         }
05659 
05660                         if (!pOwner->client->ps.saberEntityNum ||
05661                                 !pOwner->client->ps.saberInFlight ||
05662                                 pOwner->client->ps.saberEntityNum != ent->s.number)
05663                         { //the saber is knocked away and/or not flying actively, or this ent is not the cl's saber ent at all
05664                                 continue;
05665                         }
05666 
05667                         //If we get here then it's ok to be treated as a thrown saber, I guess.
05668                 }
05669                 else
05670                 {
05671                         if ( ent->s.pos.trType == TR_STATIONARY && self->s.eType == ET_PLAYER )
05672                         {//nothing you can do with a stationary missile if you're the player
05673                                 continue;
05674                         }
05675                 }
05676 
05677                 //see if they're in front of me
05678                 VectorSubtract( ent->r.currentOrigin, self->r.currentOrigin, dir );
05679                 dist = VectorNormalize( dir );
05680                 //FIXME: handle detpacks, proximity mines and tripmines
05681                 if ( ent->s.weapon == WP_THERMAL )
05682                 {//thermal detonator!
05683                         if ( self->NPC && dist < ent->splashRadius )
05684                         {
05685                                 if ( dist < ent->splashRadius && 
05686                                         ent->nextthink < level.time + 600 && 
05687                                         ent->count && 
05688                                         self->client->ps.groundEntityNum != ENTITYNUM_NONE && 
05689                                                 (ent->s.pos.trType == TR_STATIONARY||
05690                                                 ent->s.pos.trType == TR_INTERPOLATE||
05691                                                 (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
05692                                                 !WP_ForcePowerUsable( self, FP_PUSH )) )
05693                                 {//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!
05694                                         //FIXME: sometimes this might make me just jump into it...?
05695                                         self->client->ps.fd.forceJumpCharge = 480;
05696                                 }
05697                                 else if ( self->client->NPC_class != CLASS_BOBAFETT )
05698                                 {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
05699                                         ForceThrow( self, qfalse );
05700                                 }
05701                         }
05702                         continue;
05703                 }
05704                 else if ( ent->splashDamage && ent->splashRadius )
05705                 {//exploding missile
05706                         //FIXME: handle tripmines and detpacks somehow... 
05707                         //                      maybe do a force-gesture that makes them explode?  
05708                         //                      But what if we're within it's splashradius?
05709                         if ( self->s.eType == ET_PLAYER )
05710                         {//players don't auto-handle these at all
05711                                 continue;
05712                         }
05713                         else 
05714                         {
05715                                 //if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) 
05716                                 //      &&      self->client->NPC_class != CLASS_BOBAFETT )
05717                                 if (0) //Maybe handle this later?
05718                                 {//a placed explosive like a tripmine or detpack
05719                                         if ( InFOV3( ent->r.currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
05720                                         {//in front of me
05721                                                 if ( G_ClearLOS4( self, ent ) )
05722                                                 {//can see it
05723                                                         vec3_t throwDir;
05724                                                         //make the gesture
05725                                                         ForceThrow( self, qfalse );
05726                                                         //take it off the wall and toss it
05727                                                         ent->s.pos.trType = TR_GRAVITY;
05728                                                         ent->s.eType = ET_MISSILE;
05729                                                         ent->s.eFlags &= ~EF_MISSILE_STICK;
05730                                                         ent->flags |= FL_BOUNCE_HALF;
05731                                                         AngleVectors( ent->r.currentAngles, throwDir, NULL, NULL );
05732                                                         VectorMA( ent->r.currentOrigin, ent->r.maxs[0]+4, throwDir, ent->r.currentOrigin );
05733                                                         VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
05734                                                         VectorScale( throwDir, 300, ent->s.pos.trDelta );
05735                                                         ent->s.pos.trDelta[2] += 150;
05736                                                         VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
05737                                                         ent->s.pos.trTime = level.time;         // move a bit on the very first frame
05738                                                         VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
05739                                                         ent->r.ownerNum = self->s.number;
05740                                                         // make it explode, but with less damage
05741                                                         ent->splashDamage /= 3;
05742                                                         ent->splashRadius /= 3;
05743                                                         //ent->think = WP_Explode;
05744                                                         ent->nextthink = level.time + Q_irand( 500, 3000 );
05745                                                 }
05746                                         }
05747                                 }
05748                                 else if ( dist < ent->splashRadius && 
05749                                 self->client->ps.groundEntityNum != ENTITYNUM_NONE && 
05750                                         (DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE||
05751                                         !WP_ForcePowerUsable( self, FP_PUSH )) )
05752                                 {//NPCs try to evade it
05753                                         self->client->ps.fd.forceJumpCharge = 480;
05754                                 }
05755                                 else if ( self->client->NPC_class != CLASS_BOBAFETT )
05756                                 {//else, try to force-throw it away
05757                                         //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
05758                                         ForceThrow( self, qfalse );
05759                                 }
05760                         }
05761                         //otherwise, can't block it, so we're screwed
05762                         continue;
05763                 }
05764 
05765                 if ( ent->s.weapon != WP_SABER )
05766                 {//only block shots coming from behind
05767                         if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
05768                                 continue;
05769                 }
05770                 else if ( self->s.eType == ET_PLAYER )
05771                 {//player never auto-blocks thrown sabers
05772                         continue;
05773                 }//NPCs always try to block sabers coming from behind!
05774 
05775                 //see if they're heading towards me
05776                 VectorCopy( ent->s.pos.trDelta, missile_dir );
05777                 VectorNormalize( missile_dir );
05778                 if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
05779                         continue;
05780 
05781                 //FIXME: must have a clear trace to me, too...
05782                 if ( dist < closestDist )
05783                 {
05784                         VectorCopy( self->r.currentOrigin, traceTo );
05785                         traceTo[2] = self->r.absmax[2] - 4;
05786                         trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, traceTo, ent->s.number, ent->clipmask );
05787                         if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
05788                         {//okay, try one more check
05789                                 VectorNormalize2( ent->s.pos.trDelta, entDir );
05790                                 VectorMA( ent->r.currentOrigin, radius, entDir, traceTo );
05791                                 trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, traceTo, ent->s.number, ent->clipmask );
05792                                 if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
05793                                 {//can't hit me, ignore it
05794                                         continue;
05795                                 }
05796                         }
05797                         if ( self->s.eType == ET_NPC )
05798                         {//An NPC
05799                                 if ( self->NPC && !self->enemy && ent->r.ownerNum != ENTITYNUM_NONE )
05800                                 {
05801                                         gentity_t *owner = &g_entities[ent->r.ownerNum];
05802                                         if ( owner->health >= 0 && (!owner->client || owner->client->playerTeam != self->client->playerTeam) )
05803                                         {
05804                                                 G_SetEnemy( self, owner );
05805                                         }
05806                                 }
05807                         }
05808                         //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?
05809                         closestDist = dist;
05810                         incoming = ent;
05811                 }
05812         }
05813 
05814         if (self->s.eType == ET_NPC && self->localAnimIndex <= 1)
05815         { //humanoid NPCs don't set angles based on server angles for looking, unlike other NPCs
05816                 if (self->client && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD)
05817                 {
05818                         lookT = &g_entities[self->client->renderInfo.lookTarget];
05819                 }
05820         }
05821 
05822         if (lookT)
05823         { //we got a looktarget at some point so we'll assign it then.
05824                 if ( !(self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
05825                 {//lookTarget is set by and to the monster that's holding you, no other operations can change that
05826                         self->client->ps.hasLookTarget = qtrue;
05827                         self->client->ps.lookTarget = lookT->s.number;
05828                 }
05829         }
05830 
05831         if (!doFullRoutine)
05832         { //then we're done now
05833                 return;
05834         }
05835 
05836         if ( incoming )
05837         {
05838                 if ( self->NPC /*&& !G_ControlledByPlayer( self )*/ )
05839                 {
05840                         if ( Jedi_WaitingAmbush( self ) )
05841                         {
05842                                 Jedi_Ambush( self );
05843                         }
05844                         if ( self->client->NPC_class == CLASS_BOBAFETT 
05845                                 && (self->client->ps.eFlags2&EF2_FLYING)//moveType == MT_FLYSWIM 
05846                                 && incoming->methodOfDeath != MOD_ROCKET_HOMING )
05847                         {//a hovering Boba Fett, not a tracking rocket
05848                                 if ( !Q_irand( 0, 1 ) )
05849                                 {//strafe
05850                                         self->NPC->standTime = 0;
05851                                         self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
05852                                 }
05853                                 if ( !Q_irand( 0, 1 ) )
05854                                 {//go up/down
05855                                         TIMER_Set( self, "heightChange", Q_irand( 1000, 3000 ) );
05856                                         self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
05857                                 }
05858                         }
05859                         else if ( Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming, 0.0f ) != EVASION_NONE )
05860                         {//make sure to turn on your saber if it's not on
05861                                 if ( self->client->NPC_class != CLASS_BOBAFETT )
05862                                 {
05863                                         //self->client->ps.SaberActivate();
05864                                         WP_ActivateSaber(self);
05865                                 }
05866                         }
05867                 }
05868                 else//player
05869                 {
05870                         gentity_t *owner = &g_entities[incoming->r.ownerNum];
05871 
05872                         WP_SaberBlockNonRandom( self, incoming->r.currentOrigin, qtrue );
05873                         if ( owner && owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
05874                         {
05875                                 self->enemy = owner;
05876                                 //NPC_SetLookTarget( self, owner->s.number, level.time+1000 );
05877                                 //player looktargetting done differently
05878                         }
05879                 }
05880         }
05881 }
05882 
05883 #define MIN_SABER_SLICE_DISTANCE 50
05884 
05885 #define MIN_SABER_SLICE_RETURN_DISTANCE 30
05886 
05887 #define SABER_THROWN_HIT_DAMAGE 30
05888 #define SABER_THROWN_RETURN_HIT_DAMAGE 5
05889 
05890 void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace);
05891 
05892 static GAME_INLINE qboolean CheckThrownSaberDamaged(gentity_t *saberent, gentity_t *saberOwner, gentity_t *ent, int dist, int returning, qboolean noDCheck)
05893 {
05894         vec3_t vecsub;
05895         float veclen;
05896         gentity_t *te;
05897 
05898         if (saberOwner && saberOwner->client && saberOwner->client->ps.saberAttackWound > level.time)
05899         {
05900                 return qfalse;
05901         }
05902 
05903         if (ent && ent->client && ent->inuse && ent->s.number != saberOwner->s.number &&
05904                 ent->health > 0 && ent->takedamage &&
05905                 trap_InPVS(ent->client->ps.origin, saberent->r.currentOrigin) &&
05906                 ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
05907                 (ent->client->pers.connected || ent->s.eType == ET_NPC))
05908         { //hit a client
05909                 if (ent->inuse && ent->client &&
05910                         ent->client->ps.duelInProgress &&
05911                         ent->client->ps.duelIndex != saberOwner->s.number)
05912                 {
05913                         return qfalse;
05914                 }
05915 
05916                 if (ent->inuse && ent->client &&
05917                         saberOwner->client->ps.duelInProgress &&
05918                         saberOwner->client->ps.duelIndex != ent->s.number)
05919                 {
05920                         return qfalse;
05921                 }
05922 
05923                 VectorSubtract(saberent->r.currentOrigin, ent->client->ps.origin, vecsub);
05924                 veclen = VectorLength(vecsub);
05925 
05926                 if (veclen < dist)
05927                 { //within range
05928                         trace_t tr;
05929 
05930                         trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, ent->client->ps.origin, saberent->s.number, MASK_SHOT);
05931 
05932                         if (tr.fraction == 1 || tr.entityNum == ent->s.number)
05933                         { //Slice them
05934                                 if (!saberOwner->client->ps.isJediMaster && WP_SaberCanBlock(ent, tr.endpos, 0, MOD_SABER, qfalse, 999))
05935                                 { //they blocked it
05936                                         WP_SaberBlockNonRandom(ent, tr.endpos, qfalse);
05937 
05938                                         te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
05939                                         VectorCopy(tr.endpos, te->s.origin);
05940                                         VectorCopy(tr.plane.normal, te->s.angles);
05941                                         if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
05942                                         {
05943                                                 te->s.angles[1] = 1;
05944                                         }
05945                                         te->s.eventParm = 1;
05946                                         te->s.weapon = 0;//saberNum
05947                                         te->s.legsAnim = 0;//bladeNum
05948 
05949                                         if (saberCheckKnockdown_Thrown(saberent, saberOwner, &g_entities[tr.entityNum]))
05950                                         { //it was knocked out of the air
05951                                                 return qfalse;
05952                                         }
05953 
05954                                         if (!returning)
05955                                         { //return to owner if blocked
05956                                                 thrownSaberTouch(saberent, saberent, NULL);
05957                                         }
05958 
05959                                         saberOwner->client->ps.saberAttackWound = level.time + 500;
05960                                         return qfalse;
05961                                 }
05962                                 else
05963                                 { //a good hit
05964                                         vec3_t dir;
05965                                         int dflags = 0;
05966 
05967                                         VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
05968                                         VectorNormalize(dir);
05969 
05970                                         if (!dir[0] && !dir[1] && !dir[2])
05971                                         {
05972                                                 dir[1] = 1;
05973                                         }
05974 
05975                                         if ( (saberOwner->client->saber[0].saberFlags2&SFL2_NO_DISMEMBERMENT) )
05976                                         {
05977                                                 dflags |= DAMAGE_NO_DISMEMBER;
05978                                         }
05979 
05980                                         if ( saberOwner->client->saber[0].knockbackScale > 0.0f )
05981                                         {
05982                                                 dflags |= DAMAGE_SABER_KNOCKBACK1;
05983                                         }
05984 
05985                                         if (saberOwner->client->ps.isJediMaster)
05986                                         { //2x damage for the Jedi Master
05987                                                 G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage*2, dflags, MOD_SABER);
05988                                         }
05989                                         else
05990                                         {
05991                                                 G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage, dflags, MOD_SABER);
05992                                         }
05993 
05994                                         te = G_TempEntity( tr.endpos, EV_SABER_HIT );
05995                                         te->s.otherEntityNum = ent->s.number;
05996                                         te->s.otherEntityNum2 = saberOwner->s.number;
05997                                         te->s.weapon = 0;//saberNum
05998                                         te->s.legsAnim = 0;//bladeNum
05999                                         VectorCopy(tr.endpos, te->s.origin);
06000                                         VectorCopy(tr.plane.normal, te->s.angles);
06001                                         if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
06002                                         {
06003                                                 te->s.angles[1] = 1;
06004                                         }
06005 
06006                                         te->s.eventParm = 1;
06007 
06008                                         if (!returning)
06009                                         { //return to owner if blocked
06010                                                 thrownSaberTouch(saberent, saberent, NULL);
06011                                         }
06012                                 }
06013 
06014                                 saberOwner->client->ps.saberAttackWound = level.time + 500;
06015                         }
06016                 }
06017         }
06018         else if (ent && !ent->client && ent->inuse && ent->takedamage && ent->health > 0 && ent->s.number != saberOwner->s.number &&
06019                 ent->s.number != saberent->s.number && (noDCheck ||trap_InPVS(ent->r.currentOrigin, saberent->r.currentOrigin)))
06020         { //hit a non-client
06021 
06022                 if (noDCheck)
06023                 {
06024                         veclen = 0;
06025                 }
06026                 else
06027                 {
06028                         VectorSubtract(saberent->r.currentOrigin, ent->r.currentOrigin, vecsub);
06029                         veclen = VectorLength(vecsub);
06030                 }
06031 
06032                 if (veclen < dist)
06033                 {
06034                         trace_t tr;
06035                         vec3_t entOrigin;
06036 
06037                         if (ent->s.eType == ET_MOVER)
06038                         {
06039                                 VectorSubtract( ent->r.absmax, ent->r.absmin, entOrigin );
06040                                 VectorMA( ent->r.absmin, 0.5, entOrigin, entOrigin );
06041                                 VectorAdd( ent->r.absmin, ent->r.absmax, entOrigin );
06042                                 VectorScale( entOrigin, 0.5f, entOrigin );
06043                         }
06044                         else
06045                         {
06046                                 VectorCopy(ent->r.currentOrigin, entOrigin);
06047                         }
06048 
06049                         trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, entOrigin, saberent->s.number, MASK_SHOT);
06050 
06051                         if (tr.fraction == 1 || tr.entityNum == ent->s.number)
06052                         {
06053                                 vec3_t dir;
06054                                 int dflags = 0;
06055 
06056                                 VectorSubtract(tr.endpos, entOrigin, dir);
06057                                 VectorNormalize(dir);
06058 
06059                                 if ( (saberOwner->client->saber[0].saberFlags2&SFL2_NO_DISMEMBERMENT) )
06060                                 {
06061                                         dflags |= DAMAGE_NO_DISMEMBER;
06062                                 }
06063                                 if ( saberOwner->client->saber[0].knockbackScale > 0.0f )
06064                                 {
06065                                         dflags |= DAMAGE_SABER_KNOCKBACK1;
06066                                 }
06067 
06068                                 if (ent->s.eType == ET_NPC)
06069                                 { //an animent
06070                                         G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 40, dflags, MOD_SABER);
06071                                 }
06072                                 else
06073                                 {
06074                                         G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 5, dflags, MOD_SABER);
06075                                 }
06076 
06077                                 te = G_TempEntity( tr.endpos, EV_SABER_HIT );
06078                                 te->s.otherEntityNum = ENTITYNUM_NONE; //don't do this for throw damage
06079                                 //te->s.otherEntityNum = ent->s.number;
06080                                 te->s.otherEntityNum2 = saberOwner->s.number;//actually, do send this, though - for the overridden per-saber hit effects/sounds
06081                                 te->s.weapon = 0;//saberNum
06082                                 te->s.legsAnim = 0;//bladeNum
06083                                 VectorCopy(tr.endpos, te->s.origin);
06084                                 VectorCopy(tr.plane.normal, te->s.angles);
06085                                 if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
06086                                 {
06087                                         te->s.angles[1] = 1;
06088                                 }
06089 
06090                                 if ( ent->s.eType == ET_MOVER )
06091                                 {
06092                                         if ( saberOwner
06093                                                 && saberOwner->client
06094                                                 && (saberOwner->client->saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) ) 
06095                                         {//don't do clash flare - NOTE: assumes same is true for both sabers if using dual sabers!
06096                                                 G_FreeEntity( te );//kind of a waste, but...
06097                                         }
06098                                         else
06099                                         {
06100                                                 //I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing.
06101                                                 gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE );
06102                                                 VectorCopy(te->s.origin, teS->s.origin);
06103 
06104                                                 te->s.eventParm = 0;
06105                                         }
06106                                 }
06107                                 else
06108                                 {
06109                                         te->s.eventParm = 1;
06110                                 }
06111 
06112                                 if (!returning)
06113                                 { //return to owner if blocked
06114                                         thrownSaberTouch(saberent, saberent, NULL);
06115                                 }
06116 
06117                                 saberOwner->client->ps.saberAttackWound = level.time + 500;
06118                         }
06119                 }
06120         }
06121 
06122         return qtrue;
06123 }
06124 
06125 static GAME_INLINE void saberCheckRadiusDamage(gentity_t *saberent, int returning)
06126 { //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)
06127         int i = 0;
06128         int dist = 0;
06129         gentity_t *ent;
06130         gentity_t *saberOwner = &g_entities[saberent->r.ownerNum];
06131 
06132         if (returning && returning != 2)
06133         {
06134                 dist = MIN_SABER_SLICE_RETURN_DISTANCE;
06135         }
06136         else
06137         {
06138                 dist = MIN_SABER_SLICE_DISTANCE;
06139         }
06140 
06141         if (!saberOwner || !saberOwner->client)
06142         {
06143                 return;
06144         }
06145 
06146         if (saberOwner->client->ps.saberAttackWound > level.time)
06147         {
06148                 return;
06149         }
06150 
06151         while (i < level.num_entities)
06152         {
06153                 ent = &g_entities[i];
06154 
06155                 CheckThrownSaberDamaged(saberent, saberOwner, ent, dist, returning, qfalse);
06156 
06157                 i++;
06158         }
06159 }
06160 
06161 #define THROWN_SABER_COMP
06162 
06163 static GAME_INLINE void saberMoveBack( gentity_t *ent, qboolean goingBack ) 
06164 {
06165         vec3_t          origin, oldOrg;
06166 
06167         ent->s.pos.trType = TR_LINEAR;
06168 
06169         VectorCopy( ent->r.currentOrigin, oldOrg );
06170         // get current position
06171         BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
06172         //Get current angles?
06173         BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles );
06174 
06175         //compensation test code..
06176 #ifdef THROWN_SABER_COMP
06177         if (!goingBack && ent->s.pos.trType != TR_GRAVITY)
06178         { //acts as a fallback in case touch code fails, keeps saber from going through things between predictions
06179                 float originalLength = 0;
06180                 int iCompensationLength = 32;
06181                 trace_t tr;
06182                 vec3_t mins, maxs;
06183                 vec3_t calcComp, compensatedOrigin;
06184                 VectorSet( mins, -24.0f, -24.0f, -8.0f );
06185                 VectorSet( maxs, 24.0f, 24.0f, 8.0f );
06186 
06187                 VectorSubtract(origin, oldOrg, calcComp);
06188                 originalLength = VectorLength(calcComp);
06189 
06190                 VectorNormalize(calcComp);
06191 
06192                 compensatedOrigin[0] = oldOrg[0] + calcComp[0]*(originalLength+iCompensationLength);            
06193                 compensatedOrigin[1] = oldOrg[1] + calcComp[1]*(originalLength+iCompensationLength);
06194                 compensatedOrigin[2] = oldOrg[2] + calcComp[2]*(originalLength+iCompensationLength);
06195 
06196                 trap_Trace(&tr, oldOrg, mins, maxs, compensatedOrigin, ent->r.ownerNum, MASK_PLAYERSOLID);
06197 
06198                 if ((tr.fraction != 1 || tr.startsolid || tr.allsolid) && tr.entityNum != ent->r.ownerNum && !(g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER))
06199                 {
06200                         VectorClear(ent->s.pos.trDelta);
06201 
06202                         //Unfortunately doing this would defeat the purpose of the compensation. We will have to settle for a jerk on the client.
06203                         //VectorCopy( origin, ent->r.currentOrigin );
06204 
06205                         //we'll skip the dist check, since we don't really care about that (we just hit it physically)
06206                         CheckThrownSaberDamaged(ent, &g_entities[ent->r.ownerNum], &g_entities[tr.entityNum], 256, 0, qtrue);
06207 
06208                         if (ent->s.pos.trType == TR_GRAVITY)
06209                         { //got blocked and knocked away in the damage func
06210                                 return;
06211                         }
06212 
06213                         tr.startsolid = 0;
06214                         if (tr.entityNum == ENTITYNUM_NONE)
06215                         { //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)
06216                                 tr.entityNum = ENTITYNUM_WORLD;
06217                         }
06218                         thrownSaberTouch(ent, &g_entities[tr.entityNum], &tr);
06219                         return;
06220                 }
06221         }
06222 #endif
06223 
06224         VectorCopy( origin, ent->r.currentOrigin );
06225 }
06226 
06227 void SaberBounceSound( gentity_t *self, gentity_t *other, trace_t *trace )
06228 {
06229         VectorCopy(self->r.currentAngles, self->s.apos.trBase);
06230         self->s.apos.trBase[PITCH] = 90;
06231 }
06232 
06233 void DeadSaberThink(gentity_t *saberent)
06234 {
06235         if (saberent->speed < level.time)
06236         {
06237                 saberent->think = G_FreeEntity;
06238                 saberent->nextthink = level.time;
06239                 return;
06240         }
06241 
06242         G_RunObject(saberent);
06243 }
06244 
06245 void MakeDeadSaber(gentity_t *ent)
06246 {       //spawn a "dead" saber entity here so it looks like the saber fell out of the air.
06247         //This entity will remove itself after a very short time period.
06248         vec3_t startorg;
06249         vec3_t startang;
06250         gentity_t *saberent;
06251         gentity_t *owner = NULL;
06252         
06253         if (g_gametype.integer == GT_JEDIMASTER)
06254         { //never spawn a dead saber in JM, because the only saber on the level is really a world object
06255                 //G_Sound(ent, CHAN_AUTO, saberOffSound);
06256                 return;
06257         }
06258 
06259         saberent = G_Spawn();
06260 
06261         VectorCopy(ent->r.currentOrigin, startorg);
06262         VectorCopy(ent->r.currentAngles, startang);
06263 
06264         saberent->classname = "deadsaber";
06265                         
06266         saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
06267         saberent->r.ownerNum = ent->s.number;
06268 
06269         saberent->clipmask = MASK_PLAYERSOLID;
06270         saberent->r.contents = CONTENTS_TRIGGER;//0;
06271 
06272         VectorSet( saberent->r.mins, -3.0f, -3.0f, -1.5f );
06273         VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f );
06274 
06275         saberent->touch = SaberBounceSound;
06276 
06277         saberent->think = DeadSaberThink;
06278         saberent->nextthink = level.time;
06279 
06280         VectorCopy(startorg, saberent->s.pos.trBase);
06281         VectorCopy(startang, saberent->s.apos.trBase);
06282 
06283         VectorCopy(startorg, saberent->s.origin);
06284         VectorCopy(startang, saberent->s.angles);
06285 
06286         VectorCopy(startorg, saberent->r.currentOrigin);
06287         VectorCopy(startang, saberent->r.currentAngles);
06288 
06289         saberent->s.apos.trType = TR_GRAVITY;
06290         saberent->s.apos.trDelta[0] = Q_irand(200, 800);
06291         saberent->s.apos.trDelta[1] = Q_irand(200, 800);
06292         saberent->s.apos.trDelta[2] = Q_irand(200, 800);
06293         saberent->s.apos.trTime = level.time-50;
06294 
06295         saberent->s.pos.trType = TR_GRAVITY;
06296         saberent->s.pos.trTime = level.time-50;
06297         saberent->flags = FL_BOUNCE_HALF;
06298         if (ent->r.ownerNum >= 0 && ent->r.ownerNum < ENTITYNUM_WORLD)
06299         {
06300                 owner = &g_entities[ent->r.ownerNum];
06301 
06302                 if (owner->inuse && owner->client &&
06303                         owner->client->saber[0].model[0])
06304                 {
06305                         WP_SaberAddG2Model( saberent, owner->client->saber[0].model, owner->client->saber[0].skin );
06306                 }
06307                 else
06308                 {
06309                         //WP_SaberAddG2Model( saberent, NULL, 0 );
06310                         //argh!!!!
06311                         G_FreeEntity(saberent);
06312                         return;
06313                 }
06314         }
06315 
06316         saberent->s.modelGhoul2 = 1;
06317         saberent->s.g2radius = 20;
06318 
06319         saberent->s.eType = ET_MISSILE;
06320         saberent->s.weapon = WP_SABER;
06321 
06322         saberent->speed = level.time + 4000;
06323 
06324         saberent->bounceCount = 12;
06325 
06326         //fall off in the direction the real saber was headed
06327         VectorCopy(ent->s.pos.trDelta, saberent->s.pos.trDelta);
06328 
06329         saberMoveBack(saberent, qtrue);
06330         saberent->s.pos.trType = TR_GRAVITY;
06331 
06332         trap_LinkEntity(saberent);      
06333 }
06334 
06335 #define MAX_LEAVE_TIME 20000
06336 
06337 void saberReactivate(gentity_t *saberent, gentity_t *saberOwner);
06338 void saberBackToOwner(gentity_t *saberent);
06339 
06340 void DownedSaberThink(gentity_t *saberent)
06341 {
06342         gentity_t *saberOwn = NULL;
06343         qboolean notDisowned = qfalse;
06344         qboolean pullBack = qfalse;
06345 
06346         saberent->nextthink = level.time;
06347 
06348         if (saberent->r.ownerNum == ENTITYNUM_NONE)
06349         {
06350                 MakeDeadSaber(saberent);
06351 
06352                 saberent->think = G_FreeEntity;
06353                 saberent->nextthink = level.time;
06354                 return;
06355         }
06356 
06357         saberOwn = &g_entities[saberent->r.ownerNum];
06358 
06359         if (!saberOwn ||
06360                 !saberOwn->inuse ||
06361                 !saberOwn->client ||
06362                 saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR ||
06363                 (saberOwn->client->ps.pm_flags & PMF_FOLLOW))
06364         {
06365                 MakeDeadSaber(saberent);
06366 
06367                 saberent->think = G_FreeEntity;
06368                 saberent->nextthink = level.time;
06369                 return;
06370         }
06371 
06372         if (saberOwn->client->ps.saberEntityNum)
06373         {
06374                 if (saberOwn->client->ps.saberEntityNum == saberent->s.number)
06375                 { //owner shouldn't have this set if we're thinking in here. Must've fallen off a cliff and instantly respawned or something.
06376                         notDisowned = qtrue;
06377                 }
06378                 else
06379                 { //This should never happen, but just in case..
06380                         assert(!"ULTRA BAD THING");
06381                         MakeDeadSaber(saberent);
06382 
06383                         saberent->think = G_FreeEntity;
06384                         saberent->nextthink = level.time;
06385                         return;
06386                 }
06387         }
06388 
06389         if (notDisowned || saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
06390         { //He's dead, just go back to our normal saber status
06391                 saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex;
06392 
06393                 //MakeDeadSaber(saberent); //spawn a dead saber on top of where we are now. The "bodyqueue" method.
06394                 //Actually this will get taken care of when the thrown saber func sees we're dead.
06395 
06396 #ifdef _DEBUG
06397                 if (saberOwn->client->saberStoredIndex != saberent->s.number)
06398                 { //I'm paranoid.
06399                         assert(!"Bad saber index!!!");
06400                 }
06401 #endif
06402 
06403                 saberReactivate(saberent, saberOwn);
06404 
06405                 if (saberOwn->health < 1)
06406                 {
06407                         saberOwn->client->ps.saberInFlight = qfalse;
06408                         MakeDeadSaber(saberent);
06409                 }
06410 
06411                 saberent->touch = SaberGotHit;
06412                 saberent->think = SaberUpdateSelf;
06413                 saberent->genericValue5 = 0;
06414                 saberent->nextthink = level.time;
06415 
06416                 saberent->r.svFlags |= (SVF_NOCLIENT);
06417                 //saberent->r.contents = CONTENTS_LIGHTSABER;
06418                 saberent->s.loopSound = 0;
06419                 saberent->s.loopIsSoundset = qfalse;
06420 
06421                 if (saberOwn->health > 0)
06422                 { //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.
06423                         saberOwn->client->ps.saberInFlight = qfalse;
06424                         WP_SaberRemoveG2Model( saberent );
06425                 }
06426                 saberOwn->client->ps.saberEntityState = 0;
06427                 saberOwn->client->ps.saberThrowDelay = level.time + 500;
06428                 saberOwn->client->ps.saberCanThrow = qfalse;
06429 
06430                 return;
06431         }
06432 
06433         if (saberOwn->client->saberKnockedTime < level.time && (saberOwn->client->pers.cmd.buttons & BUTTON_ATTACK))
06434         { //He wants us back
06435                 pullBack = qtrue;
06436         }
06437         else if ((level.time - saberOwn->client->saberKnockedTime) > MAX_LEAVE_TIME)
06438         { //Been sitting around for too long, go back no matter what he wants.
06439                 pullBack = qtrue;
06440         }
06441 
06442         if (pullBack)
06443         { //Get going back to the owner.
06444                 saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex;
06445 
06446 #ifdef _DEBUG
06447                 if (saberOwn->client->saberStoredIndex != saberent->s.number)
06448                 { //I'm paranoid.
06449                         assert(!"Bad saber index!!!");
06450                 }
06451 #endif
06452                 saberReactivate(saberent, saberOwn);
06453 
06454                 saberent->touch = SaberGotHit;
06455 
06456                 saberent->think = saberBackToOwner;
06457                 saberent->speed = 0;
06458                 saberent->genericValue5 = 0;
06459                 saberent->nextthink = level.time;
06460 
06461                 saberent->r.contents = CONTENTS_LIGHTSABER;
06462 
06463                 G_Sound( saberOwn, CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
06464                 if (saberOwn->client->saber[0].soundOn)
06465                 {
06466                         G_Sound( saberent, CHAN_BODY, saberOwn->client->saber[0].soundOn );
06467                 }
06468                 if (saberOwn->client->saber[1].soundOn)
06469                 {
06470                         G_Sound( saberOwn, CHAN_BODY, saberOwn->client->saber[1].soundOn );
06471                 }
06472 
06473                 return;
06474         }
06475 
06476         G_RunObject(saberent);
06477         saberent->nextthink = level.time;
06478 }
06479 
06480 void saberReactivate(gentity_t *saberent, gentity_t *saberOwner)
06481 {
06482         saberent->s.saberInFlight = qtrue;
06483 
06484         saberent->s.apos.trType = TR_LINEAR;
06485         saberent->s.apos.trDelta[0] = 0;
06486         saberent->s.apos.trDelta[1] = 800;
06487         saberent->s.apos.trDelta[2] = 0;
06488 
06489         saberent->s.pos.trType = TR_LINEAR;
06490         saberent->s.eType = ET_GENERAL;
06491         saberent->s.eFlags = 0;
06492 
06493         saberent->parent = saberOwner;
06494 
06495         saberent->genericValue5 = 0;
06496 
06497         SetSaberBoxSize(saberent);
06498 
06499         saberent->touch = thrownSaberTouch;
06500 
06501         saberent->s.weapon = WP_SABER;
06502 
06503         saberOwner->client->ps.saberEntityState = 1;
06504 
06505         trap_LinkEntity(saberent);
06506 }
06507 
06508 #define SABER_RETRIEVE_DELAY 3000 //3 seconds for now. This will leave you nice and open if you lose your saber.
06509 
06510 void saberKnockDown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
06511 {
06512         saberOwner->client->ps.saberEntityNum = 0; //still stored in client->saberStoredIndex
06513         saberOwner->client->saberKnockedTime = level.time + SABER_RETRIEVE_DELAY;
06514 
06515         saberent->clipmask = MASK_SOLID;
06516         saberent->r.contents = CONTENTS_TRIGGER;//0;
06517 
06518         VectorSet( saberent->r.mins, -3.0f, -3.0f, -1.5f );
06519         VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f );
06520 
06521         saberent->s.apos.trType = TR_GRAVITY;
06522         saberent->s.apos.trDelta[0] = Q_irand(200, 800);
06523         saberent->s.apos.trDelta[1] = Q_irand(200, 800);
06524         saberent->s.apos.trDelta[2] = Q_irand(200, 800);
06525         saberent->s.apos.trTime = level.time-50;
06526 
06527         saberent->s.pos.trType = TR_GRAVITY;
06528         saberent->s.pos.trTime = level.time-50;
06529         saberent->flags |= FL_BOUNCE_HALF;
06530 
06531         WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin );
06532 
06533         saberent->s.modelGhoul2 = 1;
06534         saberent->s.g2radius = 20;
06535 
06536         saberent->s.eType = ET_MISSILE;
06537         saberent->s.weapon = WP_SABER;
06538 
06539         saberent->speed = level.time + 4000;
06540 
06541         saberent->bounceCount = -5;//8;
06542 
06543         saberMoveBack(saberent, qtrue);
06544         saberent->s.pos.trType = TR_GRAVITY;
06545 
06546         saberent->s.loopSound = 0; //kill this in case it was spinning.
06547         saberent->s.loopIsSoundset = qfalse;
06548 
06549         saberent->r.svFlags &= ~(SVF_NOCLIENT); //make sure the client is getting updates on where it is and such.
06550 
06551         saberent->touch = SaberBounceSound;
06552         saberent->think = DownedSaberThink;
06553         saberent->nextthink = level.time;
06554 
06555         if (saberOwner != other)
06556         { //if someone knocked it out of the air and it wasn't turned off, go in the direction they were facing.
06557                 if (other->inuse && other->client)
06558                 {
06559                         vec3_t otherFwd;
06560                         float deflectSpeed = 200;
06561 
06562                         AngleVectors(other->client->ps.viewangles, otherFwd, 0, 0);
06563 
06564                         saberent->s.pos.trDelta[0] = otherFwd[0]*deflectSpeed;
06565                         saberent->s.pos.trDelta[1] = otherFwd[1]*deflectSpeed;
06566                         saberent->s.pos.trDelta[2] = otherFwd[2]*deflectSpeed;
06567                 }
06568         }
06569 
06570         trap_LinkEntity(saberent);
06571 
06572         if (saberOwner->client->saber[0].soundOff)
06573         {
06574                 G_Sound( saberent, CHAN_BODY, saberOwner->client->saber[0].soundOff );
06575         }
06576 
06577         if (saberOwner->client->saber[1].soundOff &&
06578                 saberOwner->client->saber[1].model[0])
06579         {
06580                 G_Sound( saberOwner, CHAN_BODY, saberOwner->client->saber[1].soundOff );
06581         }
06582 }
06583 
06584 //sort of a silly macro I guess. But if I change anything in here I'll probably want it to be everywhere.
06585 #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))
06586 
06587 void WP_SaberRemoveG2Model( gentity_t *saberent )
06588 {
06589         if ( saberent->ghoul2 )
06590         {
06591                 trap_G2API_RemoveGhoul2Models( &saberent->ghoul2 );
06592         }
06593 }
06594 
06595 void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin )
06596 {
06597         WP_SaberRemoveG2Model( saberent );
06598         if ( saberModel && saberModel[0] )
06599         {
06600                 saberent->s.modelindex = G_ModelIndex(saberModel);
06601         }
06602         else
06603         {
06604                 saberent->s.modelindex = G_ModelIndex( "models/weapons2/saber/saber_w.glm" );
06605         }
06606         //FIXME: use customSkin?
06607         trap_G2API_InitGhoul2Model( &saberent->ghoul2, saberModel, saberent->s.modelindex, saberSkin, 0, 0, 0 );
06608 }
06609 
06610 //Make the saber go flying directly out of the owner's hand in the specified direction
06611 qboolean saberKnockOutOfHand(gentity_t *saberent, gentity_t *saberOwner, vec3_t velocity)
06612 {
06613         if (!saberent || !saberOwner ||
06614                 !saberent->inuse || !saberOwner->inuse ||
06615                 !saberOwner->client)
06616         {
06617                 return qfalse;
06618         }
06619 
06620         if (!saberOwner->client->ps.saberEntityNum)
06621         { //already gone
06622                 return qfalse;
06623         }
06624 
06625         if ((level.time - saberOwner->client->lastSaberStorageTime) > 50)
06626         { //must have a reasonably updated saber base pos
06627                 return qfalse;
06628         }
06629 
06630         if (saberOwner->client->ps.saberLockTime > (level.time-100))
06631         {
06632                 return qfalse;
06633         }
06634         if ( (saberOwner->client->saber[0].saberFlags&SFL_NOT_DISARMABLE) )
06635         {
06636                 return qfalse;
06637         }
06638 
06639         saberOwner->client->ps.saberInFlight = qtrue;
06640         saberOwner->client->ps.saberEntityState = 1;
06641 
06642         saberent->s.saberInFlight = qfalse;//qtrue;
06643 
06644         saberent->s.pos.trType = TR_LINEAR;
06645         saberent->s.eType = ET_GENERAL;
06646         saberent->s.eFlags = 0;
06647 
06648         WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin );
06649 
06650         saberent->s.modelGhoul2 = 127;
06651 
06652         saberent->parent = saberOwner;
06653 
06654         saberent->damage = SABER_THROWN_HIT_DAMAGE;
06655         saberent->methodOfDeath = MOD_SABER;
06656         saberent->splashMethodOfDeath = MOD_SABER;
06657         saberent->s.solid = 2;
06658         saberent->r.contents = CONTENTS_LIGHTSABER;
06659 
06660         saberent->genericValue5 = 0;
06661 
06662         VectorSet( saberent->r.mins, -24.0f, -24.0f, -8.0f );
06663         VectorSet( saberent->r.maxs, 24.0f, 24.0f, 8.0f );
06664 
06665         saberent->s.genericenemyindex = saberOwner->s.number+1024;
06666         saberent->s.weapon = WP_SABER;
06667 
06668         saberent->genericValue5 = 0;
06669 
06670         G_SetOrigin(saberent, saberOwner->client->lastSaberBase_Always); //use this as opposed to the right hand bolt,
06671         //because I don't want to risk reconstructing the skel again to get it here. And it isn't worth storing.
06672         saberKnockDown(saberent, saberOwner, saberOwner);
06673         VectorCopy(velocity, saberent->s.pos.trDelta); //override the velocity on the knocked away saber.
06674         
06675         return qtrue;
06676 }
06677 
06678 //Called at the result of a circle lock duel - the loser gets his saber tossed away and is put into a reflected attack anim
06679 qboolean saberCheckKnockdown_DuelLoss(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
06680 {
06681         vec3_t dif;
06682         float totalDistance = 1;
06683         float distScale = 6.5f;
06684         qboolean validMomentum = qtrue;
06685         int     disarmChance = 1;
06686 
06687         if (SABERINVALID)
06688         {
06689                 return qfalse;
06690         }
06691 
06692         VectorClear(dif);
06693 
06694         if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200)
06695         { //see if the spots are valid
06696                 validMomentum = qfalse;
06697         }
06698 
06699         if (validMomentum)
06700         {
06701                 //Get the difference 
06702                 VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif);
06703                 totalDistance = VectorNormalize(dif);
06704 
06705                 if (!totalDistance)
06706                 { //fine, try our own
06707                         if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200)
06708                         {
06709                                 validMomentum = qfalse;
06710                         }
06711 
06712                         if (validMomentum)
06713                         {
06714                                 VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif);
06715                                 totalDistance = VectorNormalize(dif);
06716                         }
06717                 }
06718 
06719                 if (validMomentum)
06720                 {
06721                         if (!totalDistance)
06722                         { //try the difference between the two blades
06723                                 VectorSubtract(saberOwner->client->lastSaberBase_Always, other->client->lastSaberBase_Always, dif);
06724                                 totalDistance = VectorNormalize(dif);
06725                         }
06726 
06727                         if (totalDistance)
06728                         { //if we still have no difference somehow, just let it fall to the ground when the time comes.
06729                                 if (totalDistance < 20)
06730                                 {
06731                                         totalDistance = 20;
06732                                 }
06733                                 VectorScale(dif, totalDistance*distScale, dif);
06734                         }
06735                 }
06736         }
06737 
06738         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)
06739         saberOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
06740 
06741         if ( other && other->client )
06742         {
06743                 disarmChance += other->client->saber[0].disarmBonus;
06744                 if ( other->client->saber[1].model
06745                         && other->client->saber[1].model[0]
06746                         && !other->client->ps.saberHolstered )
06747                 {
06748                         other->client->saber[1].disarmBonus;
06749                 }
06750         }
06751         if ( Q_irand( 0, disarmChance ) )
06752         {
06753                 return saberKnockOutOfHand(saberent, saberOwner, dif);
06754         }
06755         else
06756         {
06757                 return qfalse;
06758         }
06759 }
06760 
06761 //Called when we want to try knocking the saber out of the owner's hand upon them going into a broken parry.
06762 //Also called on reflected attacks.
06763 qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
06764 {
06765         int myAttack;
06766         int otherAttack;
06767         qboolean doKnock = qfalse;
06768         int     disarmChance = 1;
06769 
06770         if (SABERINVALID)
06771         {
06772                 return qfalse;
06773         }
06774 
06775         //Neither gets an advantage based on attack state, when it comes to knocking
06776         //saber out of hand.
06777         myAttack = G_SaberAttackPower(saberOwner, qfalse);
06778         otherAttack = G_SaberAttackPower(other, qfalse);
06779 
06780         if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200)
06781         { //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it
06782                 return qfalse;
06783         }
06784 
06785         //only knock the saber out of the hand if they're in a stronger stance I suppose. Makes strong more advantageous.
06786         if (otherAttack > myAttack+1 && Q_irand(1, 10) <= 7)
06787         { //This would be, say, strong stance against light stance.
06788                 doKnock = qtrue;
06789         }
06790         else if (otherAttack > myAttack && Q_irand(1, 10) <= 3)
06791         { //Strong vs. medium, medium vs. light
06792                 doKnock = qtrue;
06793         }
06794 
06795         if (doKnock)
06796         {
06797                 vec3_t dif;
06798                 float totalDistance;
06799                 float distScale = 6.5f;
06800 
06801                 VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif);
06802                 totalDistance = VectorNormalize(dif);
06803 
06804                 if (!totalDistance)
06805                 { //fine, try our own
06806                         if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200)
06807                         { //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it
06808                                 return qfalse;
06809                         }
06810 
06811                         VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif);
06812                         totalDistance = VectorNormalize(dif);
06813                 }
06814 
06815                 if (!totalDistance)
06816                 { //...forget it then.
06817                         return qfalse;
06818                 }
06819 
06820                 if (totalDistance < 20)
06821                 {
06822                         totalDistance = 20;
06823                 }
06824                 VectorScale(dif, totalDistance*distScale, dif);
06825 
06826                 if ( other && other->client )
06827                 {
06828                         disarmChance += other->client->saber[0].disarmBonus;
06829                         if ( other->client->saber[1].model
06830                                 && other->client->saber[1].model[0]
06831                                 && !other->client->ps.saberHolstered )
06832                         {
06833                                 other->client->saber[1].disarmBonus;
06834                         }
06835                 }
06836                 if ( Q_irand( 0, disarmChance ) )
06837                 {
06838                         return saberKnockOutOfHand(saberent, saberOwner, dif);
06839                 }
06840         }
06841 
06842         return qfalse;
06843 }
06844 
06845 #include "../namespace_begin.h"
06846 qboolean BG_InExtraDefenseSaberMove( int move );
06847 #include "../namespace_end.h"
06848 
06849 //Called upon an enemy actually slashing into a thrown saber
06850 qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage)
06851 {
06852         if (SABERINVALID)
06853         {
06854                 return qfalse;
06855         }
06856 
06857         if (!saberOwner->client->ps.saberInFlight)
06858         { //can only do this if the saber is already actually in flight
06859                 return qfalse;
06860         }
06861 
06862         if ( other
06863                 && other->inuse
06864                 && other->client 
06865                 && BG_InExtraDefenseSaberMove( other->client->ps.saberMove ) )
06866         { //make sure the blow was strong enough
06867                 saberKnockDown(saberent, saberOwner, other);
06868                 return qtrue;
06869         }
06870 
06871         if (damage > 10)
06872         { //make sure the blow was strong enough
06873                 saberKnockDown(saberent, saberOwner, other);
06874                 return qtrue;
06875         }
06876 
06877         return qfalse;
06878 }
06879 
06880 //Called upon blocking a thrown saber. If the throw level compared to the blocker's defense level
06881 //is inferior, or equal and a random factor is met, then the saber will be tossed to the ground.
06882 qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
06883 {
06884         int throwLevel = 0;
06885         int defenLevel = 0;
06886         qboolean tossIt = qfalse;
06887 
06888         if (SABERINVALID)
06889         {
06890                 return qfalse;
06891         }
06892 
06893         defenLevel = other->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE];
06894         throwLevel = saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW];
06895 
06896         if (defenLevel > throwLevel)
06897         {
06898                 tossIt = qtrue;
06899         }
06900         else if (defenLevel == throwLevel && Q_irand(1, 10) <= 4)
06901         {
06902                 tossIt = qtrue;
06903         }
06904         //otherwise don't
06905 
06906         if (tossIt)
06907         {
06908                 saberKnockDown(saberent, saberOwner, other);
06909                 return qtrue;
06910         }
06911 
06912         return qfalse;
06913 }
06914 
06915 void saberBackToOwner(gentity_t *saberent)
06916 {
06917         gentity_t *saberOwner = &g_entities[saberent->r.ownerNum];
06918         vec3_t dir;
06919         float ownerLen;
06920 
06921         if (saberent->r.ownerNum == ENTITYNUM_NONE)
06922         {
06923                 MakeDeadSaber(saberent);
06924 
06925                 saberent->think = G_FreeEntity;
06926                 saberent->nextthink = level.time;
06927                 return;
06928         }
06929 
06930         if (!saberOwner->inuse ||
06931                 !saberOwner->client ||
06932                 saberOwner->client->sess.sessionTeam == TEAM_SPECTATOR)
06933         {
06934                 MakeDeadSaber(saberent);
06935 
06936                 saberent->think = G_FreeEntity;
06937                 saberent->nextthink = level.time;
06938                 return;
06939         }
06940 
06941         if (saberOwner->health < 1 || !saberOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
06942         { //He's dead, just go back to our normal saber status
06943                 saberent->touch = SaberGotHit;
06944                 saberent->think = SaberUpdateSelf;
06945                 saberent->genericValue5 = 0;
06946                 saberent->nextthink = level.time;
06947 
06948                 if (saberOwner->client &&
06949                         saberOwner->client->saber[0].soundOff)
06950                 {
06951                         G_Sound(saberent, CHAN_AUTO, saberOwner->client->saber[0].soundOff);
06952                 }
06953                 MakeDeadSaber(saberent);
06954 
06955                 saberent->r.svFlags |= (SVF_NOCLIENT);
06956                 saberent->r.contents = CONTENTS_LIGHTSABER;
06957                 SetSaberBoxSize(saberent);
06958                 saberent->s.loopSound = 0;
06959                 saberent->s.loopIsSoundset = qfalse;
06960                 WP_SaberRemoveG2Model( saberent );
06961 
06962                 saberOwner->client->ps.saberInFlight = qfalse;
06963                 saberOwner->client->ps.saberEntityState = 0;
06964                 saberOwner->client->ps.saberThrowDelay = level.time + 500;
06965                 saberOwner->client->ps.saberCanThrow = qfalse;
06966 
06967                 return;
06968         }
06969 
06970         //make sure this is set alright
06971         assert(saberOwner->client->ps.saberEntityNum == saberent->s.number ||
06972                 saberOwner->client->saberStoredIndex == saberent->s.number);
06973         saberOwner->client->ps.saberEntityNum = saberent->s.number;
06974 
06975         saberent->r.contents = CONTENTS_LIGHTSABER;
06976 
06977         VectorSubtract(saberent->pos1, saberent->r.currentOrigin, dir);
06978 
06979         ownerLen = VectorLength(dir);
06980 
06981         if (saberent->speed < level.time)
06982         {
06983                 float baseSpeed = 900;
06984 
06985                 VectorNormalize(dir);
06986 
06987                 saberMoveBack(saberent, qtrue);
06988                 VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
06989 
06990                 if (saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
06991                 { //allow players with high saber throw rank to control the return speed of the saber
06992                         baseSpeed = 900;
06993 
06994                         saberent->speed = level.time;// + 200;
06995                 }
06996                 else
06997                 {
06998                         baseSpeed = 700;
06999                         saberent->speed = level.time + 50;
07000                 }
07001 
07002                 //Gradually slow down as it approaches, so it looks smoother coming into the hand.
07003                 if (ownerLen < 64)
07004                 {
07005                         VectorScale(dir, baseSpeed-200, saberent->s.pos.trDelta );
07006                 }
07007                 else if (ownerLen < 128)
07008                 {
07009                         VectorScale(dir, baseSpeed-150, saberent->s.pos.trDelta );
07010                 }
07011                 else if (ownerLen < 256)
07012                 {
07013                         VectorScale(dir, baseSpeed-100, saberent->s.pos.trDelta );
07014                 }
07015                 else
07016                 {
07017                         VectorScale(dir, baseSpeed, saberent->s.pos.trDelta );
07018                 }
07019 
07020                 saberent->s.pos.trTime = level.time;
07021         }
07022 
07023         /*
07024         if (ownerLen <= 512)
07025         {
07026                 saberent->s.saberInFlight = qfalse;
07027                 saberent->s.loopSound = saberHumSound;
07028                 saberent->s.loopIsSoundset = qfalse;
07029         }
07030         */
07031         //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.
07032         if (saberOwner->client->ps.saberEntityNum == saberent->s.number)
07033         {
07034                 if ( !(saberOwner->client->saber[0].saberFlags&SFL_RETURN_DAMAGE)
07035                         || saberOwner->client->ps.saberHolstered )
07036                 {
07037                         saberent->s.saberInFlight = qfalse;
07038                 }
07039                 saberent->s.loopSound = saberOwner->client->saber[0].soundLoop;
07040                 saberent->s.loopIsSoundset = qfalse;
07041 
07042                 if (ownerLen <= 32)
07043                 {
07044                         G_Sound( saberent, CHAN_AUTO, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
07045 
07046                         saberOwner->client->ps.saberInFlight = qfalse;
07047                         saberOwner->client->ps.saberEntityState = 0;
07048                         saberOwner->client->ps.saberCanThrow = qfalse;
07049                         saberOwner->client->ps.saberThrowDelay = level.time + 300;
07050 
07051                         saberent->touch = SaberGotHit;
07052 
07053                         saberent->think = SaberUpdateSelf;
07054                         saberent->genericValue5 = 0;
07055                         saberent->nextthink = level.time + 50;
07056                         WP_SaberRemoveG2Model( saberent );
07057 
07058                         return;
07059                 }
07060 
07061                 if (!saberent->s.saberInFlight)
07062                 {
07063                         saberCheckRadiusDamage(saberent, 1);
07064                 }
07065                 else
07066                 {
07067                         saberCheckRadiusDamage(saberent, 2);
07068                 }
07069 
07070                 saberMoveBack(saberent, qtrue);
07071         }
07072 
07073         saberent->nextthink = level.time;
07074 }
07075 
07076 void saberFirstThrown(gentity_t *saberent);
07077 
07078 void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace)
07079 {
07080         gentity_t *hitEnt = other;
07081 
07082         if (other && other->s.number == saberent->r.ownerNum)
07083         {
07084                 return;
07085         }
07086         VectorClear(saberent->s.pos.trDelta);
07087         saberent->s.pos.trTime = level.time;
07088 
07089         saberent->s.apos.trType = TR_LINEAR;
07090         saberent->s.apos.trDelta[0] = 0;
07091         saberent->s.apos.trDelta[1] = 800;
07092         saberent->s.apos.trDelta[2] = 0;
07093 
07094         VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
07095 
07096         saberent->think = saberBackToOwner;
07097         saberent->nextthink = level.time;
07098 
07099         if (other && other->r.ownerNum < MAX_CLIENTS &&
07100                 (other->r.contents & CONTENTS_LIGHTSABER) &&
07101                 g_entities[other->r.ownerNum].client &&
07102                 g_entities[other->r.ownerNum].inuse)
07103         {
07104                 hitEnt = &g_entities[other->r.ownerNum];
07105         }
07106 
07107         //we'll skip the dist check, since we don't really care about that (we just hit it physically)
07108         CheckThrownSaberDamaged(saberent, &g_entities[saberent->r.ownerNum], hitEnt, 256, 0, qtrue);
07109 
07110         saberent->speed = 0;
07111 }
07112 
07113 #define SABER_MAX_THROW_DISTANCE 700
07114 
07115 void saberFirstThrown(gentity_t *saberent)
07116 {
07117         vec3_t          vSub;
07118         float           vLen;
07119         gentity_t       *saberOwn = &g_entities[saberent->r.ownerNum];
07120 
07121         if (saberent->r.ownerNum == ENTITYNUM_NONE)
07122         {
07123                 MakeDeadSaber(saberent);
07124 
07125                 saberent->think = G_FreeEntity;
07126                 saberent->nextthink = level.time;
07127                 return;
07128         }
07129 
07130         if (!saberOwn ||
07131                 !saberOwn->inuse ||
07132                 !saberOwn->client ||
07133                 saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR)
07134         {
07135                 MakeDeadSaber(saberent);
07136 
07137                 saberent->think = G_FreeEntity;
07138                 saberent->nextthink = level.time;
07139                 return;
07140         }
07141 
07142         if (saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
07143         { //He's dead, just go back to our normal saber status
07144                 saberent->touch = SaberGotHit;
07145                 saberent->think = SaberUpdateSelf;
07146                 saberent->genericValue5 = 0;
07147                 saberent->nextthink = level.time;
07148 
07149                 if (saberOwn->client &&
07150                         saberOwn->client->saber[0].soundOff)
07151                 {
07152                         G_Sound(saberent, CHAN_AUTO, saberOwn->client->saber[0].soundOff);
07153                 }
07154                 MakeDeadSaber(saberent);
07155 
07156                 saberent->r.svFlags |= (SVF_NOCLIENT);
07157                 saberent->r.contents = CONTENTS_LIGHTSABER;
07158                 SetSaberBoxSize(saberent);
07159                 saberent->s.loopSound = 0;
07160                 saberent->s.loopIsSoundset = qfalse;
07161                 WP_SaberRemoveG2Model( saberent );
07162 
07163                 saberOwn->client->ps.saberInFlight = qfalse;
07164                 saberOwn->client->ps.saberEntityState = 0;
07165                 saberOwn->client->ps.saberThrowDelay = level.time + 500;
07166                 saberOwn->client->ps.saberCanThrow = qfalse;
07167 
07168                 return;
07169         }
07170 
07171         if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 500)
07172         {
07173                 if (!(saberOwn->client->buttons & BUTTON_ALT_ATTACK))
07174                 { //If owner releases altattack 500ms or later after throwing saber, it autoreturns
07175                         thrownSaberTouch(saberent, saberent, NULL);
07176                         goto runMin;
07177                 }
07178                 else if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 6000)
07179                 { //if it's out longer than 6 seconds, return it
07180                         thrownSaberTouch(saberent, saberent, NULL);
07181                         goto runMin;
07182                 }
07183         }
07184 
07185         if (BG_HasYsalamiri(g_gametype.integer, &saberOwn->client->ps))
07186         {
07187                 thrownSaberTouch(saberent, saberent, NULL);
07188                 goto runMin;
07189         }
07190         
07191         if (!BG_CanUseFPNow(g_gametype.integer, &saberOwn->client->ps, level.time, FP_SABERTHROW))
07192         {
07193                 thrownSaberTouch(saberent, saberent, NULL);
07194                 goto runMin;
07195         }
07196 
07197         VectorSubtract(saberOwn->client->ps.origin, saberent->r.currentOrigin, vSub);
07198         vLen = VectorLength(vSub);
07199 
07200         if (vLen >= (SABER_MAX_THROW_DISTANCE*saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW]))
07201         {
07202                 thrownSaberTouch(saberent, saberent, NULL);
07203                 goto runMin;
07204         }
07205 
07206         if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_2 &&
07207                 saberent->speed < level.time)
07208         { //if owner is rank 3 in saber throwing, the saber goes where he points
07209                 vec3_t fwd, traceFrom, traceTo, dir;
07210                 trace_t tr;
07211 
07212                 AngleVectors(saberOwn->client->ps.viewangles, fwd, 0, 0);
07213 
07214                 VectorCopy(saberOwn->client->ps.origin, traceFrom);
07215                 traceFrom[2] += saberOwn->client->ps.viewheight;
07216 
07217                 VectorCopy(traceFrom, traceTo);
07218                 traceTo[0] += fwd[0]*4096;
07219                 traceTo[1] += fwd[1]*4096;
07220                 traceTo[2] += fwd[2]*4096;
07221 
07222                 saberMoveBack(saberent, qfalse);
07223                 VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
07224 
07225                 if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
07226                 { //if highest saber throw rank, we can direct the saber toward players directly by looking at them
07227                         trap_Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_PLAYERSOLID);
07228                 }
07229                 else
07230                 {
07231                         trap_Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_SOLID);
07232                 }
07233 
07234                 VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
07235 
07236                 VectorNormalize(dir);
07237 
07238                 VectorScale(dir, 500, saberent->s.pos.trDelta );
07239                 saberent->s.pos.trTime = level.time;
07240 
07241                 if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
07242                 { //we'll treat them to a quicker update rate if their throw rank is high enough
07243                         saberent->speed = level.time + 100;
07244                 }
07245                 else
07246                 {
07247                         saberent->speed = level.time + 400;
07248                 }
07249         }
07250 
07251 runMin:
07252 
07253         saberCheckRadiusDamage(saberent, 0);
07254         G_RunObject(saberent);
07255 }
07256 
07257 void UpdateClientRenderBolts(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles)
07258 {
07259         mdxaBone_t boltMatrix;
07260         renderInfo_t *ri = &self->client->renderInfo;
07261 
07262         if (!self->ghoul2)
07263         {
07264                 VectorCopy(self->client->ps.origin, ri->headPoint);
07265                 VectorCopy(self->client->ps.origin, ri->handRPoint);
07266                 VectorCopy(self->client->ps.origin, ri->handLPoint);
07267                 VectorCopy(self->client->ps.origin, ri->torsoPoint);
07268                 VectorCopy(self->client->ps.origin, ri->crotchPoint);
07269                 VectorCopy(self->client->ps.origin, ri->footRPoint);
07270                 VectorCopy(self->client->ps.origin, ri->footLPoint);
07271         }
07272         else
07273         {
07274                 //head
07275                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07276                 ri->headPoint[0] = boltMatrix.matrix[0][3];
07277                 ri->headPoint[1] = boltMatrix.matrix[1][3];
07278                 ri->headPoint[2] = boltMatrix.matrix[2][3];
07279 
07280                 //right hand
07281                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07282                 ri->handRPoint[0] = boltMatrix.matrix[0][3];
07283                 ri->handRPoint[1] = boltMatrix.matrix[1][3];
07284                 ri->handRPoint[2] = boltMatrix.matrix[2][3];
07285 
07286                 //left hand
07287                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07288                 ri->handLPoint[0] = boltMatrix.matrix[0][3];
07289                 ri->handLPoint[1] = boltMatrix.matrix[1][3];
07290                 ri->handLPoint[2] = boltMatrix.matrix[2][3];
07291 
07292                 //chest
07293                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07294                 ri->torsoPoint[0] = boltMatrix.matrix[0][3];
07295                 ri->torsoPoint[1] = boltMatrix.matrix[1][3];
07296                 ri->torsoPoint[2] = boltMatrix.matrix[2][3];
07297 
07298                 //crotch
07299                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07300                 ri->crotchPoint[0] = boltMatrix.matrix[0][3];
07301                 ri->crotchPoint[1] = boltMatrix.matrix[1][3];
07302                 ri->crotchPoint[2] = boltMatrix.matrix[2][3];
07303 
07304                 //right foot
07305                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07306                 ri->footRPoint[0] = boltMatrix.matrix[0][3];
07307                 ri->footRPoint[1] = boltMatrix.matrix[1][3];
07308                 ri->footRPoint[2] = boltMatrix.matrix[2][3];
07309 
07310                 //left foot
07311                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07312                 ri->footLPoint[0] = boltMatrix.matrix[0][3];
07313                 ri->footLPoint[1] = boltMatrix.matrix[1][3];
07314                 ri->footLPoint[2] = boltMatrix.matrix[2][3];
07315         }
07316 
07317         self->client->renderInfo.boltValidityTime = level.time;
07318 }
07319 
07320 void UpdateClientRenderinfo(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles)
07321 {
07322         renderInfo_t *ri = &self->client->renderInfo;
07323         if ( ri->mPCalcTime < level.time )
07324         {
07325                 //We're just going to give rough estimates on most of this stuff,
07326                 //it's not like most of it matters.
07327 
07328         #if 0 //#if 0'd since it's a waste setting all this to 0 each frame.
07329                 //Should you wish to make any of this valid then feel free to do so.
07330                 ri->headYawRangeLeft = ri->headYawRangeRight = ri->headPitchRangeUp = ri->headPitchRangeDown = 0;
07331                 ri->torsoYawRangeLeft = ri->torsoYawRangeRight = ri->torsoPitchRangeUp = ri->torsoPitchRangeDown = 0;
07332 
07333                 ri->torsoFpsMod = ri->legsFpsMod = 0;
07334 
07335                 VectorClear(ri->customRGB);
07336                 ri->customAlpha = 0;
07337                 ri->renderFlags = 0;
07338                 ri->lockYaw = 0;
07339 
07340                 VectorClear(ri->headAngles);
07341                 VectorClear(ri->torsoAngles);
07342 
07343                 //VectorClear(ri->eyeAngles);
07344 
07345                 ri->legsYaw = 0;
07346         #endif
07347 
07348                 if (self->ghoul2 &&
07349                         self->ghoul2 != ri->lastG2)
07350                 { //the g2 instance changed, so update all the bolts.
07351                         //rwwFIXMEFIXME: Base on skeleton used? Assuming humanoid currently.
07352                         ri->lastG2 = self->ghoul2;
07353 
07354                         if (self->localAnimIndex <= 1)
07355                         {
07356                                 ri->headBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*head_eyes");
07357                                 ri->handRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_hand");
07358                                 ri->handLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_hand");
07359                                 ri->torsoBolt = trap_G2API_AddBolt(self->ghoul2, 0, "thoracic");
07360                                 ri->crotchBolt = trap_G2API_AddBolt(self->ghoul2, 0, "pelvis");
07361                                 ri->footRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_leg_foot");
07362                                 ri->footLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_leg_foot");
07363                                 ri->motionBolt = trap_G2API_AddBolt(self->ghoul2, 0, "Motion");
07364                         }
07365                         else
07366                         {
07367                                 ri->headBolt = -1;
07368                                 ri->handRBolt = -1;
07369                                 ri->handLBolt = -1;
07370                                 ri->torsoBolt = -1;
07371                                 ri->crotchBolt = -1;
07372                                 ri->footRBolt = -1;
07373                                 ri->footLBolt = -1;
07374                                 ri->motionBolt = -1;
07375                         }
07376 
07377                         ri->lastG2 = self->ghoul2;
07378                 }
07379 
07380                 VectorCopy( self->client->ps.viewangles, self->client->renderInfo.eyeAngles );
07381 
07382                 //we'll just say the legs/torso are whatever the first frame of our current anim is.
07383                 ri->torsoFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].firstFrame;
07384                 ri->legsFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.legsAnim].firstFrame;
07385                 if (g_debugServerSkel.integer)
07386                 {       //Alright, I was doing this, but it's just too slow to do every frame.
07387                         //From now on if we want this data to be valid we're going to have to make a verify call for it before
07388                         //accessing it. I'm only doing this now if we want to debug the server skel by drawing lines from bolt
07389                         //positions every frame.
07390                         mdxaBone_t boltMatrix;
07391 
07392                         if (!self->ghoul2)
07393                         {
07394                                 VectorCopy(self->client->ps.origin, ri->headPoint);
07395                                 VectorCopy(self->client->ps.origin, ri->handRPoint);
07396                                 VectorCopy(self->client->ps.origin, ri->handLPoint);
07397                                 VectorCopy(self->client->ps.origin, ri->torsoPoint);
07398                                 VectorCopy(self->client->ps.origin, ri->crotchPoint);
07399                                 VectorCopy(self->client->ps.origin, ri->footRPoint);
07400                                 VectorCopy(self->client->ps.origin, ri->footLPoint);
07401                         }
07402                         else
07403                         {
07404                                 //head
07405                                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07406                                 ri->headPoint[0] = boltMatrix.matrix[0][3];
07407                                 ri->headPoint[1] = boltMatrix.matrix[1][3];
07408                                 ri->headPoint[2] = boltMatrix.matrix[2][3];
07409 
07410                                 //right hand
07411                                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07412                                 ri->handRPoint[0] = boltMatrix.matrix[0][3];
07413                                 ri->handRPoint[1] = boltMatrix.matrix[1][3];
07414                                 ri->handRPoint[2] = boltMatrix.matrix[2][3];
07415 
07416                                 //left hand
07417                                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07418                                 ri->handLPoint[0] = boltMatrix.matrix[0][3];
07419                                 ri->handLPoint[1] = boltMatrix.matrix[1][3];
07420                                 ri->handLPoint[2] = boltMatrix.matrix[2][3];
07421 
07422                                 //chest
07423                                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07424                                 ri->torsoPoint[0] = boltMatrix.matrix[0][3];
07425                                 ri->torsoPoint[1] = boltMatrix.matrix[1][3];
07426                                 ri->torsoPoint[2] = boltMatrix.matrix[2][3];
07427 
07428                                 //crotch
07429                                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07430                                 ri->crotchPoint[0] = boltMatrix.matrix[0][3];
07431                                 ri->crotchPoint[1] = boltMatrix.matrix[1][3];
07432                                 ri->crotchPoint[2] = boltMatrix.matrix[2][3];
07433 
07434                                 //right foot
07435                                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07436                                 ri->footRPoint[0] = boltMatrix.matrix[0][3];
07437                                 ri->footRPoint[1] = boltMatrix.matrix[1][3];
07438                                 ri->footRPoint[2] = boltMatrix.matrix[2][3];
07439 
07440                                 //left foot
07441                                 trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
07442                                 ri->footLPoint[0] = boltMatrix.matrix[0][3];
07443                                 ri->footLPoint[1] = boltMatrix.matrix[1][3];
07444                                 ri->footLPoint[2] = boltMatrix.matrix[2][3];
07445                         }
07446 
07447                         //Now draw the skel for debug
07448                         G_TestLine(ri->headPoint, ri->torsoPoint, 0x000000ff, 50);
07449                         G_TestLine(ri->torsoPoint, ri->handRPoint, 0x000000ff, 50);
07450                         G_TestLine(ri->torsoPoint, ri->handLPoint, 0x000000ff, 50);
07451                         G_TestLine(ri->torsoPoint, ri->crotchPoint, 0x000000ff, 50);
07452                         G_TestLine(ri->crotchPoint, ri->footRPoint, 0x000000ff, 50);
07453                         G_TestLine(ri->crotchPoint, ri->footLPoint, 0x000000ff, 50);
07454                 }
07455 
07456                 //muzzle point calc (we are going to be cheap here)
07457                 VectorCopy(ri->muzzlePoint, ri->muzzlePointOld);
07458                 VectorCopy(self->client->ps.origin, ri->muzzlePoint);
07459                 VectorCopy(ri->muzzleDir, ri->muzzleDirOld);
07460                 AngleVectors(self->client->ps.viewangles, ri->muzzleDir, 0, 0);
07461                 ri->mPCalcTime = level.time;
07462 
07463                 VectorCopy(self->client->ps.origin, ri->eyePoint);
07464                 ri->eyePoint[2] += self->client->ps.viewheight;
07465         }
07466 }
07467 
07468 #define STAFF_KICK_RANGE 16
07469 extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ); //NPC_utils.c
07470 
07471 extern qboolean BG_InKnockDown( int anim );
07472 static qboolean G_KickDownable(gentity_t *ent)
07473 {
07474         if (!d_saberKickTweak.integer)
07475         {
07476                 return qtrue;
07477         }
07478 
07479         if (!ent || !ent->inuse || !ent->client)
07480         {
07481                 return qfalse;
07482         }
07483 
07484         if (BG_InKnockDown(ent->client->ps.legsAnim) ||
07485                 BG_InKnockDown(ent->client->ps.torsoAnim))
07486         {
07487                 return qfalse;
07488         }
07489 
07490         if (ent->client->ps.weaponTime <= 0 &&
07491                 ent->client->ps.weapon == WP_SABER &&
07492                 ent->client->ps.groundEntityNum != ENTITYNUM_NONE)
07493         {
07494                 return qfalse;
07495         }
07496 
07497         return qtrue;
07498 }
07499 
07500 static void G_TossTheMofo(gentity_t *ent, vec3_t tossDir, float tossStr)
07501 {
07502         if (!ent->inuse || !ent->client)
07503         { //no good
07504                 return;
07505         }
07506 
07507         if (ent->s.eType == ET_NPC && ent->s.NPC_class == CLASS_VEHICLE)
07508         { //no, silly
07509                 return;
07510         }
07511 
07512         VectorMA(ent->client->ps.velocity, tossStr, tossDir, ent->client->ps.velocity);
07513         ent->client->ps.velocity[2] = 200;
07514         if (ent->health > 0 && ent->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN &&
07515                 BG_KnockDownable(&ent->client->ps) &&
07516                 G_KickDownable(ent))
07517         { //if they are alive, knock them down I suppose
07518                 ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
07519                 ent->client->ps.forceHandExtendTime = level.time + 700;
07520                 ent->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim
07521                 //ent->client->ps.quickerGetup = qtrue;
07522         }
07523 }
07524 
07525 static gentity_t *G_KickTrace( gentity_t *ent, vec3_t kickDir, float kickDist, vec3_t kickEnd, int kickDamage, float kickPush )
07526 {
07527         vec3_t  traceOrg, traceEnd, kickMins, kickMaxs;
07528         trace_t trace;
07529         gentity_t       *hitEnt = NULL;
07530         VectorSet(kickMins, -2.0f, -2.0f, -2.0f);
07531         VectorSet(kickMaxs, 2.0f, 2.0f, 2.0f);
07532         //FIXME: variable kick height?
07533         if ( kickEnd && !VectorCompare( kickEnd, vec3_origin ) )
07534         {//they passed us the end point of the trace, just use that
07535                 //this makes the trace flat
07536                 VectorSet( traceOrg, ent->r.currentOrigin[0], ent->r.currentOrigin[1], kickEnd[2] );
07537                 VectorCopy( kickEnd, traceEnd );
07538         }
07539         else
07540         {//extrude
07541                 VectorSet( traceOrg, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2]+ent->r.maxs[2]*0.5f );
07542                 VectorMA( traceOrg, kickDist, kickDir, traceEnd );
07543         }
07544 
07545         if (d_saberKickTweak.integer)
07546         {
07547                 trap_G2Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
07548         }
07549         else
07550         {
07551                 trap_Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT );
07552         }
07553 
07554         //G_TestLine(traceOrg, traceEnd, 0x0000ff, 5000);
07555         if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid )
07556         {
07557                 if (ent->client->jediKickTime > level.time)
07558                 {
07559                         if (trace.entityNum == ent->client->jediKickIndex)
07560                         { //we are hitting the same ent we last hit in this same anim, don't hit it again
07561                                 return NULL;
07562                         }
07563                 }
07564                 ent->client->jediKickIndex = trace.entityNum;
07565                 ent->client->jediKickTime = level.time + ent->client->ps.legsTimer;
07566 
07567                 hitEnt = &g_entities[trace.entityNum];
07568                 //FIXME: regardless of what we hit, do kick hit sound and impact effect
07569                 //G_PlayEffect( "misc/kickHit", trace.endpos, trace.plane.normal );
07570                 if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
07571                 {
07572                         G_Sound( ent, CHAN_AUTO, G_SoundIndex( "sound/movers/objects/saber_slam" ) );
07573                 }
07574                 else
07575                 {
07576                         G_Sound( ent, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
07577                 }
07578                 if ( hitEnt->inuse )
07579                 {//we hit an entity
07580                         //FIXME: don't hit same ent more than once per kick
07581                         if ( hitEnt->takedamage )
07582                         {//hurt it
07583                                 if (hitEnt->client)
07584                                 {
07585                                         hitEnt->client->ps.otherKiller = ent->s.number;
07586                                         hitEnt->client->ps.otherKillerDebounceTime = level.time + 10000;
07587                                         hitEnt->client->ps.otherKillerTime = level.time + 10000;
07588                                 }
07589 
07590                                 if (d_saberKickTweak.integer)
07591                                 {
07592                                         G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage*0.2f, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
07593                                 }
07594                                 else
07595                                 {
07596                                         G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
07597                                 }
07598                         }
07599                         if ( hitEnt->client 
07600                                 && !(hitEnt->client->ps.pm_flags&PMF_TIME_KNOCKBACK) //not already flying through air?  Intended to stop multiple hits, but...
07601                                 && G_CanBeEnemy(ent, hitEnt) )
07602                         {//FIXME: this should not always work
07603                                 if ( hitEnt->health <= 0 )
07604                                 {//we kicked a dead guy
07605                                         //throw harder - FIXME: no matter how hard I push them, they don't go anywhere... corpses use less physics???
07606                                 //      G_Throw( hitEnt, kickDir, kickPush*4 );
07607                                         //see if we should play a better looking death on them
07608                                 //      G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos );
07609                                         G_TossTheMofo(hitEnt, kickDir, kickPush*4.0f);
07610                                 }
07611                                 else
07612                                 {
07613                                         /*
07614                                         G_Throw( hitEnt, kickDir, kickPush );
07615                                         if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) )
07616                                         {
07617                                                 G_Knockdown( hitEnt, ent, kickDir, 300, qtrue );
07618                                         }
07619                                         else
07620                                         {
07621                                                 G_Knockdown( hitEnt, ent, kickDir, kickPush, qtrue );
07622                                         }
07623                                         */
07624                                         if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) )
07625                                         {
07626                                                 G_TossTheMofo(hitEnt, kickDir, 300.0f);
07627                                         }
07628                                         else
07629                                         {
07630                                                 G_TossTheMofo(hitEnt, kickDir, kickPush);
07631                                         }
07632                                 }
07633                         }
07634                 }
07635         }
07636         return (hitEnt);
07637 }
07638 
07639 static void G_KickSomeMofos(gentity_t *ent)
07640 {
07641         vec3_t  kickDir, kickEnd, fwdAngs;
07642         float animLength = BG_AnimLength( ent->localAnimIndex, (animNumber_t)ent->client->ps.legsAnim );
07643         float elapsedTime = (float)(animLength-ent->client->ps.legsTimer);
07644         float remainingTime = (animLength-elapsedTime);
07645         float kickDist = (ent->r.maxs[0]*1.5f)+STAFF_KICK_RANGE+8.0f;//fudge factor of 8
07646         int       kickDamage = Q_irand(10, 15);//Q_irand( 3, 8 ); //since it can only hit a guy once now
07647         int       kickPush = flrand( 50.0f, 100.0f );
07648         qboolean doKick = qfalse;
07649         renderInfo_t *ri = &ent->client->renderInfo;
07650 
07651         VectorSet(kickDir, 0.0f, 0.0f, 0.0f);
07652         VectorSet(kickEnd, 0.0f, 0.0f, 0.0f);
07653         VectorSet(fwdAngs, 0.0f, ent->client->ps.viewangles[YAW], 0.0f);
07654 
07655         //HMM... or maybe trace from origin to footRBolt/footLBolt?  Which one?  G2 trace?  Will do hitLoc, if so...
07656         if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
07657         {
07658                 if ( elapsedTime >= 250 && remainingTime >= 250 )
07659                 {//front
07660                         doKick = qtrue;
07661                         if ( ri->handRBolt != -1 )
07662                         {//actually trace to a bolt
07663                                 G_GetBoltPosition( ent, ri->handRBolt, kickEnd, 0 );
07664                                 VectorSubtract( kickEnd, ent->client->ps.origin, kickDir );
07665                                 kickDir[2] = 0;//ah, flatten it, I guess...
07666                                 VectorNormalize( kickDir );
07667                         }
07668                         else
07669                         {//guess
07670                                 AngleVectors( fwdAngs, kickDir, NULL, NULL );
07671                         }
07672                 }
07673         }
07674         else
07675         {
07676                 switch ( ent->client->ps.legsAnim )
07677                 {
07678                 case BOTH_GETUP_BROLL_B:
07679                 case BOTH_GETUP_BROLL_F:
07680                 case BOTH_GETUP_FROLL_B:
07681                 case BOTH_GETUP_FROLL_F:
07682                         if ( elapsedTime >= 250 && remainingTime >= 250 )
07683                         {//front
07684                                 doKick = qtrue;
07685                                 if ( ri->footRBolt != -1 )
07686                                 {//actually trace to a bolt
07687                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07688                                         VectorSubtract( kickEnd, ent->client->ps.origin, kickDir );
07689                                         kickDir[2] = 0;//ah, flatten it, I guess...
07690                                         VectorNormalize( kickDir );
07691                                 }
07692                                 else
07693                                 {//guess
07694                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07695                                 }
07696                         }
07697                         break;
07698                 case BOTH_A7_KICK_F_AIR:
07699                 case BOTH_A7_KICK_B_AIR:
07700                 case BOTH_A7_KICK_R_AIR:
07701                 case BOTH_A7_KICK_L_AIR:
07702                         if ( elapsedTime >= 100 && remainingTime >= 250 )
07703                         {//air
07704                                 doKick = qtrue;
07705                                 if ( ri->footRBolt != -1 )
07706                                 {//actually trace to a bolt
07707                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07708                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07709                                         kickDir[2] = 0;//ah, flatten it, I guess...
07710                                         VectorNormalize( kickDir );
07711                                 }
07712                                 else
07713                                 {//guess
07714                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07715                                 }
07716                         }
07717                         break;
07718                 case BOTH_A7_KICK_F:
07719                         //FIXME: push forward?
07720                         if ( elapsedTime >= 250 && remainingTime >= 250 )
07721                         {//front
07722                                 doKick = qtrue;
07723                                 if ( ri->footRBolt != -1 )
07724                                 {//actually trace to a bolt
07725                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07726                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07727                                         kickDir[2] = 0;//ah, flatten it, I guess...
07728                                         VectorNormalize( kickDir );
07729                                 }
07730                                 else
07731                                 {//guess
07732                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07733                                 }
07734                         }
07735                         break;
07736                 case BOTH_A7_KICK_B:
07737                         //FIXME: push back?
07738                         if ( elapsedTime >= 250 && remainingTime >= 250 )
07739                         {//back
07740                                 doKick = qtrue;
07741                                 if ( ri->footRBolt != -1 )
07742                                 {//actually trace to a bolt
07743                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07744                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07745                                         kickDir[2] = 0;//ah, flatten it, I guess...
07746                                         VectorNormalize( kickDir );
07747                                 }
07748                                 else
07749                                 {//guess
07750                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07751                                         VectorScale( kickDir, -1, kickDir );
07752                                 }
07753                         }
07754                         break;
07755                 case BOTH_A7_KICK_R:
07756                         //FIXME: push right?
07757                         if ( elapsedTime >= 250 && remainingTime >= 250 )
07758                         {//right
07759                                 doKick = qtrue;
07760                                 if ( ri->footRBolt != -1 )
07761                                 {//actually trace to a bolt
07762                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07763                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07764                                         kickDir[2] = 0;//ah, flatten it, I guess...
07765                                         VectorNormalize( kickDir );
07766                                 }
07767                                 else
07768                                 {//guess
07769                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07770                                 }
07771                         }
07772                         break;
07773                 case BOTH_A7_KICK_L:
07774                         //FIXME: push left?
07775                         if ( elapsedTime >= 250 && remainingTime >= 250 )
07776                         {//left
07777                                 doKick = qtrue;
07778                                 if ( ri->footLBolt != -1 )
07779                                 {//actually trace to a bolt
07780                                         G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 );
07781                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07782                                         kickDir[2] = 0;//ah, flatten it, I guess...
07783                                         VectorNormalize( kickDir );
07784                                 }
07785                                 else
07786                                 {//guess
07787                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07788                                         VectorScale( kickDir, -1, kickDir );
07789                                 }
07790                         }
07791                         break;
07792                 case BOTH_A7_KICK_S:
07793                         kickPush = flrand( 75.0f, 125.0f );
07794                         if ( ri->footRBolt != -1 )
07795                         {//actually trace to a bolt
07796                                 if ( elapsedTime >= 550 
07797                                         && elapsedTime <= 1050 )
07798                                 {
07799                                         doKick = qtrue;
07800                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07801                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07802                                         kickDir[2] = 0;//ah, flatten it, I guess...
07803                                         VectorNormalize( kickDir );
07804                                         //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
07805                                         VectorMA( kickEnd, 8.0f, kickDir, kickEnd );
07806                                 }
07807                         }
07808                         else
07809                         {//guess
07810                                 if ( elapsedTime >= 400 && elapsedTime < 500 )
07811                                 {//front
07812                                         doKick = qtrue;
07813                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07814                                 }
07815                                 else if ( elapsedTime >= 500 && elapsedTime < 600 )
07816                                 {//front-right?
07817                                         doKick = qtrue;
07818                                         fwdAngs[YAW] += 45;
07819                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07820                                 }
07821                                 else if ( elapsedTime >= 600 && elapsedTime < 700 )
07822                                 {//right
07823                                         doKick = qtrue;
07824                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07825                                 }
07826                                 else if ( elapsedTime >= 700 && elapsedTime < 800 )
07827                                 {//back-right?
07828                                         doKick = qtrue;
07829                                         fwdAngs[YAW] += 45;
07830                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07831                                 }
07832                                 else if ( elapsedTime >= 800 && elapsedTime < 900 )
07833                                 {//back
07834                                         doKick = qtrue;
07835                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07836                                         VectorScale( kickDir, -1, kickDir );
07837                                 }
07838                                 else if ( elapsedTime >= 900 && elapsedTime < 1000 )
07839                                 {//back-left?
07840                                         doKick = qtrue;
07841                                         fwdAngs[YAW] += 45;
07842                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07843                                 }
07844                                 else if ( elapsedTime >= 1000 && elapsedTime < 1100 )
07845                                 {//left
07846                                         doKick = qtrue;
07847                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07848                                         VectorScale( kickDir, -1, kickDir );
07849                                 }
07850                                 else if ( elapsedTime >= 1100 && elapsedTime < 1200 )
07851                                 {//front-left?
07852                                         doKick = qtrue;
07853                                         fwdAngs[YAW] += 45;
07854                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07855                                         VectorScale( kickDir, -1, kickDir );
07856                                 }
07857                         }
07858                         break;
07859                 case BOTH_A7_KICK_BF:
07860                         kickPush = flrand( 75.0f, 125.0f );
07861                         kickDist += 20.0f;
07862                         if ( elapsedTime < 1500 )
07863                         {//auto-aim!
07864         //                      overridAngles = PM_AdjustAnglesForBFKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles;
07865                                 //FIXME: if we haven't done the back kick yet and there's no-one there to
07866                                 //                      kick anymore, go into some anim that returns us to our base stance
07867                         }
07868                         if ( ri->footRBolt != -1 )
07869                         {//actually trace to a bolt
07870                                 if ( ( elapsedTime >= 750 && elapsedTime < 850 )
07871                                         || ( elapsedTime >= 1400 && elapsedTime < 1500 ) )
07872                                 {//right, though either would do
07873                                         doKick = qtrue;
07874                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07875                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07876                                         kickDir[2] = 0;//ah, flatten it, I guess...
07877                                         VectorNormalize( kickDir );
07878                                         //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
07879                                         VectorMA( kickEnd, 8, kickDir, kickEnd );
07880                                 }
07881                         }
07882                         else
07883                         {//guess
07884                                 if ( elapsedTime >= 250 && elapsedTime < 350 )
07885                                 {//front
07886                                         doKick = qtrue;
07887                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07888                                 }
07889                                 else if ( elapsedTime >= 350 && elapsedTime < 450 )
07890                                 {//back
07891                                         doKick = qtrue;
07892                                         AngleVectors( fwdAngs, kickDir, NULL, NULL );
07893                                         VectorScale( kickDir, -1, kickDir );
07894                                 }
07895                         }
07896                         break;
07897                 case BOTH_A7_KICK_RL:
07898                         kickPush = flrand( 75.0f, 125.0f );
07899                         kickDist += 10.0f;
07900 
07901                         //ok, I'm tracing constantly on these things, they NEVER hit otherwise (in MP at least)
07902 
07903                         //FIXME: auto aim at enemies on the side of us?
07904                         //overridAngles = PM_AdjustAnglesForRLKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles;
07905                         //if ( elapsedTime >= 250 && elapsedTime < 350 )
07906                         if (level.framenum&1)
07907                         {//right
07908                                 doKick = qtrue;
07909                                 if ( ri->footRBolt != -1 )
07910                                 {//actually trace to a bolt
07911                                         G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
07912                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07913                                         kickDir[2] = 0;//ah, flatten it, I guess...
07914                                         VectorNormalize( kickDir );
07915                                         //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
07916                                         VectorMA( kickEnd, 8, kickDir, kickEnd );
07917                                 }
07918                                 else
07919                                 {//guess
07920                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07921                                 }
07922                         }
07923                         //else if ( elapsedTime >= 350 && elapsedTime < 450 )
07924                         else
07925                         {//left
07926                                 doKick = qtrue;
07927                                 if ( ri->footLBolt != -1 )
07928                                 {//actually trace to a bolt
07929                                         G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 );
07930                                         VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
07931                                         kickDir[2] = 0;//ah, flatten it, I guess...
07932                                         VectorNormalize( kickDir );
07933                                         //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
07934                                         VectorMA( kickEnd, 8, kickDir, kickEnd );
07935                                 }
07936                                 else
07937                                 {//guess
07938                                         AngleVectors( fwdAngs, NULL, kickDir, NULL );
07939                                         VectorScale( kickDir, -1, kickDir );
07940                                 }
07941                         }
07942                         break;
07943                 }
07944         }
07945 
07946         if ( doKick )
07947         {
07948 //              G_KickTrace( ent, kickDir, kickDist, kickEnd, kickDamage, kickPush );
07949                 G_KickTrace( ent, kickDir, kickDist, NULL, kickDamage, kickPush );
07950         }
07951 }
07952 
07953 static GAME_INLINE qboolean G_PrettyCloseIGuess(float a, float b, float tolerance)
07954 {
07955     if ((a-b) < tolerance &&
07956                 (a-b) > -tolerance)
07957         {
07958                 return qtrue;
07959         }
07960 
07961         return qfalse;
07962 }
07963 
07964 static void G_GrabSomeMofos(gentity_t *self)
07965 {
07966         renderInfo_t *ri = &self->client->renderInfo;
07967         mdxaBone_t boltMatrix;
07968         vec3_t flatAng;
07969         vec3_t pos;
07970         vec3_t grabMins, grabMaxs;
07971         trace_t trace;
07972 
07973         if (!self->ghoul2 || ri->handRBolt == -1)
07974         { //no good
07975                 return;
07976         }
07977 
07978     VectorSet(flatAng, 0.0f, self->client->ps.viewangles[1], 0.0f);
07979         trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, flatAng, self->client->ps.origin,
07980                 level.time, NULL, self->modelScale);
07981         BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pos);
07982 
07983         VectorSet(grabMins, -4.0f, -4.0f, -4.0f);
07984         VectorSet(grabMaxs, 4.0f, 4.0f, 4.0f);
07985 
07986         //trace from my origin to my hand, if we hit anyone then get 'em
07987         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 );
07988     
07989         if (trace.fraction != 1.0f &&
07990                 trace.entityNum < ENTITYNUM_WORLD)
07991         {
07992                 gentity_t *grabbed = &g_entities[trace.entityNum];
07993 
07994                 if (grabbed->inuse && (grabbed->s.eType == ET_PLAYER || grabbed->s.eType == ET_NPC) &&
07995                         grabbed->client && grabbed->health > 0 &&
07996                         G_CanBeEnemy(self, grabbed) &&
07997                         G_PrettyCloseIGuess(grabbed->client->ps.origin[2], self->client->ps.origin[2], 4.0f) &&
07998                         (!BG_InGrappleMove(grabbed->client->ps.torsoAnim) || grabbed->client->ps.torsoAnim == BOTH_KYLE_GRAB) &&
07999                         (!BG_InGrappleMove(grabbed->client->ps.legsAnim) || grabbed->client->ps.legsAnim == BOTH_KYLE_GRAB))
08000                 { //grabbed an active player/npc
08001                         int tortureAnim = -1;
08002                         int correspondingAnim = -1;
08003 
08004                         if (self->client->pers.cmd.forwardmove > 0)
08005                         { //punch grab
08006                                 tortureAnim = BOTH_KYLE_PA_1;
08007                                 correspondingAnim = BOTH_PLAYER_PA_1;
08008                         }
08009                         else if (self->client->pers.cmd.forwardmove < 0)
08010                         { //knee-throw
08011                                 tortureAnim = BOTH_KYLE_PA_2;
08012                                 correspondingAnim = BOTH_PLAYER_PA_2;
08013                         }
08014 
08015                         if (tortureAnim == -1 || correspondingAnim == -1)
08016                         {
08017                                 if (self->client->ps.torsoTimer < 300 && !self->client->grappleState)
08018                                 { //you failed to grab anyone, play the "failed to grab" anim
08019                                         G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
08020                                         if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
08021                                         { //providing the anim set succeeded..
08022                                                 self->client->ps.weaponTime = self->client->ps.torsoTimer;
08023                                         }
08024                                 }
08025                                 return;
08026                         }
08027 
08028                         self->client->grappleIndex = grabbed->s.number;
08029                         self->client->grappleState = 1;
08030 
08031                         grabbed->client->grappleIndex = self->s.number;
08032                         grabbed->client->grappleState = 20;
08033 
08034                         //time to crack some heads
08035                         G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, tortureAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
08036                         if (self->client->ps.torsoAnim == tortureAnim)
08037                         { //providing the anim set succeeded..
08038                                 self->client->ps.weaponTime = self->client->ps.torsoTimer;
08039                         }
08040 
08041                         G_SetAnim(grabbed, &grabbed->client->pers.cmd, SETANIM_BOTH, correspondingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
08042                         if (grabbed->client->ps.torsoAnim == correspondingAnim)
08043                         { //providing the anim set succeeded..
08044                                 if (grabbed->client->ps.weapon == WP_SABER)
08045                                 { //turn it off
08046                                         if (!grabbed->client->ps.saberHolstered)
08047                                         {
08048                                                 grabbed->client->ps.saberHolstered = 2;
08049                                                 if (grabbed->client->saber[0].soundOff)
08050                                                 {
08051                                                         G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[0].soundOff);
08052                                                 }
08053                                                 if (grabbed->client->saber[1].soundOff &&
08054                                                         grabbed->client->saber[1].model[0])
08055                                                 {
08056                                                         G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[1].soundOff);
08057                                                 }
08058                                         }
08059                                 }
08060                                 if (grabbed->client->ps.torsoTimer < self->client->ps.torsoTimer)
08061                                 { //make sure they stay in the anim at least as long as the grabber
08062                                         grabbed->client->ps.torsoTimer = self->client->ps.torsoTimer;
08063                                 }
08064                                 grabbed->client->ps.weaponTime = grabbed->client->ps.torsoTimer;
08065                         }
08066                 }
08067         }
08068 
08069         if (self->client->ps.torsoTimer < 300 && !self->client->grappleState)
08070         { //you failed to grab anyone, play the "failed to grab" anim
08071                 G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
08072                 if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
08073                 { //providing the anim set succeeded..
08074                         self->client->ps.weaponTime = self->client->ps.torsoTimer;
08075                 }
08076         }
08077 }
08078 
08079 void WP_SaberPositionUpdate( gentity_t *self, usercmd_t *ucmd )
08080 { //rww - keep the saber position as updated as possible on the server so that we can try to do realistic-looking contact stuff
08081   //Note that this function also does the majority of working in maintaining the server g2 client instance (updating angles/anims/etc)
08082         gentity_t *mySaber = NULL;
08083         mdxaBone_t      boltMatrix;
08084         vec3_t properAngles, properOrigin;
08085         vec3_t boltAngles, boltOrigin;
08086         vec3_t end;
08087         vec3_t legAxis[3];
08088         vec3_t addVel;
08089         vec3_t rawAngles;
08090         float fVSpeed = 0;
08091         int returnAfterUpdate = 0;
08092         float animSpeedScale = 1.0f;
08093         int saberNum;
08094         qboolean clientOverride;
08095         gentity_t *vehEnt = NULL;
08096         int rSaberNum = 0;
08097         int rBladeNum = 0;
08098 
08099 #ifdef _DEBUG
08100         if (g_disableServerG2.integer)
08101         {
08102                 return;
08103         }
08104 #endif
08105 
08106         if (self && self->inuse && self->client)
08107         {
08108                 if (self->client->saberCycleQueue)
08109                 {
08110                         self->client->ps.fd.saberDrawAnimLevel = self->client->saberCycleQueue;
08111                 }
08112                 else
08113                 {
08114                         self->client->ps.fd.saberDrawAnimLevel = self->client->ps.fd.saberAnimLevel;
08115                 }
08116         }
08117 
08118         if (self &&
08119                 self->inuse &&
08120                 self->client &&
08121                 self->client->saberCycleQueue &&
08122                 (self->client->ps.weaponTime <= 0 || self->health < 1))
08123         { //we cycled attack levels while we were busy, so update now that we aren't (even if that means we're dead)
08124                 self->client->ps.fd.saberAnimLevel = self->client->saberCycleQueue;
08125                 self->client->saberCycleQueue = 0;
08126         }
08127 
08128         if (!self ||
08129                 !self->inuse ||
08130                 !self->client ||
08131                 !self->ghoul2 ||
08132                 !g2SaberInstance)
08133         {
08134                 return;
08135         }
08136 
08137         if (BG_KickingAnim(self->client->ps.legsAnim))
08138         { //do some kick traces and stuff if we're in the appropriate anim
08139                 G_KickSomeMofos(self);
08140         }
08141         else if (self->client->ps.torsoAnim == BOTH_KYLE_GRAB)
08142         { //try to grab someone
08143                 G_GrabSomeMofos(self);
08144         }
08145         else if (self->client->grappleState)
08146         {
08147                 gentity_t *grappler = &g_entities[self->client->grappleIndex];
08148 
08149                 if (!grappler->inuse || !grappler->client || grappler->client->grappleIndex != self->s.number ||
08150                         !BG_InGrappleMove(grappler->client->ps.torsoAnim) || !BG_InGrappleMove(grappler->client->ps.legsAnim) ||
08151                         !BG_InGrappleMove(self->client->ps.torsoAnim) || !BG_InGrappleMove(self->client->ps.legsAnim) ||
08152                         !self->client->grappleState || !grappler->client->grappleState ||
08153                         grappler->health < 1 || self->health < 1 ||
08154                         !G_PrettyCloseIGuess(self->client->ps.origin[2], grappler->client->ps.origin[2], 4.0f))
08155                 {
08156                         self->client->grappleState = 0;
08157                         if ((BG_InGrappleMove(self->client->ps.torsoAnim) && self->client->ps.torsoTimer > 100) ||
08158                                 (BG_InGrappleMove(self->client->ps.legsAnim) && self->client->ps.legsTimer > 100))
08159                         { //if they're pretty far from finishing the anim then shove them into another anim
08160                                 G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
08161                                 if (self->client->ps.torsoAnim ==