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