1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6 
7 This file is part of the OpenJK source code.
8 
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22 
23 #include "g_local.h"
24 #include "bg_local.h"
25 #include "w_saber.h"
26 #include "ai_main.h"
27 #include "ghoul2/G2.h"
28 
29 #define SABER_BOX_SIZE 16.0f
30 extern bot_state_t *botstates[MAX_CLIENTS];
31 extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold );
32 extern void G_TestLine(vec3_t start, vec3_t end, int color, int time);
33 
34 int saberSpinSound = 0;
35 
36 //would be cleaner if these were renamed to BG_ and proto'd in a header.
37 qboolean PM_SaberInTransition( int move );
38 qboolean PM_SaberInDeflect( int move );
39 qboolean PM_SaberInBrokenParry( int move );
40 qboolean PM_SaberInBounce( int move );
41 qboolean BG_SaberInReturn( int move );
42 qboolean BG_InKnockDownOnGround( playerState_t *ps );
43 qboolean BG_StabDownAnim( int anim );
44 qboolean BG_SabersOff( playerState_t *ps );
45 qboolean BG_SaberInTransitionAny( int move );
46 qboolean BG_SaberInAttackPure( int move );
47 qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum );
48 qboolean WP_SaberBladeDoTransitionDamage( saberInfo_t *saber, int bladeNum );
49 
50 void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin );
51 void WP_SaberRemoveG2Model( gentity_t *saberent );
52 
53 //	g_randFix 0 == Same as basejka. Broken on Linux, fine on Windows
54 //	g_randFix 1 == Use proper behaviour of RAND_MAX. Fine on Linux, fine on Windows
55 //	g_randFix 2 == Intentionally break RAND_MAX. Broken on Linux, broken on Windows.
RandFloat(float min,float max)56 float RandFloat( float min, float max ) {
57 	int randActual = rand();
58 	float randMax = 32768.0f;
59 #ifdef _WIN32
60 	if ( g_randFix.integer == 2 )
61 		randActual = (randActual<<16)|randActual;
62 #elif defined(__GCC__)
63 	if ( g_randFix.integer == 1 )
64 		randMax = RAND_MAX;
65 #endif
66 	return ((randActual * (max - min)) / randMax) + min;
67 }
68 
69 #ifdef DEBUG_SABER_BOX
G_DebugBoxLines(vec3_t mins,vec3_t maxs,int duration)70 void	G_DebugBoxLines(vec3_t mins, vec3_t maxs, int duration)
71 {
72 	vec3_t start;
73 	vec3_t end;
74 
75 	float x = maxs[0] - mins[0];
76 	float y = maxs[1] - mins[1];
77 
78 	// top of box
79 	VectorCopy(maxs, start);
80 	VectorCopy(maxs, end);
81 	start[0] -= x;
82 	G_TestLine(start, end, 0x00000ff, duration);
83 	end[0] = start[0];
84 	end[1] -= y;
85 	G_TestLine(start, end, 0x00000ff, duration);
86 	start[1] = end[1];
87 	start[0] += x;
88 	G_TestLine(start, end, 0x00000ff, duration);
89 	G_TestLine(start, maxs, 0x00000ff, duration);
90 	// bottom of box
91 	VectorCopy(mins, start);
92 	VectorCopy(mins, end);
93 	start[0] += x;
94 	G_TestLine(start, end, 0x00000ff, duration);
95 	end[0] = start[0];
96 	end[1] += y;
97 	G_TestLine(start, end, 0x00000ff, duration);
98 	start[1] = end[1];
99 	start[0] -= x;
100 	G_TestLine(start, end, 0x00000ff, duration);
101 	G_TestLine(start, mins, 0x00000ff, duration);
102 }
103 #endif
104 
105 //general check for performing certain attacks against others
G_CanBeEnemy(gentity_t * self,gentity_t * enemy)106 qboolean G_CanBeEnemy( gentity_t *self, gentity_t *enemy )
107 {
108 	//ptrs!
109 	if ( !self->inuse || !enemy->inuse || !self->client || !enemy->client )
110 		return qfalse;
111 
112 	if (level.gametype < GT_TEAM)
113 		return qtrue;
114 
115 	if ( g_friendlyFire.integer )
116 		return qtrue;
117 
118 	if ( OnSameTeam( self, enemy ) )
119 		return qfalse;
120 
121 	return qtrue;
122 }
123 
124 //This function gets the attack power which is used to decide broken parries,
125 //knockaways, and numerous other things. It is not directly related to the
126 //actual amount of damage done, however. -rww
G_SaberAttackPower(gentity_t * ent,qboolean attacking)127 static QINLINE int G_SaberAttackPower(gentity_t *ent, qboolean attacking)
128 {
129 	int baseLevel;
130 	assert(ent && ent->client);
131 
132 	baseLevel = ent->client->ps.fd.saberAnimLevel;
133 
134 	//Give "medium" strength for the two special stances.
135 	if (baseLevel == SS_DUAL)
136 	{
137 		baseLevel = 2;
138 	}
139 	else if (baseLevel == SS_STAFF)
140 	{
141 		baseLevel = 2;
142 	}
143 
144 	if (attacking)
145 	{ //the attacker gets a boost to help penetrate defense.
146 		//General boost up so the individual levels make a bigger difference.
147 		baseLevel *= 2;
148 
149 		baseLevel++;
150 
151 		//Get the "speed" of the swing, roughly, and add more power
152 		//to the attack based on it.
153 		if (ent->client->lastSaberStorageTime >= (level.time-50) &&
154 			ent->client->olderIsValid)
155 		{
156 			vec3_t vSub;
157 			int swingDist;
158 			int toleranceAmt;
159 
160 			//We want different "tolerance" levels for adding in the distance of the last swing
161 			//to the base power level depending on which stance we are using. Otherwise fast
162 			//would have more advantage than it should since the animations are all much faster.
163 			switch (ent->client->ps.fd.saberAnimLevel)
164 			{
165 			case SS_STRONG:
166 				toleranceAmt = 8;
167 				break;
168 			case SS_MEDIUM:
169 				toleranceAmt = 16;
170 				break;
171 			case SS_FAST:
172 				toleranceAmt = 24;
173 				break;
174 			default: //dual, staff, etc.
175 				toleranceAmt = 16;
176 				break;
177 			}
178 
179             VectorSubtract(ent->client->lastSaberBase_Always, ent->client->olderSaberBase, vSub);
180 			swingDist = (int)VectorLength(vSub);
181 
182 			while (swingDist > 0)
183 			{ //I would like to do something more clever. But I suppose this works, at least for now.
184 				baseLevel++;
185 				swingDist -= toleranceAmt;
186 			}
187 		}
188 
189 #ifndef FINAL_BUILD
190 		if (g_saberDebugPrint.integer > 1)
191 		{
192 			Com_Printf("Client %i: ATT STR: %i\n", ent->s.number, baseLevel);
193 		}
194 #endif
195 	}
196 
197 	if ((ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) ||
198 		(ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
199 	{ //We're very weak when one of our arms is broken
200 		baseLevel *= 0.3;
201 	}
202 
203 	//Cap at reasonable values now.
204 	if (baseLevel < 1)
205 	{
206 		baseLevel = 1;
207 	}
208 	else if (baseLevel > 16)
209 	{
210 		baseLevel = 16;
211 	}
212 
213 	if (level.gametype == GT_POWERDUEL &&
214 		ent->client->sess.duelTeam == DUELTEAM_LONE)
215 	{ //get more power then
216 		return baseLevel*2;
217 	}
218 	else if (attacking && level.gametype == GT_SIEGE)
219 	{ //in siege, saber battles should be quicker and more biased toward the attacker
220 		return baseLevel*3;
221 	}
222 
223 	return baseLevel;
224 }
225 
WP_DeactivateSaber(gentity_t * self,qboolean clearLength)226 void WP_DeactivateSaber( gentity_t *self, qboolean clearLength )
227 {
228 	if ( !self || !self->client )
229 	{
230 		return;
231 	}
232 	//keep my saber off!
233 	if ( !self->client->ps.saberHolstered )
234 	{
235 		self->client->ps.saberHolstered = 2;
236 		/*
237 		if ( clearLength )
238 		{
239 			self->client->ps.SetSaberLength( 0 );
240 		}
241 		*/
242 		//Doens't matter ATM
243 		if (self->client->saber[0].soundOff)
244 		{
245 			G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOff);
246 		}
247 
248 		if (self->client->saber[1].soundOff &&
249 			self->client->saber[1].model[0])
250 		{
251 			G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOff);
252 		}
253 
254 	}
255 }
256 
WP_ActivateSaber(gentity_t * self)257 void WP_ActivateSaber( gentity_t *self )
258 {
259 	if ( !self || !self->client )
260 	{
261 		return;
262 	}
263 
264 	if (self->NPC &&
265 		self->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT &&
266 		(self->client->ps.forceHandExtendTime - level.time) > 200)
267 	{ //if we're an NPC and in the middle of a taunt then stop it
268 		self->client->ps.forceHandExtend = HANDEXTEND_NONE;
269 		self->client->ps.forceHandExtendTime = 0;
270 	}
271 	else if (self->client->ps.fd.forceGripCripple)
272 	{ //can't activate saber while being gripped
273 		return;
274 	}
275 
276 	if ( self->client->ps.saberHolstered )
277 	{
278 		self->client->ps.saberHolstered = 0;
279 		if (self->client->saber[0].soundOn)
280 		{
281 			G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOn);
282 		}
283 
284 		if (self->client->saber[1].soundOn)
285 		{
286 			G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOn);
287 		}
288 	}
289 }
290 
291 #define PROPER_THROWN_VALUE 999 //Ah, well..
292 
SaberUpdateSelf(gentity_t * ent)293 void SaberUpdateSelf(gentity_t *ent)
294 {
295 	if (ent->r.ownerNum == ENTITYNUM_NONE)
296 	{
297 		ent->think = G_FreeEntity;
298 		ent->nextthink = level.time;
299 		return;
300 	}
301 
302 	if (!g_entities[ent->r.ownerNum].inuse ||
303 		!g_entities[ent->r.ownerNum].client/* ||
304 		g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR*/)
305 	{
306 		ent->think = G_FreeEntity;
307 		ent->nextthink = level.time;
308 		return;
309 	}
310 
311 	if (g_entities[ent->r.ownerNum].client->ps.saberInFlight && g_entities[ent->r.ownerNum].health > 0)
312 	{ //let The Master take care of us now (we'll get treated like a missile until we return)
313 		ent->nextthink = level.time;
314 		ent->genericValue5 = PROPER_THROWN_VALUE;
315 		return;
316 	}
317 
318 	ent->genericValue5 = 0;
319 
320 	if (g_entities[ent->r.ownerNum].client->ps.weapon != WP_SABER ||
321 		(g_entities[ent->r.ownerNum].client->ps.pm_flags & PMF_FOLLOW) ||
322 		//RWW ADDED 7-19-03 BEGIN
323 		g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR ||
324 		g_entities[ent->r.ownerNum].client->tempSpectate >= level.time ||
325 		//RWW ADDED 7-19-03 END
326 		g_entities[ent->r.ownerNum].health < 1 ||
327 		BG_SabersOff( &g_entities[ent->r.ownerNum].client->ps ) ||
328 		(!g_entities[ent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] && g_entities[ent->r.ownerNum].s.eType != ET_NPC))
329 	{ //owner is not using saber, spectating, dead, saber holstered, or has no attack level
330 		ent->r.contents = 0;
331 		ent->clipmask = 0;
332 	}
333 	else
334 	{ //Standard contents (saber is active)
335 #ifdef DEBUG_SABER_BOX
336 		if (g_saberDebugBox.integer == 1|| g_saberDebugBox.integer == 4)
337 		{
338 			vec3_t dbgMins;
339 			vec3_t dbgMaxs;
340 
341 			VectorAdd( ent->r.currentOrigin, ent->r.mins, dbgMins );
342 			VectorAdd( ent->r.currentOrigin, ent->r.maxs, dbgMaxs );
343 
344 			G_DebugBoxLines(dbgMins, dbgMaxs, (10.0f/(float)sv_fps.integer)*100);
345 		}
346 #endif
347 		if (ent->r.contents != CONTENTS_LIGHTSABER)
348 		{
349 			if ((level.time - g_entities[ent->r.ownerNum].client->lastSaberStorageTime) <= 200)
350 			{ //Only go back to solid once we're sure our owner has updated recently
351 				ent->r.contents = CONTENTS_LIGHTSABER;
352 				ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
353 			}
354 		}
355 		else
356 		{
357 			ent->r.contents = CONTENTS_LIGHTSABER;
358 			ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
359 		}
360 	}
361 
362 	trap->LinkEntity((sharedEntity_t *)ent);
363 
364 	ent->nextthink = level.time;
365 }
366 
SaberGotHit(gentity_t * self,gentity_t * other,trace_t * trace)367 void SaberGotHit( gentity_t *self, gentity_t *other, trace_t *trace )
368 {
369 	gentity_t *own = &g_entities[self->r.ownerNum];
370 
371 	if (!own || !own->client)
372 	{
373 		return;
374 	}
375 
376 	//Do something here..? Was handling projectiles here, but instead they're now handled in their own functions.
377 }
378 
379 qboolean BG_SuperBreakLoseAnim( int anim );
380 
SetSaberBoxSize(gentity_t * saberent)381 static QINLINE void SetSaberBoxSize(gentity_t *saberent)
382 {
383 	gentity_t *owner = NULL;
384 	vec3_t saberOrg, saberTip;
385 	int i;
386 	int j = 0;
387 	int k = 0;
388 	qboolean dualSabers = qfalse;
389 	qboolean alwaysBlock[MAX_SABERS][MAX_BLADES];
390 	qboolean forceBlock = qfalse;
391 
392 	assert(saberent && saberent->inuse);
393 
394 	if (saberent->r.ownerNum < MAX_CLIENTS && saberent->r.ownerNum >= 0)
395 	{
396 		owner = &g_entities[saberent->r.ownerNum];
397 	}
398 	else if (saberent->r.ownerNum >= 0 && saberent->r.ownerNum < ENTITYNUM_WORLD &&
399 		g_entities[saberent->r.ownerNum].s.eType == ET_NPC)
400 	{
401 		owner = &g_entities[saberent->r.ownerNum];
402 	}
403 
404 	if (!owner || !owner->inuse || !owner->client)
405 	{
406 		assert(!"Saber with no owner?");
407 		return;
408 	}
409 
410 	if ( owner->client->saber[1].model[0] )
411 	{
412 		dualSabers = qtrue;
413 	}
414 
415 	if ( PM_SaberInBrokenParry(owner->client->ps.saberMove)
416 		|| BG_SuperBreakLoseAnim( owner->client->ps.torsoAnim ) )
417 	{ //let swings go right through when we're in this state
418 		for ( i = 0; i < MAX_SABERS; i++ )
419 		{
420 			if ( i > 0 && !dualSabers )
421 			{//not using a second saber, set it to not blocking
422 				for ( j = 0; j < MAX_BLADES; j++ )
423 				{
424 					alwaysBlock[i][j] = qfalse;
425 				}
426 			}
427 			else
428 			{
429 				if ( (owner->client->saber[i].saberFlags2&SFL2_ALWAYS_BLOCK) )
430 				{
431 					for ( j = 0; j < owner->client->saber[i].numBlades; j++ )
432 					{
433 						alwaysBlock[i][j] = qtrue;
434 						forceBlock = qtrue;
435 					}
436 				}
437 				if ( owner->client->saber[i].bladeStyle2Start > 0 )
438 				{
439 					for ( j = owner->client->saber[i].bladeStyle2Start; j < owner->client->saber[i].numBlades; j++ )
440 					{
441 						if ( (owner->client->saber[i].saberFlags2&SFL2_ALWAYS_BLOCK2) )
442 						{
443 							alwaysBlock[i][j] = qtrue;
444 							forceBlock = qtrue;
445 						}
446 						else
447 						{
448 							alwaysBlock[i][j] = qfalse;
449 						}
450 					}
451 				}
452 			}
453 		}
454 		if ( !forceBlock )
455 		{//no sabers/blades to FORCE to be on, so turn off blocking altogether
456 			VectorSet( saberent->r.mins, 0, 0, 0 );
457 			VectorSet( saberent->r.maxs, 0, 0, 0 );
458 #ifndef FINAL_BUILD
459 			if (g_saberDebugPrint.integer > 1)
460 			{
461 				Com_Printf("Client %i in broken parry, saber box 0\n", owner->s.number);
462 			}
463 #endif
464 			return;
465 		}
466 	}
467 
468 	if ((level.time - owner->client->lastSaberStorageTime) > 200 ||
469 		(level.time - owner->client->saber[j].blade[k].storageTime) > 100)
470 	{ //it's been too long since we got a reliable point storage, so use the defaults and leave.
471 		VectorSet( saberent->r.mins, -SABER_BOX_SIZE, -SABER_BOX_SIZE, -SABER_BOX_SIZE );
472 		VectorSet( saberent->r.maxs, SABER_BOX_SIZE, SABER_BOX_SIZE, SABER_BOX_SIZE );
473 		return;
474 	}
475 
476 	if ( dualSabers
477 		|| owner->client->saber[0].numBlades > 1 )
478 	{//dual sabers or multi-blade saber
479 		if ( owner->client->ps.saberHolstered > 1 )
480 		{//entirely off
481 			//no blocking at all
482 			VectorSet( saberent->r.mins, 0, 0, 0 );
483 			VectorSet( saberent->r.maxs, 0, 0, 0 );
484 			return;
485 		}
486 	}
487 	else
488 	{//single saber
489 		if ( owner->client->ps.saberHolstered )
490 		{//off
491 			//no blocking at all
492 			VectorSet( saberent->r.mins, 0, 0, 0 );
493 			VectorSet( saberent->r.maxs, 0, 0, 0 );
494 			return;
495 		}
496 	}
497 	//Start out at the saber origin, then go through all the blades and push out the extents
498 	//for each blade, then set the box relative to the origin.
499 	VectorCopy(saberent->r.currentOrigin, saberent->r.mins);
500 	VectorCopy(saberent->r.currentOrigin, saberent->r.maxs);
501 
502 	for (i = 0; i < 3; i++)
503 	{
504 		for (j = 0; j < MAX_SABERS; j++)
505 		{
506 			if (!owner->client->saber[j].model[0])
507 			{
508 				break;
509 			}
510 			if ( dualSabers
511 				&& owner->client->ps.saberHolstered == 1
512 				&& j == 1 )
513 			{ //this mother is holstered, get outta here.
514 				j++;
515 				continue;
516 			}
517 			for (k = 0; k < owner->client->saber[j].numBlades; k++)
518 			{
519 				if ( k > 0 )
520 				{//not the first blade
521 					if ( !dualSabers )
522 					{//using a single saber
523 						if ( owner->client->saber[j].numBlades > 1 )
524 						{//with multiple blades
525 							if( owner->client->ps.saberHolstered == 1 )
526 							{//all blades after the first one are off
527 								break;
528 							}
529 						}
530 					}
531 				}
532 				if ( forceBlock )
533 				{//only do blocking with blades that are marked to block
534 					if ( !alwaysBlock[j][k] )
535 					{//this blade shouldn't be blocking
536 						continue;
537 					}
538 				}
539 				//VectorMA(owner->client->saber[j].blade[k].muzzlePoint, owner->client->saber[j].blade[k].lengthMax*0.5f, owner->client->saber[j].blade[k].muzzleDir, saberOrg);
540 				VectorCopy(owner->client->saber[j].blade[k].muzzlePoint, saberOrg);
541 				VectorMA(owner->client->saber[j].blade[k].muzzlePoint, owner->client->saber[j].blade[k].lengthMax, owner->client->saber[j].blade[k].muzzleDir, saberTip);
542 
543 				if (saberOrg[i] < saberent->r.mins[i])
544 				{
545 					saberent->r.mins[i] = saberOrg[i];
546 				}
547 				if (saberTip[i] < saberent->r.mins[i])
548 				{
549 					saberent->r.mins[i] = saberTip[i];
550 				}
551 
552 				if (saberOrg[i] > saberent->r.maxs[i])
553 				{
554 					saberent->r.maxs[i] = saberOrg[i];
555 				}
556 				if (saberTip[i] > saberent->r.maxs[i])
557 				{
558 					saberent->r.maxs[i] = saberTip[i];
559 				}
560 
561 				//G_TestLine(saberOrg, saberTip, 0x0000ff, 50);
562 			}
563 		}
564 	}
565 
566 	VectorSubtract(saberent->r.mins, saberent->r.currentOrigin, saberent->r.mins);
567 	VectorSubtract(saberent->r.maxs, saberent->r.currentOrigin, saberent->r.maxs);
568 }
569 
WP_SaberInitBladeData(gentity_t * ent)570 void WP_SaberInitBladeData( gentity_t *ent )
571 {
572 	gentity_t *saberent = NULL;
573 	gentity_t *checkEnt;
574 	int i = 0;
575 
576 	while (i < level.num_entities)
577 	{ //make sure there are no other saber entities floating around that think they belong to this client.
578 		checkEnt = &g_entities[i];
579 
580 		if (checkEnt->inuse && checkEnt->neverFree &&
581 			checkEnt->r.ownerNum == ent->s.number &&
582 			checkEnt->classname && checkEnt->classname[0] &&
583 			!Q_stricmp(checkEnt->classname, "lightsaber"))
584 		{
585 			if (saberent)
586 			{ //already have one
587 				checkEnt->neverFree = qfalse;
588 				checkEnt->think = G_FreeEntity;
589 				checkEnt->nextthink = level.time;
590 			}
591 			else
592 			{ //hmm.. well then, take it as my own.
593 				//free the bitch but don't issue a kg2 to avoid overflowing clients.
594 				checkEnt->s.modelGhoul2 = 0;
595 				G_FreeEntity(checkEnt);
596 
597 				//now init it manually and reuse this ent slot.
598 				G_InitGentity(checkEnt);
599 				saberent = checkEnt;
600 			}
601 		}
602 
603 		i++;
604 	}
605 
606 	//We do not want the client to have any real knowledge of the entity whatsoever. It will only
607 	//ever be used on the server.
608 	if (!saberent)
609 	{ //ok, make one then
610 		saberent = G_Spawn();
611 	}
612 	ent->client->ps.saberEntityNum = ent->client->saberStoredIndex = saberent->s.number;
613 	saberent->classname = "lightsaber";
614 
615 	saberent->neverFree = qtrue; //the saber being removed would be a terrible thing.
616 
617 	saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
618 	saberent->r.ownerNum = ent->s.number;
619 
620 	saberent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER;
621 	saberent->r.contents = CONTENTS_LIGHTSABER;
622 
623 	SetSaberBoxSize(saberent);
624 
625 	saberent->mass = 10;
626 
627 	saberent->s.eFlags |= EF_NODRAW;
628 	saberent->r.svFlags |= SVF_NOCLIENT;
629 
630 	saberent->s.modelGhoul2 = 1;
631 	//should we happen to be removed (we belong to an NPC and he is removed) then
632 	//we want to attempt to remove our g2 instance on the client in case we had one.
633 
634 	saberent->touch = SaberGotHit;
635 
636 	saberent->think = SaberUpdateSelf;
637 	saberent->genericValue5 = 0;
638 	saberent->nextthink = level.time + 50;
639 
640 	saberSpinSound = G_SoundIndex("sound/weapons/saber/saberspin.wav");
641 }
642 
643 #define LOOK_DEFAULT_SPEED	0.15f
644 #define LOOK_TALKING_SPEED	0.15f
645 
G_CheckLookTarget(gentity_t * ent,vec3_t lookAngles,float * lookingSpeed)646 static QINLINE qboolean G_CheckLookTarget( gentity_t *ent, vec3_t	lookAngles, float *lookingSpeed )
647 {
648 	//FIXME: also clamp the lookAngles based on the clamp + the existing difference between
649 	//		headAngles and torsoAngles?  But often the tag_torso is straight but the torso itself
650 	//		is deformed to not face straight... sigh...
651 
652 	if (ent->s.eType == ET_NPC &&
653 		ent->s.m_iVehicleNum &&
654 		ent->s.NPC_class != CLASS_VEHICLE )
655 	{ //an NPC bolted to a vehicle should just look around randomly
656 		if ( TIMER_Done( ent, "lookAround" ) )
657 		{
658 			ent->NPC->shootAngles[YAW] = flrand(0,360);
659 			TIMER_Set( ent, "lookAround", Q_irand( 500, 3000 ) );
660 		}
661 		VectorSet( lookAngles, 0, ent->NPC->shootAngles[YAW], 0 );
662 		return qtrue;
663 	}
664 	//Now calc head angle to lookTarget, if any
665 	if ( ent->client->renderInfo.lookTarget >= 0 && ent->client->renderInfo.lookTarget < ENTITYNUM_WORLD )
666 	{
667 		vec3_t	lookDir, lookOrg, eyeOrg;
668 		int i;
669 
670 		if ( ent->client->renderInfo.lookMode == LM_ENT )
671 		{
672 			gentity_t	*lookCent = &g_entities[ent->client->renderInfo.lookTarget];
673 			if ( lookCent )
674 			{
675 				if ( lookCent != ent->enemy )
676 				{//We turn heads faster than headbob speed, but not as fast as if watching an enemy
677 					*lookingSpeed = LOOK_DEFAULT_SPEED;
678 				}
679 
680 				//FIXME: Ignore small deltas from current angles so we don't bob our head in synch with theirs?
681 
682 				/*
683 				if ( ent->client->renderInfo.lookTarget == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer )
684 				{//Special case- use cg.refdef.vieworg if looking at player and not in third person view
685 					VectorCopy( cg.refdef.vieworg, lookOrg );
686 				}
687 				*/ //No no no!
688 				if ( lookCent->client )
689 				{
690 					VectorCopy( lookCent->client->renderInfo.eyePoint, lookOrg );
691 				}
692 				else if ( lookCent->inuse && !VectorCompare( lookCent->r.currentOrigin, vec3_origin ) )
693 				{
694 					VectorCopy( lookCent->r.currentOrigin, lookOrg );
695 				}
696 				else
697 				{//at origin of world
698 					return qfalse;
699 				}
700 				//Look in dir of lookTarget
701 			}
702 		}
703 		else if ( ent->client->renderInfo.lookMode == LM_INTEREST && ent->client->renderInfo.lookTarget > -1 && ent->client->renderInfo.lookTarget < MAX_INTEREST_POINTS )
704 		{
705 			VectorCopy( level.interestPoints[ent->client->renderInfo.lookTarget].origin, lookOrg );
706 		}
707 		else
708 		{
709 			return qfalse;
710 		}
711 
712 		VectorCopy( ent->client->renderInfo.eyePoint, eyeOrg );
713 
714 		VectorSubtract( lookOrg, eyeOrg, lookDir );
715 
716 		vectoangles( lookDir, lookAngles );
717 
718 		for ( i = 0; i < 3; i++ )
719 		{
720 			lookAngles[i] = AngleNormalize180( lookAngles[i] );
721 			ent->client->renderInfo.eyeAngles[i] = AngleNormalize180( ent->client->renderInfo.eyeAngles[i] );
722 		}
723 		AnglesSubtract( lookAngles, ent->client->renderInfo.eyeAngles, lookAngles );
724 		return qtrue;
725 	}
726 
727 	return qfalse;
728 }
729 
730 //rww - attempted "port" of the SP version which is completely client-side and
731 //uses illegal gentity access. I am trying to keep this from being too
732 //bandwidth-intensive.
733 //This is primarily droid stuff I guess, I'm going to try to handle all humanoid
734 //NPC stuff in with the actual player stuff if possible.
735 void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles);
G_G2NPCAngles(gentity_t * ent,matrix3_t legs,vec3_t angles)736 static QINLINE void G_G2NPCAngles(gentity_t *ent, matrix3_t legs, vec3_t angles)
737 {
738 	char *craniumBone = "cranium";
739 	char *thoracicBone = "thoracic"; //only used by atst so doesn't need a case
740 	qboolean looking = qfalse;
741 	vec3_t viewAngles;
742 	vec3_t lookAngles;
743 
744 	if ( ent->client )
745 	{
746 		if ( (ent->client->NPC_class == CLASS_PROBE )
747 			|| (ent->client->NPC_class == CLASS_R2D2 )
748 			|| (ent->client->NPC_class == CLASS_R5D2)
749 			|| (ent->client->NPC_class == CLASS_ATST) )
750 		{
751 			vec3_t	trailingLegsAngles;
752 
753 			if (ent->s.eType == ET_NPC &&
754 				ent->s.m_iVehicleNum &&
755 				ent->s.NPC_class != CLASS_VEHICLE )
756 			{ //an NPC bolted to a vehicle should use the full angles
757 				VectorCopy(ent->r.currentAngles, angles);
758 			}
759 			else
760 			{
761 				VectorCopy( ent->client->ps.viewangles, angles );
762 				angles[PITCH] = 0;
763 			}
764 
765 			//FIXME: use actual swing/clamp tolerances?
766 			/*
767 			if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
768 			{//on the ground
769 				CG_PlayerLegsYawFromMovement( cent, ent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
770 			}
771 			else
772 			{//face legs to front
773 				CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue );
774 			}
775 			*/
776 
777 			VectorCopy( ent->client->ps.viewangles, viewAngles );
778 	//			viewAngles[YAW] = viewAngles[ROLL] = 0;
779 			viewAngles[PITCH] *= 0.5;
780 			VectorCopy( viewAngles, lookAngles );
781 
782 			lookAngles[1] = 0;
783 
784 			if ( ent->client->NPC_class == CLASS_ATST )
785 			{//body pitch
786 				NPC_SetBoneAngles(ent, thoracicBone, lookAngles);
787 				//BG_G2SetBoneAngles( cent, ent, ent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT,POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
788 			}
789 
790 			VectorCopy( viewAngles, lookAngles );
791 
792 			if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST )
793 			{
794 				//CG_ATSTLegsYaw( cent, trailingLegsAngles );
795 				AnglesToAxis( trailingLegsAngles, legs );
796 			}
797 			else
798 			{
799 				//FIXME: this needs to properly set the legs.yawing field so we don't erroneously play the turning anim, but we do play it when turning in place
800 				/*
801 				if ( angles[YAW] == cent->pe.legs.yawAngle )
802 				{
803 					cent->pe.legs.yawing = qfalse;
804 				}
805 				else
806 				{
807 					cent->pe.legs.yawing = qtrue;
808 				}
809 
810 				cent->pe.legs.yawAngle = angles[YAW];
811 				if ( ent->client )
812 				{
813 					ent->client->renderInfo.legsYaw = angles[YAW];
814 				}
815 				AnglesToAxis( angles, legs );
816 				*/
817 			}
818 
819 	//			if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST )
820 	//			{
821 	//				looking = qfalse;
822 	//			}
823 	//			else
824 			{	//look at lookTarget!
825 				//FIXME: snaps to side when lets go of lookTarget... ?
826 				float	lookingSpeed = 0.3f;
827 				looking = G_CheckLookTarget( ent, lookAngles, &lookingSpeed );
828 				lookAngles[PITCH] = lookAngles[ROLL] = 0;//droids can't pitch or roll their heads
829 				if ( looking )
830 				{//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap)
831 					ent->client->renderInfo.lookingDebounceTime = level.time + 1000;
832 				}
833 			}
834 			if ( ent->client->renderInfo.lookingDebounceTime > level.time )
835 			{	//adjust for current body orientation
836 				vec3_t	oldLookAngles;
837 
838 				lookAngles[YAW] -= 0;//ent->client->ps.viewangles[YAW];//cent->pe.torso.yawAngle;
839 				//lookAngles[YAW] -= cent->pe.legs.yawAngle;
840 
841 				//normalize
842 				lookAngles[YAW] = AngleNormalize180( lookAngles[YAW] );
843 
844 				//slowly lerp to this new value
845 				//Remember last headAngles
846 				VectorCopy( ent->client->renderInfo.lastHeadAngles, oldLookAngles );
847 				if( VectorCompare( oldLookAngles, lookAngles ) == qfalse )
848 				{
849 					//FIXME: This clamp goes off viewAngles,
850 					//but really should go off the tag_torso's axis[0] angles, no?
851 					lookAngles[YAW] = oldLookAngles[YAW]+(lookAngles[YAW]-oldLookAngles[YAW])*0.4f;
852 				}
853 				//Remember current lookAngles next time
854 				VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles );
855 			}
856 			else
857 			{//Remember current lookAngles next time
858 				VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles );
859 			}
860 			if ( ent->client->NPC_class == CLASS_ATST )
861 			{
862 				VectorCopy( ent->client->ps.viewangles, lookAngles );
863 				lookAngles[0] = lookAngles[2] = 0;
864 				lookAngles[YAW] -= trailingLegsAngles[YAW];
865 			}
866 			else
867 			{
868 				lookAngles[PITCH] = lookAngles[ROLL] = 0;
869 				lookAngles[YAW] -= ent->client->ps.viewangles[YAW];
870 			}
871 
872 			NPC_SetBoneAngles(ent, craniumBone, lookAngles);
873 			//BG_G2SetBoneAngles( cent, ent, ent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw);
874 			//return;
875 		}
876 		else//if ( (ent->client->NPC_class == CLASS_GONK ) || (ent->client->NPC_class == CLASS_INTERROGATOR) || (ent->client->NPC_class == CLASS_SENTRY) )
877 		{
878 		//	VectorCopy( ent->client->ps.viewangles, angles );
879 		//	AnglesToAxis( angles, legs );
880 			//return;
881 		}
882 	}
883 }
884 
G_G2PlayerAngles(gentity_t * ent,matrix3_t legs,vec3_t legsAngles)885 static QINLINE void G_G2PlayerAngles( gentity_t *ent, matrix3_t legs, vec3_t legsAngles)
886 {
887 	qboolean tPitching = qfalse,
888 			 tYawing = qfalse,
889 			 lYawing = qfalse;
890 	float tYawAngle = ent->client->ps.viewangles[YAW],
891 		  tPitchAngle = 0,
892 		  lYawAngle = ent->client->ps.viewangles[YAW];
893 
894 	int ciLegs = ent->client->ps.legsAnim;
895 	int ciTorso = ent->client->ps.torsoAnim;
896 
897 	vec3_t turAngles;
898 	vec3_t lerpOrg, lerpAng;
899 
900 	if (ent->s.eType == ET_NPC && ent->client)
901 	{ //sort of hacky, but it saves a pretty big load off the server
902 		int i = 0;
903 		gentity_t *clEnt;
904 
905 		//If no real clients are in the same PVS then don't do any of this stuff, no one can see him anyway!
906 		while (i < MAX_CLIENTS)
907 		{
908 			clEnt = &g_entities[i];
909 
910 			if (clEnt && clEnt->inuse && clEnt->client &&
911 				trap->InPVS(clEnt->client->ps.origin, ent->client->ps.origin))
912 			{ //this client can see him
913 				break;
914 			}
915 
916 			i++;
917 		}
918 
919 		if (i == MAX_CLIENTS)
920 		{ //no one can see him, just return
921 			return;
922 		}
923 	}
924 
925 	VectorCopy(ent->client->ps.origin, lerpOrg);
926 	VectorCopy(ent->client->ps.viewangles, lerpAng);
927 
928 	if (ent->localAnimIndex <= 1)
929 	{ //don't do these things on non-humanoids
930 		vec3_t lookAngles;
931 		entityState_t *emplaced = NULL;
932 
933 		if (ent->client->ps.hasLookTarget)
934 		{
935 			VectorSubtract(g_entities[ent->client->ps.lookTarget].r.currentOrigin, ent->client->ps.origin, lookAngles);
936 			vectoangles(lookAngles, lookAngles);
937 			ent->client->lookTime = level.time + 1000;
938 		}
939 		else
940 		{
941 			VectorCopy(ent->client->ps.origin, lookAngles);
942 		}
943 		lookAngles[PITCH] = 0;
944 
945 		if (ent->client->ps.emplacedIndex)
946 		{
947 			emplaced = &g_entities[ent->client->ps.emplacedIndex].s;
948 		}
949 
950 		BG_G2PlayerAngles(ent->ghoul2, ent->client->renderInfo.motionBolt, &ent->s, level.time, lerpOrg, lerpAng, legs,
951 			legsAngles, &tYawing, &tPitching, &lYawing, &tYawAngle, &tPitchAngle, &lYawAngle, FRAMETIME, turAngles,
952 			ent->modelScale, ciLegs, ciTorso, &ent->client->corrTime, lookAngles, ent->client->lastHeadAngles,
953 			ent->client->lookTime, emplaced, NULL);
954 
955 		if (ent->client->ps.heldByClient && ent->client->ps.heldByClient <= MAX_CLIENTS)
956 		{ //then put our arm in this client's hand
957 			//is index+1 because index 0 is valid.
958 			int heldByIndex = ent->client->ps.heldByClient-1;
959 			gentity_t *other = &g_entities[heldByIndex];
960 			int lHandBolt = 0;
961 
962 			if (other && other->inuse && other->client && other->ghoul2)
963 			{
964 				lHandBolt = trap->G2API_AddBolt(other->ghoul2, 0, "*l_hand");
965 			}
966 			else
967 			{ //they left the game, perhaps?
968 				ent->client->ps.heldByClient = 0;
969 				return;
970 			}
971 
972 			if (lHandBolt)
973 			{
974 				mdxaBone_t boltMatrix;
975 				vec3_t boltOrg;
976 				vec3_t tAngles;
977 
978 				VectorCopy(other->client->ps.viewangles, tAngles);
979 				tAngles[PITCH] = tAngles[ROLL] = 0;
980 
981 				trap->G2API_GetBoltMatrix(other->ghoul2, 0, lHandBolt, &boltMatrix, tAngles, other->client->ps.origin, level.time, 0, other->modelScale);
982 				boltOrg[0] = boltMatrix.matrix[0][3];
983 				boltOrg[1] = boltMatrix.matrix[1][3];
984 				boltOrg[2] = boltMatrix.matrix[2][3];
985 
986 				BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s, ent->client->ps.torsoAnim/*BOTH_DEAD1*/, boltOrg, &ent->client->ikStatus,
987 					ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qfalse);
988 			}
989 		}
990 		else if (ent->client->ikStatus)
991 		{ //make sure we aren't IKing if we don't have anyone to hold onto us.
992 			int lHandBolt = 0;
993 
994 			if (ent && ent->inuse && ent->client && ent->ghoul2)
995 			{
996 				lHandBolt = trap->G2API_AddBolt(ent->ghoul2, 0, "*l_hand");
997 			}
998 			else
999 			{ //This shouldn't happen, but just in case it does, we'll have a failsafe.
1000 				ent->client->ikStatus = qfalse;
1001 			}
1002 
1003 			if (lHandBolt)
1004 			{
1005 				BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s,
1006 					ent->client->ps.torsoAnim/*BOTH_DEAD1*/, vec3_origin, &ent->client->ikStatus, ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qtrue);
1007 			}
1008 		}
1009 	}
1010 	else if ( ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
1011 	{
1012 		vec3_t lookAngles;
1013 
1014 		VectorCopy(ent->client->ps.viewangles, legsAngles);
1015 		legsAngles[PITCH] = 0;
1016 		AnglesToAxis( legsAngles, legs );
1017 
1018 		VectorCopy(ent->client->ps.viewangles, lookAngles);
1019 		lookAngles[YAW] = lookAngles[ROLL] = 0;
1020 
1021 		BG_G2ATSTAngles( ent->ghoul2, level.time, lookAngles );
1022 	}
1023 	else if (ent->NPC)
1024 	{ //an NPC not using a humanoid skeleton, do special angle stuff.
1025 		if (ent->s.eType == ET_NPC &&
1026 			ent->s.NPC_class == CLASS_VEHICLE &&
1027 			ent->m_pVehicle &&
1028 			ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
1029 		{ //fighters actually want to take pitch and roll into account for the axial angles
1030 			VectorCopy(ent->client->ps.viewangles, legsAngles);
1031 			AnglesToAxis( legsAngles, legs );
1032 		}
1033 		else
1034 		{
1035 			G_G2NPCAngles(ent, legs, legsAngles);
1036 		}
1037 	}
1038 }
1039 
SaberAttacking(gentity_t * self)1040 static QINLINE qboolean SaberAttacking( gentity_t *self )
1041 {
1042 	if ( PM_SaberInParry(self->client->ps.saberMove) )
1043 		return qfalse;
1044 	if ( PM_SaberInBrokenParry(self->client->ps.saberMove) )
1045 		return qfalse;
1046 	if ( PM_SaberInDeflect(self->client->ps.saberMove) )
1047 		return qfalse;
1048 	if ( PM_SaberInBounce(self->client->ps.saberMove) )
1049 		return qfalse;
1050 	if ( PM_SaberInKnockaway(self->client->ps.saberMove) )
1051 		return qfalse;
1052 
1053 	//if we're firing and not blocking, then we're attacking.
1054 	if (BG_SaberInAttack(self->client->ps.saberMove))
1055 		if (self->client->ps.weaponstate == WEAPON_FIRING && self->client->ps.saberBlocked == BLOCKED_NONE)
1056 			return qtrue;
1057 
1058 	if ( BG_SaberInSpecial( self->client->ps.saberMove ) )
1059 		return qtrue;
1060 
1061 	return qfalse;
1062 }
1063 
1064 typedef enum
1065 {
1066 	LOCK_FIRST = 0,
1067 	LOCK_TOP = LOCK_FIRST,
1068 	LOCK_DIAG_TR,
1069 	LOCK_DIAG_TL,
1070 	LOCK_DIAG_BR,
1071 	LOCK_DIAG_BL,
1072 	LOCK_R,
1073 	LOCK_L,
1074 	LOCK_RANDOM
1075 } sabersLockMode_t;
1076 
1077 #define LOCK_IDEAL_DIST_TOP 32.0f
1078 #define LOCK_IDEAL_DIST_CIRCLE 48.0f
1079 
1080 #define SABER_HITDAMAGE 35
1081 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
1082 
G_SaberLockAnim(int attackerSaberStyle,int defenderSaberStyle,int topOrSide,int lockOrBreakOrSuperBreak,int winOrLose)1083 int G_SaberLockAnim( int attackerSaberStyle, int defenderSaberStyle, int topOrSide, int lockOrBreakOrSuperBreak, int winOrLose )
1084 {
1085 	int baseAnim = -1;
1086 	if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
1087 	{//special case: if we're using the same style and locking
1088 		if ( attackerSaberStyle == defenderSaberStyle
1089 			|| (attackerSaberStyle>=SS_FAST&&attackerSaberStyle<=SS_TAVION&&defenderSaberStyle>=SS_FAST&&defenderSaberStyle<=SS_TAVION) )
1090 		{//using same style
1091 			if ( winOrLose == SABERLOCK_LOSE )
1092 			{//you want the defender's stance...
1093 				switch ( defenderSaberStyle )
1094 				{
1095 				case SS_DUAL:
1096 					if ( topOrSide == SABERLOCK_TOP )
1097 					{
1098 						baseAnim = BOTH_LK_DL_DL_T_L_2;
1099 					}
1100 					else
1101 					{
1102 						baseAnim = BOTH_LK_DL_DL_S_L_2;
1103 					}
1104 					break;
1105 				case SS_STAFF:
1106 					if ( topOrSide == SABERLOCK_TOP )
1107 					{
1108 						baseAnim = BOTH_LK_ST_ST_T_L_2;
1109 					}
1110 					else
1111 					{
1112 						baseAnim = BOTH_LK_ST_ST_S_L_2;
1113 					}
1114 					break;
1115 				default:
1116 					if ( topOrSide == SABERLOCK_TOP )
1117 					{
1118 						baseAnim = BOTH_LK_S_S_T_L_2;
1119 					}
1120 					else
1121 					{
1122 						baseAnim = BOTH_LK_S_S_S_L_2;
1123 					}
1124 					break;
1125 				}
1126 			}
1127 		}
1128 	}
1129 	if ( baseAnim == -1 )
1130 	{
1131 		switch ( attackerSaberStyle )
1132 		{
1133 		case SS_DUAL:
1134 			switch ( defenderSaberStyle )
1135 			{
1136 				case SS_DUAL:
1137 					baseAnim = BOTH_LK_DL_DL_S_B_1_L;
1138 					break;
1139 				case SS_STAFF:
1140 					baseAnim = BOTH_LK_DL_ST_S_B_1_L;
1141 					break;
1142 				default://single
1143 					baseAnim = BOTH_LK_DL_S_S_B_1_L;
1144 					break;
1145 			}
1146 			break;
1147 		case SS_STAFF:
1148 			switch ( defenderSaberStyle )
1149 			{
1150 				case SS_DUAL:
1151 					baseAnim = BOTH_LK_ST_DL_S_B_1_L;
1152 					break;
1153 				case SS_STAFF:
1154 					baseAnim = BOTH_LK_ST_ST_S_B_1_L;
1155 					break;
1156 				default://single
1157 					baseAnim = BOTH_LK_ST_S_S_B_1_L;
1158 					break;
1159 			}
1160 			break;
1161 		default://single
1162 			switch ( defenderSaberStyle )
1163 			{
1164 				case SS_DUAL:
1165 					baseAnim = BOTH_LK_S_DL_S_B_1_L;
1166 					break;
1167 				case SS_STAFF:
1168 					baseAnim = BOTH_LK_S_ST_S_B_1_L;
1169 					break;
1170 				default://single
1171 					baseAnim = BOTH_LK_S_S_S_B_1_L;
1172 					break;
1173 			}
1174 			break;
1175 		}
1176 		//side lock or top lock?
1177 		if ( topOrSide == SABERLOCK_TOP )
1178 		{
1179 			baseAnim += 5;
1180 		}
1181 		//lock, break or superbreak?
1182 		if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
1183 		{
1184 			baseAnim += 2;
1185 		}
1186 		else
1187 		{//a break or superbreak
1188 			if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK )
1189 			{
1190 				baseAnim += 3;
1191 			}
1192 			//winner or loser?
1193 			if ( winOrLose == SABERLOCK_WIN )
1194 			{
1195 				baseAnim += 1;
1196 			}
1197 		}
1198 	}
1199 	return baseAnim;
1200 }
1201 
1202 extern qboolean BG_CheckIncrementLockAnim( int anim, int winOrLose ); //bg_saber.c
1203 #define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
1204 
WP_SabersCheckLock2(gentity_t * attacker,gentity_t * defender,sabersLockMode_t lockMode)1205 static QINLINE qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
1206 {
1207 	int		attAnim, defAnim = 0;
1208 	float	attStart = 0.5f, defStart = 0.5f;
1209 	float	idealDist = 48.0f;
1210 	vec3_t	attAngles, defAngles, defDir;
1211 	vec3_t	newOrg;
1212 	vec3_t	attDir;
1213 	float	diff = 0;
1214 	trace_t trace;
1215 
1216 	//MATCH ANIMS
1217 	if ( lockMode == LOCK_RANDOM )
1218 	{
1219 		lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
1220 	}
1221 	if ( attacker->client->ps.fd.saberAnimLevel >= SS_FAST
1222 		&& attacker->client->ps.fd.saberAnimLevel <= SS_TAVION
1223 		&& defender->client->ps.fd.saberAnimLevel >= SS_FAST
1224 		&& defender->client->ps.fd.saberAnimLevel <= SS_TAVION )
1225 	{//2 single sabers?  Just do it the old way...
1226 		switch ( lockMode )
1227 		{
1228 		case LOCK_TOP:
1229 			attAnim = BOTH_BF2LOCK;
1230 			defAnim = BOTH_BF1LOCK;
1231 			attStart = defStart = 0.5f;
1232 			idealDist = LOCK_IDEAL_DIST_TOP;
1233 			break;
1234 		case LOCK_DIAG_TR:
1235 			attAnim = BOTH_CCWCIRCLELOCK;
1236 			defAnim = BOTH_CWCIRCLELOCK;
1237 			attStart = defStart = 0.5f;
1238 			idealDist = LOCK_IDEAL_DIST_CIRCLE;
1239 			break;
1240 		case LOCK_DIAG_TL:
1241 			attAnim = BOTH_CWCIRCLELOCK;
1242 			defAnim = BOTH_CCWCIRCLELOCK;
1243 			attStart = defStart = 0.5f;
1244 			idealDist = LOCK_IDEAL_DIST_CIRCLE;
1245 			break;
1246 		case LOCK_DIAG_BR:
1247 			attAnim = BOTH_CWCIRCLELOCK;
1248 			defAnim = BOTH_CCWCIRCLELOCK;
1249 			attStart = defStart = 0.85f;
1250 			idealDist = LOCK_IDEAL_DIST_CIRCLE;
1251 			break;
1252 		case LOCK_DIAG_BL:
1253 			attAnim = BOTH_CCWCIRCLELOCK;
1254 			defAnim = BOTH_CWCIRCLELOCK;
1255 			attStart = defStart = 0.85f;
1256 			idealDist = LOCK_IDEAL_DIST_CIRCLE;
1257 			break;
1258 		case LOCK_R:
1259 			attAnim = BOTH_CCWCIRCLELOCK;
1260 			defAnim = BOTH_CWCIRCLELOCK;
1261 			attStart = defStart = 0.75f;
1262 			idealDist = LOCK_IDEAL_DIST_CIRCLE;
1263 			break;
1264 		case LOCK_L:
1265 			attAnim = BOTH_CWCIRCLELOCK;
1266 			defAnim = BOTH_CCWCIRCLELOCK;
1267 			attStart = defStart = 0.75f;
1268 			idealDist = LOCK_IDEAL_DIST_CIRCLE;
1269 			break;
1270 		default:
1271 			return qfalse;
1272 			break;
1273 		}
1274 	}
1275 	else
1276 	{//use the new system
1277 		idealDist = LOCK_IDEAL_DIST_JKA;//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
1278 		if ( lockMode == LOCK_TOP )
1279 		{//top lock
1280 			attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN );
1281 			defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_LOSE );
1282 			attStart = defStart = 0.5f;
1283 		}
1284 		else
1285 		{//side lock
1286 			switch ( lockMode )
1287 			{
1288 			case LOCK_DIAG_TR:
1289 				attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
1290 				defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
1291 				attStart = defStart = 0.5f;
1292 				break;
1293 			case LOCK_DIAG_TL:
1294 				attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
1295 				defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
1296 				attStart = defStart = 0.5f;
1297 				break;
1298 			case LOCK_DIAG_BR:
1299 				attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
1300 				defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
1301 				if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
1302 				{
1303 					attStart = 0.85f;//move to end of anim
1304 				}
1305 				else
1306 				{
1307 					attStart = 0.15f;//start at beginning of anim
1308 				}
1309 				if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
1310 				{
1311 					defStart = 0.85f;//start at end of anim
1312 				}
1313 				else
1314 				{
1315 					defStart = 0.15f;//start at beginning of anim
1316 				}
1317 				break;
1318 			case LOCK_DIAG_BL:
1319 				attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
1320 				defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
1321 				if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
1322 				{
1323 					attStart = 0.85f;//move to end of anim
1324 				}
1325 				else
1326 				{
1327 					attStart = 0.15f;//start at beginning of anim
1328 				}
1329 				if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
1330 				{
1331 					defStart = 0.85f;//start at end of anim
1332 				}
1333 				else
1334 				{
1335 					defStart = 0.15f;//start at beginning of anim
1336 				}
1337 				break;
1338 			case LOCK_R:
1339 				attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
1340 				defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
1341 				if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
1342 				{
1343 					attStart = 0.75f;//move to end of anim
1344 				}
1345 				else
1346 				{
1347 					attStart = 0.25f;//start at beginning of anim
1348 				}
1349 				if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
1350 				{
1351 					defStart = 0.75f;//start at end of anim
1352 				}
1353 				else
1354 				{
1355 					defStart = 0.25f;//start at beginning of anim
1356 				}
1357 				break;
1358 			case LOCK_L:
1359 				attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
1360 				defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
1361 				//attacker starts with advantage
1362 				if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
1363 				{
1364 					attStart = 0.75f;//move to end of anim
1365 				}
1366 				else
1367 				{
1368 					attStart = 0.25f;//start at beginning of anim
1369 				}
1370 				if ( BG_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
1371 				{
1372 					defStart = 0.75f;//start at end of anim
1373 				}
1374 				else
1375 				{
1376 					defStart = 0.25f;//start at beginning of anim
1377 				}
1378 				break;
1379 			default:
1380 				return qfalse;
1381 				break;
1382 			}
1383 		}
1384 	}
1385 
1386 	G_SetAnim(attacker, NULL, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
1387 	attacker->client->ps.saberLockFrame = bgAllAnims[attacker->localAnimIndex].anims[attAnim].firstFrame+(bgAllAnims[attacker->localAnimIndex].anims[attAnim].numFrames*attStart);
1388 
1389 	G_SetAnim(defender, NULL, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
1390 	defender->client->ps.saberLockFrame = bgAllAnims[defender->localAnimIndex].anims[defAnim].firstFrame+(bgAllAnims[defender->localAnimIndex].anims[defAnim].numFrames*defStart);
1391 
1392 	attacker->client->ps.saberLockHits = 0;
1393 	defender->client->ps.saberLockHits = 0;
1394 
1395 	attacker->client->ps.saberLockAdvance = qfalse;
1396 	defender->client->ps.saberLockAdvance = qfalse;
1397 
1398 	VectorClear( attacker->client->ps.velocity );
1399 	VectorClear( defender->client->ps.velocity );
1400 	attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + 10000;
1401 	attacker->client->ps.saberLockEnemy = defender->s.number;
1402 	defender->client->ps.saberLockEnemy = attacker->s.number;
1403 	attacker->client->ps.weaponTime = defender->client->ps.weaponTime = Q_irand( 1000, 3000 );//delay 1 to 3 seconds before pushing
1404 
1405 	VectorSubtract( defender->r.currentOrigin, attacker->r.currentOrigin, defDir );
1406 	VectorCopy( attacker->client->ps.viewangles, attAngles );
1407 	attAngles[YAW] = vectoyaw( defDir );
1408 	SetClientViewAngle( attacker, attAngles );
1409 	defAngles[PITCH] = attAngles[PITCH]*-1;
1410 	defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
1411 	defAngles[ROLL] = 0;
1412 	SetClientViewAngle( defender, defAngles );
1413 
1414 	//MATCH POSITIONS
1415 	diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
1416 	//try to move attacker half the diff towards the defender
1417 	VectorMA( attacker->r.currentOrigin, diff*0.5f, defDir, newOrg );
1418 
1419 	trap->Trace( &trace, attacker->r.currentOrigin, attacker->r.mins, attacker->r.maxs, newOrg, attacker->s.number, attacker->clipmask, qfalse, 0, 0 );
1420 	if ( !trace.startsolid && !trace.allsolid )
1421 	{
1422 		G_SetOrigin( attacker, trace.endpos );
1423 		if (attacker->client)
1424 		{
1425 			VectorCopy(trace.endpos, attacker->client->ps.origin);
1426 		}
1427 		trap->LinkEntity( (sharedEntity_t *)attacker );
1428 	}
1429 	//now get the defender's dist and do it for him too
1430 	VectorSubtract( attacker->r.currentOrigin, defender->r.currentOrigin, attDir );
1431 	diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
1432 	//try to move defender all of the remaining diff towards the attacker
1433 	VectorMA( defender->r.currentOrigin, diff, attDir, newOrg );
1434 	trap->Trace( &trace, defender->r.currentOrigin, defender->r.mins, defender->r.maxs, newOrg, defender->s.number, defender->clipmask, qfalse, 0, 0 );
1435 	if ( !trace.startsolid && !trace.allsolid )
1436 	{
1437 		if (defender->client)
1438 		{
1439 			VectorCopy(trace.endpos, defender->client->ps.origin);
1440 		}
1441 		G_SetOrigin( defender, trace.endpos );
1442 		trap->LinkEntity( (sharedEntity_t *)defender );
1443 	}
1444 
1445 	//DONE!
1446 	return qtrue;
1447 }
1448 
WP_SabersCheckLock(gentity_t * ent1,gentity_t * ent2)1449 qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
1450 {
1451 	float dist;
1452 	qboolean	ent1BlockingPlayer = qfalse;
1453 	qboolean	ent2BlockingPlayer = qfalse;
1454 
1455 	if ( g_debugSaberLocks.integer )
1456 	{
1457 		WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
1458 		return qtrue;
1459 	}
1460 	//for now.. it's not fair to the lone duelist.
1461 	//we need dual saber lock animations.
1462 	if (level.gametype == GT_POWERDUEL)
1463 	{
1464 		return qfalse;
1465 	}
1466 
1467 	if (!g_saberLocking.integer)
1468 	{
1469 		return qfalse;
1470 	}
1471 
1472 	if (!ent1->client || !ent2->client)
1473 	{
1474 		return qfalse;
1475 	}
1476 
1477 	if (ent1->s.eType == ET_NPC ||
1478 		ent2->s.eType == ET_NPC)
1479 	{ //if either ents is NPC, then never let an NPC lock with someone on the same playerTeam
1480 		if (ent1->client->playerTeam == ent2->client->playerTeam)
1481 		{
1482 			return qfalse;
1483 		}
1484 	}
1485 
1486 	if (!ent1->client->ps.saberEntityNum ||
1487 		!ent2->client->ps.saberEntityNum ||
1488 		ent1->client->ps.saberInFlight ||
1489 		ent2->client->ps.saberInFlight)
1490 	{ //can't get in lock if one of them has had the saber knocked out of his hand
1491 		return qfalse;
1492 	}
1493 
1494 	if (ent1->s.eType != ET_NPC && ent2->s.eType != ET_NPC)
1495 	{ //can always get into locks with NPCs
1496 		if (!ent1->client->ps.duelInProgress ||
1497 			!ent2->client->ps.duelInProgress ||
1498 			ent1->client->ps.duelIndex != ent2->s.number ||
1499 			ent2->client->ps.duelIndex != ent1->s.number)
1500 		{ //only allow saber locking if two players are dueling with each other directly
1501 			if (level.gametype != GT_DUEL && level.gametype != GT_POWERDUEL)
1502 			{
1503 				return qfalse;
1504 			}
1505 		}
1506 	}
1507 
1508 	if ( fabs( ent1->r.currentOrigin[2]-ent2->r.currentOrigin[2] ) > 16 )
1509 	{
1510 		return qfalse;
1511 	}
1512 	if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
1513 		ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
1514 	{
1515 		return qfalse;
1516 	}
1517 	dist = DistanceSquared(ent1->r.currentOrigin,ent2->r.currentOrigin);
1518 	if ( dist < 64 || dist > 6400 )
1519 	{//between 8 and 80 from each other
1520 		return qfalse;
1521 	}
1522 
1523 	if (BG_InSpecialJump(ent1->client->ps.legsAnim))
1524 	{
1525 		return qfalse;
1526 	}
1527 	if (BG_InSpecialJump(ent2->client->ps.legsAnim))
1528 	{
1529 		return qfalse;
1530 	}
1531 
1532 	if (BG_InRoll(&ent1->client->ps, ent1->client->ps.legsAnim))
1533 	{
1534 		return qfalse;
1535 	}
1536 	if (BG_InRoll(&ent2->client->ps, ent2->client->ps.legsAnim))
1537 	{
1538 		return qfalse;
1539 	}
1540 
1541 	if (ent1->client->ps.forceHandExtend != HANDEXTEND_NONE ||
1542 		ent2->client->ps.forceHandExtend != HANDEXTEND_NONE)
1543 	{
1544 		return qfalse;
1545 	}
1546 
1547 	if ((ent1->client->ps.pm_flags & PMF_DUCKED) ||
1548 		(ent2->client->ps.pm_flags & PMF_DUCKED))
1549 	{
1550 		return qfalse;
1551 	}
1552 
1553 	if ( (ent1->client->saber[0].saberFlags&SFL_NOT_LOCKABLE)
1554 		|| (ent2->client->saber[0].saberFlags&SFL_NOT_LOCKABLE) )
1555 	{
1556 		return qfalse;
1557 	}
1558 	if ( ent1->client->saber[1].model[0]
1559 		&& !ent1->client->ps.saberHolstered
1560 		&& (ent1->client->saber[1].saberFlags&SFL_NOT_LOCKABLE) )
1561 	{
1562 		return qfalse;
1563 	}
1564 	if ( ent2->client->saber[1].model[0]
1565 		&& !ent2->client->ps.saberHolstered
1566 		&& (ent2->client->saber[1].saberFlags&SFL_NOT_LOCKABLE) )
1567 	{
1568 		return qfalse;
1569 	}
1570 
1571 	if (!InFront( ent1->client->ps.origin, ent2->client->ps.origin, ent2->client->ps.viewangles, 0.4f ))
1572 	{
1573 		return qfalse;
1574 	}
1575 	if (!InFront( ent2->client->ps.origin, ent1->client->ps.origin, ent1->client->ps.viewangles, 0.4f ))
1576 	{
1577 		return qfalse;
1578 	}
1579 
1580 	//T to B lock
1581 	if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
1582 		ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
1583 		ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
1584 		ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
1585 		ent1->client->ps.torsoAnim == BOTH_A5_T__B_ ||
1586 		ent1->client->ps.torsoAnim == BOTH_A6_T__B_ ||
1587 		ent1->client->ps.torsoAnim == BOTH_A7_T__B_)
1588 	{//ent1 is attacking top-down
1589 		return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
1590 	}
1591 
1592 	if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
1593 		ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
1594 		ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
1595 		ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
1596 		ent2->client->ps.torsoAnim == BOTH_A5_T__B_ ||
1597 		ent2->client->ps.torsoAnim == BOTH_A6_T__B_ ||
1598 		ent2->client->ps.torsoAnim == BOTH_A7_T__B_)
1599 	{//ent2 is attacking top-down
1600 		return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
1601 	}
1602 
1603 	if ( ent1->s.number >= 0 && ent1->s.number < MAX_CLIENTS &&
1604 		ent1->client->ps.saberBlocking == BLK_WIDE && ent1->client->ps.weaponTime <= 0 )
1605 	{
1606 		ent1BlockingPlayer = qtrue;
1607 	}
1608 	if ( ent2->s.number >= 0 && ent2->s.number < MAX_CLIENTS &&
1609 		ent2->client->ps.saberBlocking == BLK_WIDE && ent2->client->ps.weaponTime <= 0 )
1610 	{
1611 		ent2BlockingPlayer = qtrue;
1612 	}
1613 
1614 	//TR to BL lock
1615 	if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
1616 		ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
1617 		ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
1618 		ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
1619 		ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
1620 		ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
1621 		ent1->client->ps.torsoAnim == BOTH_A7_TR_BL)
1622 	{//ent1 is attacking diagonally
1623 		if ( ent2BlockingPlayer )
1624 		{//player will block this anyway
1625 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
1626 		}
1627 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
1628 			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
1629 			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
1630 			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
1631 			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
1632 			ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
1633 			ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
1634 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
1635 		{//ent2 is attacking in the opposite diagonal
1636 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
1637 		}
1638 		if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
1639 			ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
1640 			ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
1641 			ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
1642 			ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
1643 			ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
1644 			ent2->client->ps.torsoAnim == BOTH_A7_BR_TL ||
1645 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
1646 		{//ent2 is attacking in the opposite diagonal
1647 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
1648 		}
1649 		return qfalse;
1650 	}
1651 
1652 	if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
1653 		ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
1654 		ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
1655 		ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
1656 		ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
1657 		ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
1658 		ent2->client->ps.torsoAnim == BOTH_A7_TR_BL)
1659 	{//ent2 is attacking diagonally
1660 		if ( ent1BlockingPlayer )
1661 		{//player will block this anyway
1662 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
1663 		}
1664 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
1665 			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
1666 			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
1667 			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
1668 			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
1669 			ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
1670 			ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
1671 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
1672 		{//ent1 is attacking in the opposite diagonal
1673 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
1674 		}
1675 		if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
1676 			ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
1677 			ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
1678 			ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
1679 			ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
1680 			ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
1681 			ent1->client->ps.torsoAnim == BOTH_A7_BR_TL ||
1682 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
1683 		{//ent1 is attacking in the opposite diagonal
1684 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
1685 		}
1686 		return qfalse;
1687 	}
1688 
1689 	//TL to BR lock
1690 	if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
1691 		ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
1692 		ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
1693 		ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
1694 		ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
1695 		ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
1696 		ent1->client->ps.torsoAnim == BOTH_A7_TL_BR)
1697 	{//ent1 is attacking diagonally
1698 		if ( ent2BlockingPlayer )
1699 		{//player will block this anyway
1700 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
1701 		}
1702 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
1703 			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
1704 			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
1705 			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
1706 			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
1707 			ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
1708 			ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
1709 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
1710 		{//ent2 is attacking in the opposite diagonal
1711 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
1712 		}
1713 		if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
1714 			ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
1715 			ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
1716 			ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
1717 			ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
1718 			ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
1719 			ent2->client->ps.torsoAnim == BOTH_A7_BL_TR ||
1720 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
1721 		{//ent2 is attacking in the opposite diagonal
1722 			return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
1723 		}
1724 		return qfalse;
1725 	}
1726 
1727 	if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
1728 		ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
1729 		ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
1730 		ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
1731 		ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
1732 		ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
1733 		ent2->client->ps.torsoAnim == BOTH_A7_TL_BR)
1734 	{//ent2 is attacking diagonally
1735 		if ( ent1BlockingPlayer )
1736 		{//player will block this anyway
1737 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
1738 		}
1739 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
1740 			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
1741 			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
1742 			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
1743 			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
1744 			ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
1745 			ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
1746 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
1747 		{//ent1 is attacking in the opposite diagonal
1748 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
1749 		}
1750 		if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
1751 			ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
1752 			ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
1753 			ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
1754 			ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
1755 			ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
1756 			ent1->client->ps.torsoAnim == BOTH_A7_BL_TR ||
1757 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
1758 		{//ent1 is attacking in the opposite diagonal
1759 			return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
1760 		}
1761 		return qfalse;
1762 	}
1763 	//L to R lock
1764 	if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
1765 		ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
1766 		ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
1767 		ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
1768 		ent1->client->ps.torsoAnim == BOTH_A5__L__R ||
1769 		ent1->client->ps.torsoAnim == BOTH_A6__L__R ||
1770 		ent1->client->ps.torsoAnim == BOTH_A7__L__R)
1771 	{//ent1 is attacking l to r
1772 		if ( ent2BlockingPlayer )
1773 		{//player will block this anyway
1774 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
1775 		}
1776 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
1777 			ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
1778 			ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
1779 			ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
1780 			ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
1781 			ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
1782 			ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
1783 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
1784 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
1785 		{//ent2 is attacking or blocking on the r
1786 			return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
1787 		}
1788 		return qfalse;
1789 	}
1790 	if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
1791 		ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
1792 		ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
1793 		ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
1794 		ent2->client->ps.torsoAnim == BOTH_A5__L__R ||
1795 		ent2->client->ps.torsoAnim == BOTH_A6__L__R ||
1796 		ent2->client->ps.torsoAnim == BOTH_A7__L__R)
1797 	{//ent2 is attacking l to r
1798 		if ( ent1BlockingPlayer )
1799 		{//player will block this anyway
1800 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
1801 		}
1802 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
1803 			ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
1804 			ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
1805 			ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
1806 			ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
1807 			ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
1808 			ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
1809 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
1810 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
1811 		{//ent1 is attacking or blocking on the r
1812 			return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
1813 		}
1814 		return qfalse;
1815 	}
1816 	//R to L lock
1817 	if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
1818 		ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
1819 		ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
1820 		ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
1821 		ent1->client->ps.torsoAnim == BOTH_A5__R__L ||
1822 		ent1->client->ps.torsoAnim == BOTH_A6__R__L ||
1823 		ent1->client->ps.torsoAnim == BOTH_A7__R__L)
1824 	{//ent1 is attacking r to l
1825 		if ( ent2BlockingPlayer )
1826 		{//player will block this anyway
1827 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
1828 		}
1829 		if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
1830 			ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
1831 			ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
1832 			ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
1833 			ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
1834 			ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
1835 			ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
1836 			ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
1837 			ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
1838 		{//ent2 is attacking or blocking on the l
1839 			return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
1840 		}
1841 		return qfalse;
1842 	}
1843 	if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
1844 		ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
1845 		ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
1846 		ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
1847 		ent2->client->ps.torsoAnim == BOTH_A5__R__L ||
1848 		ent2->client->ps.torsoAnim == BOTH_A6__R__L ||
1849 		ent2->client->ps.torsoAnim == BOTH_A7__R__L)
1850 	{//ent2 is attacking r to l
1851 		if ( ent1BlockingPlayer )
1852 		{//player will block this anyway
1853 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
1854 		}
1855 		if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
1856 			ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
1857 			ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
1858 			ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
1859 			ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
1860 			ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
1861 			ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
1862 			ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
1863 			ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
1864 		{//ent1 is attacking or blocking on the l
1865 			return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
1866 		}
1867 		return qfalse;
1868 	}
1869 	if ( !Q_irand( 0, 10 ) )
1870 	{
1871 		return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
1872 	}
1873 	return qfalse;
1874 }
1875 
G_GetParryForBlock(int block)1876 static QINLINE int G_GetParryForBlock(int block)
1877 {
1878 	switch (block)
1879 	{
1880 		case BLOCKED_UPPER_RIGHT:
1881 			return LS_PARRY_UR;
1882 			break;
1883 		case BLOCKED_UPPER_RIGHT_PROJ:
1884 			return LS_REFLECT_UR;
1885 			break;
1886 		case BLOCKED_UPPER_LEFT:
1887 			return LS_PARRY_UL;
1888 			break;
1889 		case BLOCKED_UPPER_LEFT_PROJ:
1890 			return LS_REFLECT_UL;
1891 			break;
1892 		case BLOCKED_LOWER_RIGHT:
1893 			return LS_PARRY_LR;
1894 			break;
1895 		case BLOCKED_LOWER_RIGHT_PROJ:
1896 			return LS_REFLECT_LR;
1897 			break;
1898 		case BLOCKED_LOWER_LEFT:
1899 			return LS_PARRY_LL;
1900 			break;
1901 		case BLOCKED_LOWER_LEFT_PROJ:
1902 			return LS_REFLECT_LL;
1903 			break;
1904 		case BLOCKED_TOP:
1905 			return LS_PARRY_UP;
1906 			break;
1907 		case BLOCKED_TOP_PROJ:
1908 			return LS_REFLECT_UP;
1909 			break;
1910 		default:
1911 			break;
1912 	}
1913 
1914 	return LS_NONE;
1915 }
1916 
1917 int PM_SaberBounceForAttack( int move );
1918 int PM_SaberDeflectionForQuad( int quad );
1919 
1920 extern stringID_table_t animTable[MAX_ANIMATIONS+1];
WP_GetSaberDeflectionAngle(gentity_t * attacker,gentity_t * defender,float saberHitFraction)1921 static QINLINE qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender, float saberHitFraction )
1922 {
1923 	qboolean animBasedDeflection = qtrue;
1924 	int attSaberLevel, defSaberLevel;
1925 
1926 	if ( !attacker || !attacker->client || !attacker->ghoul2 )
1927 	{
1928 		return qfalse;
1929 	}
1930 	if ( !defender || !defender->client || !defender->ghoul2 )
1931 	{
1932 		return qfalse;
1933 	}
1934 
1935 	if ((level.time - attacker->client->lastSaberStorageTime) > 500)
1936 	{ //last update was too long ago, something is happening to this client to prevent his saber from updating
1937 		return qfalse;
1938 	}
1939 	if ((level.time - defender->client->lastSaberStorageTime) > 500)
1940 	{ //ditto
1941 		return qfalse;
1942 	}
1943 
1944 	attSaberLevel = G_SaberAttackPower(attacker, SaberAttacking(attacker));
1945 	defSaberLevel = G_SaberAttackPower(defender, SaberAttacking(defender));
1946 
1947 	if ( animBasedDeflection )
1948 	{
1949 		//Hmm, let's try just basing it off the anim
1950 		int attQuadStart = saberMoveData[attacker->client->ps.saberMove].startQuad;
1951 		int attQuadEnd = saberMoveData[attacker->client->ps.saberMove].endQuad;
1952 		int defQuad = saberMoveData[defender->client->ps.saberMove].endQuad;
1953 		int quadDiff = fabs((float)(defQuad-attQuadStart));
1954 
1955 		if ( defender->client->ps.saberMove == LS_READY )
1956 		{
1957 			//FIXME: we should probably do SOMETHING here...
1958 			//I have this return qfalse here in the hopes that
1959 			//the defender will pick a parry and the attacker
1960 			//will hit the defender's saber again.
1961 			//But maybe this func call should come *after*
1962 			//it's decided whether or not the defender is
1963 			//going to parry.
1964 			return qfalse;
1965 		}
1966 
1967 		//reverse the left/right of the defQuad because of the mirrored nature of facing each other in combat
1968 		switch ( defQuad )
1969 		{
1970 		case Q_BR:
1971 			defQuad = Q_BL;
1972 			break;
1973 		case Q_R:
1974 			defQuad = Q_L;
1975 			break;
1976 		case Q_TR:
1977 			defQuad = Q_TL;
1978 			break;
1979 		case Q_TL:
1980 			defQuad = Q_TR;
1981 			break;
1982 		case Q_L:
1983 			defQuad = Q_R;
1984 			break;
1985 		case Q_BL:
1986 			defQuad = Q_BR;
1987 			break;
1988 		}
1989 
1990 		if ( quadDiff > 4 )
1991 		{//wrap around so diff is never greater than 180 (4 * 45)
1992 			quadDiff = 4 - (quadDiff - 4);
1993 		}
1994 		//have the quads, find a good anim to use
1995 		if ( (!quadDiff || (quadDiff == 1 && Q_irand(0,1))) //defender pretty much stopped the attack at a 90 degree angle
1996 			&& (defSaberLevel == attSaberLevel || Q_irand( 0, defSaberLevel-attSaberLevel ) >= 0) )//and the defender's style is stronger
1997 		{
1998 			//bounce straight back
1999 #ifndef FINAL_BUILD
2000 			int attMove = attacker->client->ps.saberMove;
2001 #endif
2002 			attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
2003 #ifndef FINAL_BUILD
2004 			if (g_saberDebugPrint.integer)
2005 			{
2006 				Com_Printf( "attack %s vs. parry %s bounced to %s\n",
2007 					animTable[saberMoveData[attMove].animToUse].name,
2008 					animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
2009 					animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
2010 			}
2011 #endif
2012 			attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
2013 			return qfalse;
2014 		}
2015 		else
2016 		{//attack hit at an angle, figure out what angle it should bounce off att
2017 			int newQuad;
2018 			quadDiff = defQuad - attQuadEnd;
2019 			//add half the diff of between the defense and attack end to the attack end
2020 			if ( quadDiff > 4 )
2021 			{
2022 				quadDiff = 4 - (quadDiff - 4);
2023 			}
2024 			else if ( quadDiff < -4 )
2025 			{
2026 				quadDiff = -4 + (quadDiff + 4);
2027 			}
2028 			newQuad = attQuadEnd + ceil( ((float)quadDiff)/2.0f );
2029 			if ( newQuad < Q_BR )
2030 			{//less than zero wraps around
2031 				newQuad = Q_B + newQuad;
2032 			}
2033 			if ( newQuad == attQuadStart )
2034 			{//never come off at the same angle that we would have if the attack was not interrupted
2035 				if ( Q_irand(0, 1) )
2036 				{
2037 					newQuad--;
2038 				}
2039 				else
2040 				{
2041 					newQuad++;
2042 				}
2043 				if ( newQuad < Q_BR )
2044 				{
2045 					newQuad = Q_B;
2046 				}
2047 				else if ( newQuad > Q_B )
2048 				{
2049 					newQuad = Q_BR;
2050 				}
2051 			}
2052 			if ( newQuad == defQuad )
2053 			{//bounce straight back
2054 #ifndef FINAL_BUILD
2055 				int attMove = attacker->client->ps.saberMove;
2056 #endif
2057 				attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
2058 #ifndef FINAL_BUILD
2059 				if (g_saberDebugPrint.integer)
2060 				{
2061 					Com_Printf( "attack %s vs. parry %s bounced to %s\n",
2062 						animTable[saberMoveData[attMove].animToUse].name,
2063 						animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
2064 						animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
2065 				}
2066 #endif
2067 				attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
2068 				return qfalse;
2069 			}
2070 			//else, pick a deflection
2071 			else
2072 			{
2073 #ifndef FINAL_BUILD
2074 				int attMove = attacker->client->ps.saberMove;
2075 #endif
2076 				attacker->client->ps.saberMove = PM_SaberDeflectionForQuad( newQuad );
2077 #ifndef FINAL_BUILD
2078 				if (g_saberDebugPrint.integer)
2079 				{
2080 					Com_Printf( "attack %s vs. parry %s deflected to %s\n",
2081 						animTable[saberMoveData[attMove].animToUse].name,
2082 						animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name,
2083 						animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name );
2084 				}
2085 #endif
2086 				attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
2087 				return qtrue;
2088 			}
2089 		}
2090 	}
2091 	else
2092 	{ //old math-based method (probably broken)
2093 		vec3_t	att_HitDir, def_BladeDir, temp;
2094 		float	hitDot;
2095 
2096 		VectorCopy(attacker->client->lastSaberBase_Always, temp);
2097 
2098 		AngleVectors(attacker->client->lastSaberDir_Always, att_HitDir, 0, 0);
2099 
2100 		AngleVectors(defender->client->lastSaberDir_Always, def_BladeDir, 0, 0);
2101 
2102 		//now compare
2103 		hitDot = DotProduct( att_HitDir, def_BladeDir );
2104 		if ( hitDot < 0.25f && hitDot > -0.25f )
2105 		{//hit pretty much perpendicular, pop straight back
2106 			attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
2107 			attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
2108 			return qfalse;
2109 		}
2110 		else
2111 		{//a deflection
2112 			vec3_t	att_Right, att_Up, att_DeflectionDir;
2113 			float	swingRDot, swingUDot;
2114 
2115 			//get the direction of the deflection
2116 			VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
2117 			//get our bounce straight back direction
2118 			VectorScale( att_HitDir, -1.0f, temp );
2119 			//add the bounce back and deflection
2120 			VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
2121 			//normalize the result to determine what direction our saber should bounce back toward
2122 			VectorNormalize( att_DeflectionDir );
2123 
2124 			//need to know the direction of the deflectoin relative to the attacker's facing
2125 			VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
2126 			AngleVectors( temp, NULL, att_Right, att_Up );
2127 			swingRDot = DotProduct( att_Right, att_DeflectionDir );
2128 			swingUDot = DotProduct( att_Up, att_DeflectionDir );
2129 
2130 			if ( swingRDot > 0.25f )
2131 			{//deflect to right
2132 				if ( swingUDot > 0.25f )
2133 				{//deflect to top
2134 					attacker->client->ps.saberMove = LS_D1_TR;
2135 				}
2136 				else if ( swingUDot < -0.25f )
2137 				{//deflect to bottom
2138 					attacker->client->ps.saberMove = LS_D1_BR;
2139 				}
2140 				else
2141 				{//deflect horizontally
2142 					attacker->client->ps.saberMove = LS_D1__R;
2143 				}
2144 			}
2145 			else if ( swingRDot < -0.25f )
2146 			{//deflect to left
2147 				if ( swingUDot > 0.25f )
2148 				{//deflect to top
2149 					attacker->client->ps.saberMove = LS_D1_TL;
2150 				}
2151 				else if ( swingUDot < -0.25f )
2152 				{//deflect to bottom
2153 					attacker->client->ps.saberMove = LS_D1_BL;
2154 				}
2155 				else
2156 				{//deflect horizontally
2157 					attacker->client->ps.saberMove = LS_D1__L;
2158 				}
2159 			}
2160 			else
2161 			{//deflect in middle
2162 				if ( swingUDot > 0.25f )
2163 				{//deflect to top
2164 					attacker->client->ps.saberMove = LS_D1_T_;
2165 				}
2166 				else if ( swingUDot < -0.25f )
2167 				{//deflect to bottom
2168 					attacker->client->ps.saberMove = LS_D1_B_;
2169 				}
2170 				else
2171 				{//deflect horizontally?  Well, no such thing as straight back in my face, so use top
2172 					if ( swingRDot > 0 )
2173 					{
2174 						attacker->client->ps.saberMove = LS_D1_TR;
2175 					}
2176 					else if ( swingRDot < 0 )
2177 					{
2178 						attacker->client->ps.saberMove = LS_D1_TL;
2179 					}
2180 					else
2181 					{
2182 						attacker->client->ps.saberMove = LS_D1_T_;
2183 					}
2184 				}
2185 			}
2186 
2187 			attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
2188 			return qtrue;
2189 		}
2190 	}
2191 }
2192 
G_KnockawayForParry(int move)2193 int G_KnockawayForParry( int move )
2194 {
2195 	//FIXME: need actual anims for this
2196 	//FIXME: need to know which side of the saber was hit!  For now, we presume the saber gets knocked away from the center
2197 	switch ( move )
2198 	{
2199 	case LS_PARRY_UP:
2200 		return LS_K1_T_;//push up
2201 		break;
2202 	case LS_PARRY_UR:
2203 	default://case LS_READY:
2204 		return LS_K1_TR;//push up, slightly to right
2205 		break;
2206 	case LS_PARRY_UL:
2207 		return LS_K1_TL;//push up and to left
2208 		break;
2209 	case LS_PARRY_LR:
2210 		return LS_K1_BR;//push down and to left
2211 		break;
2212 	case LS_PARRY_LL:
2213 		return LS_K1_BL;//push down and to right
2214 		break;
2215 	}
2216 }
2217 
2218 #define SABER_NONATTACK_DAMAGE 1
2219 
2220 //For strong attacks, we ramp damage based on the point in the attack animation
G_GetAttackDamage(gentity_t * self,int minDmg,int maxDmg,float multPoint)2221 static QINLINE int G_GetAttackDamage(gentity_t *self, int minDmg, int maxDmg, float multPoint)
2222 {
2223 	int speedDif = 0;
2224 	int totalDamage = maxDmg;
2225 	float peakPoint = 0;
2226 	float attackAnimLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].frameLerp));
2227 	float currentPoint = 0;
2228 	float damageFactor = 0;
2229 	float animSpeedFactor = 1.0f;
2230 
2231 	//Be sure to scale by the proper anim speed just as if we were going to play the animation
2232 	BG_SaberStartTransAnim(self->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs);
2233 	speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor);
2234 	attackAnimLength += speedDif;
2235 	peakPoint = attackAnimLength;
2236 	peakPoint -= attackAnimLength*multPoint;
2237 
2238 	//we treat torsoTimer as the point in the animation (closer it is to attackAnimLength, closer it is to beginning)
2239 	currentPoint = self->client->ps.torsoTimer;
2240 
2241 	damageFactor = (float)((currentPoint/peakPoint));
2242 	if (damageFactor > 1)
2243 	{
2244 		damageFactor = (2.0f - damageFactor);
2245 	}
2246 
2247 	totalDamage *= damageFactor;
2248 	if (totalDamage < minDmg)
2249 	{
2250 		totalDamage = minDmg;
2251 	}
2252 	if (totalDamage > maxDmg)
2253 	{
2254 		totalDamage = maxDmg;
2255 	}
2256 
2257 	//Com_Printf("%i\n", totalDamage);
2258 
2259 	return totalDamage;
2260 }
2261 
2262 //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)
G_GetAnimPoint(gentity_t * self)2263 static QINLINE float G_GetAnimPoint(gentity_t *self)
2264 {
2265 	int speedDif = 0;
2266 	float attackAnimLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].frameLerp));
2267 	float currentPoint = 0;
2268 	float animSpeedFactor = 1.0f;
2269 	float animPercentage = 0;
2270 
2271 	//Be sure to scale by the proper anim speed just as if we were going to play the animation
2272 	BG_SaberStartTransAnim(self->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs);
2273 	speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor);
2274 	attackAnimLength += speedDif;
2275 
2276 	currentPoint = self->client->ps.torsoTimer;
2277 
2278 	animPercentage = currentPoint/attackAnimLength;
2279 
2280 	//Com_Printf("%f\n", animPercentage);
2281 
2282 	return animPercentage;
2283 }
2284 
G_ClientIdleInWorld(gentity_t * ent)2285 static QINLINE qboolean G_ClientIdleInWorld(gentity_t *ent)
2286 {
2287 	if (ent->s.eType == ET_NPC)
2288 	{
2289 		return qfalse;
2290 	}
2291 
2292 	if (!ent->client->pers.cmd.upmove && !ent->client->pers.cmd.forwardmove && !ent->client->pers.cmd.rightmove &&
2293 		!(ent->client->pers.cmd.buttons & BUTTON_GESTURE) &&
2294 		!(ent->client->pers.cmd.buttons & BUTTON_FORCEGRIP) &&
2295 		!(ent->client->pers.cmd.buttons & BUTTON_ALT_ATTACK) &&
2296 		!(ent->client->pers.cmd.buttons & BUTTON_FORCEPOWER) &&
2297 		!(ent->client->pers.cmd.buttons & BUTTON_FORCE_LIGHTNING) &&
2298 		!(ent->client->pers.cmd.buttons & BUTTON_FORCE_DRAIN) &&
2299 		!(ent->client->pers.cmd.buttons & BUTTON_ATTACK))
2300 		return qtrue;
2301 
2302 	return qfalse;
2303 }
2304 
G_G2TraceCollide(trace_t * tr,vec3_t lastValidStart,vec3_t lastValidEnd,vec3_t traceMins,vec3_t traceMaxs)2305 static QINLINE qboolean G_G2TraceCollide(trace_t *tr, vec3_t lastValidStart, vec3_t lastValidEnd, vec3_t traceMins, vec3_t traceMaxs)
2306 { //Hit the ent with the normal trace, try the collision trace.
2307 	G2Trace_t		G2Trace;
2308 	gentity_t		*g2Hit;
2309 	vec3_t			angles;
2310 	int				tN = 0;
2311 	float			fRadius = 0;
2312 
2313 	if (!d_saberGhoul2Collision.integer)
2314 	{
2315 		return qfalse;
2316 	}
2317 
2318 	if (!g_entities[tr->entityNum].inuse /*||
2319 		(g_entities[tr->entityNum].s.eFlags & EF_DEAD)*/)
2320 	{ //don't do perpoly on corpses.
2321 		return qfalse;
2322 	}
2323 
2324 	if (traceMins[0] ||
2325 		traceMins[1] ||
2326 		traceMins[2] ||
2327 		traceMaxs[0] ||
2328 		traceMaxs[1] ||
2329 		traceMaxs[2])
2330 	{
2331 		fRadius=(traceMaxs[0]-traceMins[0])/2.0f;
2332 	}
2333 
2334 	memset (&G2Trace, 0, sizeof(G2Trace));
2335 
2336 	while (tN < MAX_G2_COLLISIONS)
2337 	{
2338 		G2Trace[tN].mEntityNum = -1;
2339 		tN++;
2340 	}
2341 	g2Hit = &g_entities[tr->entityNum];
2342 
2343 	if (g2Hit && g2Hit->inuse && g2Hit->ghoul2)
2344 	{
2345 		vec3_t g2HitOrigin;
2346 
2347 		angles[ROLL] = angles[PITCH] = 0;
2348 
2349 		if (g2Hit->client)
2350 		{
2351 			VectorCopy(g2Hit->client->ps.origin, g2HitOrigin);
2352 			angles[YAW] = g2Hit->client->ps.viewangles[YAW];
2353 		}
2354 		else
2355 		{
2356 			VectorCopy(g2Hit->r.currentOrigin, g2HitOrigin);
2357 			angles[YAW] = g2Hit->r.currentAngles[YAW];
2358 		}
2359 
2360 		if (com_optvehtrace.integer &&
2361 			g2Hit->s.eType == ET_NPC &&
2362 			g2Hit->s.NPC_class == CLASS_VEHICLE &&
2363 			g2Hit->m_pVehicle)
2364 		{
2365 			trap->G2API_CollisionDetectCache ( G2Trace, g2Hit->ghoul2, angles, g2HitOrigin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, g_g2TraceLod.integer, fRadius );
2366 		}
2367 		else
2368 		{
2369 			trap->G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2HitOrigin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, g_g2TraceLod.integer, fRadius );
2370 		}
2371 
2372 		if (G2Trace[0].mEntityNum != g2Hit->s.number)
2373 		{
2374 			tr->fraction = 1.0f;
2375 			tr->entityNum = ENTITYNUM_NONE;
2376 			tr->startsolid = 0;
2377 			tr->allsolid = 0;
2378 			return qfalse;
2379 		}
2380 		else
2381 		{ //The ghoul2 trace result matches, so copy the collision position into the trace endpos and send it back.
2382 			VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos);
2383 			VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal);
2384 
2385 			if (g2Hit->client)
2386 			{
2387 				g2Hit->client->g2LastSurfaceHit = G2Trace[0].mSurfaceIndex;
2388 				g2Hit->client->g2LastSurfaceTime = level.time;
2389 			}
2390 			return qtrue;
2391 		}
2392 	}
2393 
2394 	return qfalse;
2395 }
2396 
G_SaberInBackAttack(int move)2397 static QINLINE qboolean G_SaberInBackAttack(int move)
2398 {
2399 	switch (move)
2400 	{
2401 	case LS_A_BACK:
2402 	case LS_A_BACK_CR:
2403 	case LS_A_BACKSTAB:
2404 		return qtrue;
2405 	}
2406 
2407 	return qfalse;
2408 }
2409 
2410 qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
2411 qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage);
2412 qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other);
2413 
2414 
2415 typedef struct saberFace_s
2416 {
2417 	vec3_t v1;
2418 	vec3_t v2;
2419 	vec3_t v3;
2420 } saberFace_t;
2421 
2422 //build faces around blade for collision checking -rww
G_BuildSaberFaces(vec3_t base,vec3_t tip,float radius,vec3_t fwd,vec3_t right,int * fNum,saberFace_t ** fList)2423 static QINLINE void G_BuildSaberFaces(vec3_t base, vec3_t tip, float radius, vec3_t fwd,
2424 										  vec3_t right, int *fNum, saberFace_t **fList)
2425 {
2426 	static saberFace_t faces[12];
2427 	int i = 0;
2428 	float *d1 = NULL, *d2 = NULL;
2429 	vec3_t invFwd;
2430 	vec3_t invRight;
2431 
2432 	VectorCopy(fwd, invFwd);
2433 	VectorInverse(invFwd);
2434 	VectorCopy(right, invRight);
2435 	VectorInverse(invRight);
2436 
2437 	while (i < 8)
2438 	{
2439 		//yeah, this part is kind of a hack, but eh
2440 		if (i < 2)
2441 		{ //"left" surface
2442 			d1 = &fwd[0];
2443 			d2 = &invRight[0];
2444 		}
2445 		else if (i < 4)
2446 		{ //"right" surface
2447 			d1 = &fwd[0];
2448 			d2 = &right[0];
2449 		}
2450 		else if (i < 6)
2451 		{ //"front" surface
2452 			d1 = &right[0];
2453 			d2 = &fwd[0];
2454 		}
2455 		else if (i < 8)
2456 		{ //"back" surface
2457 			d1 = &right[0];
2458 			d2 = &invFwd[0];
2459 		}
2460 
2461 		//first triangle for this surface
2462 		VectorMA(base, radius/2.0f, d1, faces[i].v1);
2463 		VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1);
2464 
2465 		VectorMA(tip, radius/2.0f, d1, faces[i].v2);
2466 		VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2);
2467 
2468 		VectorMA(tip, -radius/2.0f, d1, faces[i].v3);
2469 		VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3);
2470 
2471 		i++;
2472 
2473 		//second triangle for this surface
2474 		VectorMA(tip, -radius/2.0f, d1, faces[i].v1);
2475 		VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1);
2476 
2477 		VectorMA(base, radius/2.0f, d1, faces[i].v2);
2478 		VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2);
2479 
2480 		VectorMA(base, -radius/2.0f, d1, faces[i].v3);
2481 		VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3);
2482 
2483 		i++;
2484 	}
2485 
2486 	//top surface
2487 	//face 1
2488 	VectorMA(tip, radius/2.0f, fwd, faces[i].v1);
2489 	VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1);
2490 
2491 	VectorMA(tip, radius/2.0f, fwd, faces[i].v2);
2492 	VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2);
2493 
2494 	VectorMA(tip, -radius/2.0f, fwd, faces[i].v3);
2495 	VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3);
2496 
2497 	i++;
2498 
2499 	//face 2
2500 	VectorMA(tip, radius/2.0f, fwd, faces[i].v1);
2501 	VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1);
2502 
2503 	VectorMA(tip, -radius/2.0f, fwd, faces[i].v2);
2504 	VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2);
2505 
2506 	VectorMA(tip, -radius/2.0f, fwd, faces[i].v3);
2507 	VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3);
2508 
2509 	i++;
2510 
2511 	//bottom surface
2512 	//face 1
2513 	VectorMA(base, radius/2.0f, fwd, faces[i].v1);
2514 	VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1);
2515 
2516 	VectorMA(base, radius/2.0f, fwd, faces[i].v2);
2517 	VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2);
2518 
2519 	VectorMA(base, -radius/2.0f, fwd, faces[i].v3);
2520 	VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3);
2521 
2522 	i++;
2523 
2524 	//face 2
2525 	VectorMA(base, radius/2.0f, fwd, faces[i].v1);
2526 	VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1);
2527 
2528 	VectorMA(base, -radius/2.0f, fwd, faces[i].v2);
2529 	VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2);
2530 
2531 	VectorMA(base, -radius/2.0f, fwd, faces[i].v3);
2532 	VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3);
2533 
2534 	i++;
2535 
2536 	//yeah.. always going to be 12 I suppose.
2537 	*fNum = i;
2538 	*fList = &faces[0];
2539 }
2540 
2541 //collision utility function -rww
G_SabCol_CalcPlaneEq(vec3_t x,vec3_t y,vec3_t z,float * planeEq)2542 static QINLINE void G_SabCol_CalcPlaneEq(vec3_t x, vec3_t y, vec3_t z, float *planeEq)
2543 {
2544 	planeEq[0] = x[1]*(y[2]-z[2]) + y[1]*(z[2]-x[2]) + z[1]*(x[2]-y[2]);
2545 	planeEq[1] = x[2]*(y[0]-z[0]) + y[2]*(z[0]-x[0]) + z[2]*(x[0]-y[0]);
2546 	planeEq[2] = x[0]*(y[1]-z[1]) + y[0]*(z[1]-x[1]) + z[0]*(x[1]-y[1]);
2547 	planeEq[3] = -(x[0]*(y[1]*z[2] - z[1]*y[2]) + y[0]*(z[1]*x[2] - x[1]*z[2]) + z[0]*(x[1]*y[2] - y[1]*x[2]) );
2548 }
2549 
2550 //collision utility function -rww
G_SabCol_PointRelativeToPlane(vec3_t pos,float * side,float * planeEq)2551 static QINLINE int G_SabCol_PointRelativeToPlane(vec3_t pos, float *side, float *planeEq)
2552 {
2553 	*side = planeEq[0]*pos[0] + planeEq[1]*pos[1] + planeEq[2]*pos[2] + planeEq[3];
2554 
2555 	if (*side > 0.0f)
2556 	{
2557 		return 1;
2558 	}
2559 	else if (*side < 0.0f)
2560 	{
2561 		return -1;
2562 	}
2563 
2564 	return 0;
2565 }
2566 
2567 //do actual collision check using generated saber "faces"
G_SaberFaceCollisionCheck(int fNum,saberFace_t * fList,vec3_t atkStart,vec3_t atkEnd,vec3_t atkMins,vec3_t atkMaxs,vec3_t impactPoint)2568 static QINLINE qboolean G_SaberFaceCollisionCheck(int fNum, saberFace_t *fList, vec3_t atkStart,
2569 											 vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint)
2570 {
2571 	static float planeEq[4];
2572 	static float side, side2, dist;
2573 	static vec3_t dir;
2574 	static vec3_t point;
2575 	int i = 0;
2576 
2577 	if (VectorCompare(atkMins, vec3_origin) && VectorCompare(atkMaxs, vec3_origin))
2578 	{
2579 		VectorSet(atkMins, -1.0f, -1.0f, -1.0f);
2580 		VectorSet(atkMaxs, 1.0f, 1.0f, 1.0f);
2581 	}
2582 
2583 	VectorSubtract(atkEnd, atkStart, dir);
2584 
2585 	while (i < fNum)
2586 	{
2587 		G_SabCol_CalcPlaneEq(fList->v1, fList->v2, fList->v3, planeEq);
2588 
2589 		if (G_SabCol_PointRelativeToPlane(atkStart, &side, planeEq) !=
2590 			G_SabCol_PointRelativeToPlane(atkEnd, &side2, planeEq))
2591 		{ //start/end points intersect with the plane
2592 			static vec3_t extruded;
2593 			static vec3_t minPoint, maxPoint;
2594 			static vec3_t planeNormal;
2595 			static int facing;
2596 
2597 			VectorCopy(&planeEq[0], planeNormal);
2598 			side2 = planeNormal[0]*dir[0] + planeNormal[1]*dir[1] + planeNormal[2]*dir[2];
2599 
2600 			dist = side/side2;
2601 			VectorMA(atkStart, -dist, dir, point);
2602 
2603 			VectorAdd(point, atkMins, minPoint);
2604 			VectorAdd(point, atkMaxs, maxPoint);
2605 
2606 			//point is now the point at which we intersect on the plane.
2607 			//see if that point is within the edges of the face.
2608             VectorMA(fList->v1, -2.0f, planeNormal, extruded);
2609 			G_SabCol_CalcPlaneEq(fList->v1, fList->v2, extruded, planeEq);
2610 			facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
2611 
2612 			if (facing < 0)
2613 			{ //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
2614 				facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
2615 				if (facing < 0)
2616 				{
2617 					facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
2618 				}
2619 			}
2620 
2621 			if (facing >= 0)
2622 			{ //first edge is facing...
2623 				VectorMA(fList->v2, -2.0f, planeNormal, extruded);
2624 				G_SabCol_CalcPlaneEq(fList->v2, fList->v3, extruded, planeEq);
2625 				facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
2626 
2627 				if (facing < 0)
2628 				{ //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
2629 					facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
2630 					if (facing < 0)
2631 					{
2632 						facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
2633 					}
2634 				}
2635 
2636 				if (facing >= 0)
2637 				{ //second edge is facing...
2638 					VectorMA(fList->v3, -2.0f, planeNormal, extruded);
2639 					G_SabCol_CalcPlaneEq(fList->v3, fList->v1, extruded, planeEq);
2640 					facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq);
2641 
2642 					if (facing < 0)
2643 					{ //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane
2644 						facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq);
2645 						if (facing < 0)
2646 						{
2647 							facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq);
2648 						}
2649 					}
2650 
2651 					if (facing >= 0)
2652 					{ //third edge is facing.. success
2653 						VectorCopy(point, impactPoint);
2654 						return qtrue;
2655 					}
2656 				}
2657 			}
2658 		}
2659 
2660 		i++;
2661 		fList++;
2662 	}
2663 
2664 	//did not hit anything
2665 	return qfalse;
2666 }
2667 
2668 //check for collision of 2 blades -rww
G_SaberCollide(gentity_t * atk,gentity_t * def,vec3_t atkStart,vec3_t atkEnd,vec3_t atkMins,vec3_t atkMaxs,vec3_t impactPoint)2669 static QINLINE qboolean G_SaberCollide(gentity_t *atk, gentity_t *def, vec3_t atkStart,
2670 						vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint)
2671 {
2672 	static int i, j;
2673 
2674 	if (!g_saberBladeFaces.integer)
2675 	{ //detailed check not enabled
2676 		return qtrue;
2677 	}
2678 
2679 	if (!atk->inuse || !atk->client || !def->inuse || !def->client)
2680 	{ //must have 2 clients and a valid saber entity
2681 		return qfalse;
2682 	}
2683 
2684 	i = 0;
2685 	while (i < MAX_SABERS)
2686 	{
2687 		j = 0;
2688 		if (def->client->saber[i].model[0])
2689 		{ //valid saber on the defender
2690 			bladeInfo_t *blade;
2691 			vec3_t v, fwd, right, base, tip;
2692 			int fNum;
2693 			saberFace_t *fList;
2694 
2695 			//go through each blade on the defender's sabers
2696 			while (j < def->client->saber[i].numBlades)
2697 			{
2698 				blade = &def->client->saber[i].blade[j];
2699 
2700 				if ((level.time-blade->storageTime) < 200)
2701 				{ //recently updated
2702 					//first get base and tip of blade
2703 					VectorCopy(blade->muzzlePoint, base);
2704 					VectorMA(base, blade->lengthMax, blade->muzzleDir, tip);
2705 
2706 					//Now get relative angles between the points
2707 					VectorSubtract(tip, base, v);
2708 					vectoangles(v, v);
2709 					AngleVectors(v, NULL, right, fwd);
2710 
2711 					//now build collision faces for this blade
2712 					G_BuildSaberFaces(base, tip, blade->radius*3.0f, fwd, right, &fNum, &fList);
2713 					if (fNum > 0)
2714 					{
2715 #if 0
2716 						if (atk->s.number == 0)
2717 						{
2718 							int x = 0;
2719 							saberFace_t *l = fList;
2720 							while (x < fNum)
2721 							{
2722 								G_TestLine(fList->v1, fList->v2, 0x0000ff, 100);
2723 								G_TestLine(fList->v2, fList->v3, 0x0000ff, 100);
2724 								G_TestLine(fList->v3, fList->v1, 0x0000ff, 100);
2725 
2726 								fList++;
2727 								x++;
2728 							}
2729 							fList = l;
2730 						}
2731 #endif
2732 
2733 						if (G_SaberFaceCollisionCheck(fNum, fList, atkStart, atkEnd, atkMins, atkMaxs, impactPoint))
2734 						{ //collided
2735 							return qtrue;
2736 						}
2737 					}
2738 				}
2739 				j++;
2740 			}
2741 		}
2742 		i++;
2743 	}
2744 
2745 	return qfalse;
2746 }
2747 
WP_SaberBladeLength(saberInfo_t * saber)2748 float WP_SaberBladeLength( saberInfo_t *saber )
2749 {//return largest length
2750 	int	i;
2751 	float len = 0.0f;
2752 	for ( i = 0; i < saber->numBlades; i++ )
2753 	{
2754 		if ( saber->blade[i].lengthMax > len )
2755 		{
2756 			len = saber->blade[i].lengthMax;
2757 		}
2758 	}
2759 	return len;
2760 }
2761 
WP_SaberLength(gentity_t * ent)2762 float WP_SaberLength( gentity_t *ent )
2763 {//return largest length
2764 	if ( !ent || !ent->client )
2765 	{
2766 		return 0.0f;
2767 	}
2768 	else
2769 	{
2770 		int i;
2771 		float len, bestLen = 0.0f;
2772 		for ( i = 0; i < MAX_SABERS; i++ )
2773 		{
2774 			len = WP_SaberBladeLength( &ent->client->saber[i] );
2775 			if ( len > bestLen )
2776 			{
2777 				bestLen = len;
2778 			}
2779 		}
2780 		return bestLen;
2781 	}
2782 }
WPDEBUG_SaberColor(saber_colors_t saberColor)2783 int WPDEBUG_SaberColor( saber_colors_t saberColor )
2784 {
2785 	switch( (int)(saberColor) )
2786 	{
2787 		case SABER_RED:
2788 			return 0x000000ff;
2789 			break;
2790 		case SABER_ORANGE:
2791 			return 0x000088ff;
2792 			break;
2793 		case SABER_YELLOW:
2794 			return 0x0000ffff;
2795 			break;
2796 		case SABER_GREEN:
2797 			return 0x0000ff00;
2798 			break;
2799 		case SABER_BLUE:
2800 			return 0x00ff0000;
2801 			break;
2802 		case SABER_PURPLE:
2803 			return 0x00ff00ff;
2804 			break;
2805 		default:
2806 			return 0x00ffffff;//white
2807 			break;
2808 	}
2809 }
2810 /*
2811 WP_SabersIntersect
2812 
2813 Breaks the two saber paths into 2 tris each and tests each tri for the first saber path against each of the other saber path's tris
2814 
2815 FIXME: subdivide the arc into a consistant increment
2816 FIXME: test the intersection to see if the sabers really did intersect (weren't going in the same direction and/or passed through same point at different times)?
2817 */
2818 extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2);
2819 #define SABER_EXTRAPOLATE_DIST 16.0f
WP_SabersIntersect(gentity_t * ent1,int ent1SaberNum,int ent1BladeNum,gentity_t * ent2,qboolean checkDir)2820 qboolean WP_SabersIntersect( gentity_t *ent1, int ent1SaberNum, int ent1BladeNum, gentity_t *ent2, qboolean checkDir )
2821 {
2822 	vec3_t	saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
2823 	vec3_t	saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
2824 	int		ent2SaberNum = 0, ent2BladeNum = 0;
2825 	vec3_t	dir;
2826 
2827 	if ( !ent1 || !ent2 )
2828 	{
2829 		return qfalse;
2830 	}
2831 	if ( !ent1->client || !ent2->client )
2832 	{
2833 		return qfalse;
2834 	}
2835 	if ( BG_SabersOff( &ent1->client->ps )
2836 		|| BG_SabersOff( &ent2->client->ps ) )
2837 	{
2838 		return qfalse;
2839 	}
2840 
2841 	for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ )
2842 	{
2843 		if ( ent2->client->saber[ent2SaberNum].type != SABER_NONE )
2844 		{
2845 			for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->saber[ent2SaberNum].numBlades; ent2BladeNum++ )
2846 			{
2847 				if ( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax > 0 )
2848 				{//valid saber and this blade is on
2849 					//if ( ent1->client->saberInFlight )
2850 					{
2851 						VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 );
2852 						VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 );
2853 
2854 						VectorSubtract( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir );
2855 						VectorNormalize( dir );
2856 						VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
2857 
2858 						VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 );
2859 						VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 );
2860 
2861 						VectorSubtract( saberTipNext1, saberTip1, dir );
2862 						VectorNormalize( dir );
2863 						VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
2864 					}
2865 					/*
2866 					else
2867 					{
2868 						VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 );
2869 						VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 );
2870 						VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 );
2871 						VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 );
2872 					}
2873 					*/
2874 
2875 					//if ( ent2->client->saberInFlight )
2876 					{
2877 						VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 );
2878 						VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 );
2879 
2880 						VectorSubtract( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir );
2881 						VectorNormalize( dir );
2882 						VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
2883 
2884 						VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 );
2885 						VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 );
2886 
2887 						VectorSubtract( saberTipNext2, saberTip2, dir );
2888 						VectorNormalize( dir );
2889 						VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
2890 					}
2891 					/*
2892 					else
2893 					{
2894 						VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 );
2895 						VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 );
2896 						VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 );
2897 						VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirNext, saberTipNext2 );
2898 					}
2899 					*/
2900 					if ( checkDir )
2901 					{//check the direction of the two swings to make sure the sabers are swinging towards each other
2902 						vec3_t saberDir1, saberDir2;
2903 						float dot = 0.0f;
2904 
2905 						VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
2906 						VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
2907 						VectorNormalize( saberDir1 );
2908 						VectorNormalize( saberDir2 );
2909 						if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
2910 						{//sabers moving in same dir, probably didn't actually hit
2911 							continue;
2912 						}
2913 						//now check orientation of sabers, make sure they're not parallel or close to it
2914 						dot = DotProduct( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir );
2915 						if ( dot > 0.9f || dot < -0.9f )
2916 						{//too parallel to really block effectively?
2917 							continue;
2918 						}
2919 					}
2920 
2921 #ifdef DEBUG_SABER_BOX
2922 					if ( g_saberDebugBox.integer == 2 || g_saberDebugBox.integer == 4 )
2923 					{
2924 						G_TestLine(saberBase1, saberTip1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
2925 						G_TestLine(saberTip1, saberTipNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
2926 						G_TestLine(saberTipNext1, saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500);
2927 
2928 						G_TestLine(saberBase2, saberTip2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
2929 						G_TestLine(saberTip2, saberTipNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
2930 						G_TestLine(saberTipNext2, saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500);
2931 					}
2932 #endif
2933 					if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
2934 					{
2935 						return qtrue;
2936 					}
2937 					if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
2938 					{
2939 						return qtrue;
2940 					}
2941 					if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
2942 					{
2943 						return qtrue;
2944 					}
2945 					if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
2946 					{
2947 						return qtrue;
2948 					}
2949 				}
2950 			}
2951 		}
2952 	}
2953 	return qfalse;
2954 }
2955 
G_PowerLevelForSaberAnim(gentity_t * ent,int saberNum,qboolean mySaberHit)2956 static QINLINE int G_PowerLevelForSaberAnim( gentity_t *ent, int saberNum, qboolean mySaberHit )
2957 {
2958 	if ( !ent || !ent->client || saberNum >= MAX_SABERS )
2959 	{
2960 		return FORCE_LEVEL_0;
2961 	}
2962 	else
2963 	{
2964 		int anim = ent->client->ps.torsoAnim;
2965 		int	animTimer = ent->client->ps.torsoTimer;
2966 		int	animTimeElapsed = BG_AnimLength( ent->localAnimIndex, (animNumber_t)anim ) - animTimer;
2967 		saberInfo_t *saber = &ent->client->saber[saberNum];
2968 		if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_D1_B____ )
2969 		{
2970 			//FIXME: these two need their own style
2971 			if ( saber->type == SABER_LANCE )
2972 			{
2973 				return FORCE_LEVEL_4;
2974 			}
2975 			else if ( saber->type == SABER_TRIDENT )
2976 			{
2977 				return FORCE_LEVEL_3;
2978 			}
2979 			return FORCE_LEVEL_1;
2980 		}
2981 		if ( anim >= BOTH_A2_T__B_ && anim <= BOTH_D2_B____ )
2982 		{
2983 			return FORCE_LEVEL_2;
2984 		}
2985 		if ( anim >= BOTH_A3_T__B_ && anim <= BOTH_D3_B____ )
2986 		{
2987 			return FORCE_LEVEL_3;
2988 		}
2989 		if ( anim >= BOTH_A4_T__B_ && anim <= BOTH_D4_B____ )
2990 		{//desann
2991 			return FORCE_LEVEL_4;
2992 		}
2993 		if ( anim >= BOTH_A5_T__B_ && anim <= BOTH_D5_B____ )
2994 		{//tavion
2995 			return FORCE_LEVEL_2;
2996 		}
2997 		if ( anim >= BOTH_A6_T__B_ && anim <= BOTH_D6_B____ )
2998 		{//dual
2999 			return FORCE_LEVEL_2;
3000 		}
3001 		if ( anim >= BOTH_A7_T__B_ && anim <= BOTH_D7_B____ )
3002 		{//staff
3003 			return FORCE_LEVEL_2;
3004 		}
3005 		if ( anim >= BOTH_P1_S1_T_ && anim <= BOTH_H1_S1_BR )
3006 		{//parries, knockaways and broken parries
3007 			return FORCE_LEVEL_1;//FIXME: saberAnimLevel?
3008 		}
3009 		switch ( anim )
3010 		{
3011 		case BOTH_A2_STABBACK1:
3012 			if ( mySaberHit )
3013 			{//someone else hit my saber, not asking for damage level, but defense strength
3014 				return FORCE_LEVEL_1;
3015 			}
3016 			if ( animTimer < 450 )
3017 			{//end of anim
3018 				return FORCE_LEVEL_0;
3019 			}
3020 			else if ( animTimeElapsed < 400 )
3021 			{//beginning of anim
3022 				return FORCE_LEVEL_0;
3023 			}
3024 			return FORCE_LEVEL_3;
3025 			break;
3026 		case BOTH_ATTACK_BACK:
3027 			if ( animTimer < 500 )
3028 			{//end of anim
3029 				return FORCE_LEVEL_0;
3030 			}
3031 			return FORCE_LEVEL_3;
3032 			break;
3033 		case BOTH_CROUCHATTACKBACK1:
3034 			if ( animTimer < 800 )
3035 			{//end of anim
3036 				return FORCE_LEVEL_0;
3037 			}
3038 			return FORCE_LEVEL_3;
3039 			break;
3040 		case BOTH_BUTTERFLY_LEFT:
3041 		case BOTH_BUTTERFLY_RIGHT:
3042 		case BOTH_BUTTERFLY_FL1:
3043 		case BOTH_BUTTERFLY_FR1:
3044 			//FIXME: break up?
3045 			return FORCE_LEVEL_3;
3046 			break;
3047 		case BOTH_FJSS_TR_BL:
3048 		case BOTH_FJSS_TL_BR:
3049 			//FIXME: break up?
3050 			return FORCE_LEVEL_3;
3051 			break;
3052 		case BOTH_K1_S1_T_:	//# knockaway saber top
3053 		case BOTH_K1_S1_TR:	//# knockaway saber top right
3054 		case BOTH_K1_S1_TL:	//# knockaway saber top left
3055 		case BOTH_K1_S1_BL:	//# knockaway saber bottom left
3056 		case BOTH_K1_S1_B_:	//# knockaway saber bottom
3057 		case BOTH_K1_S1_BR:	//# knockaway saber bottom right
3058 			//FIXME: break up?
3059 			return FORCE_LEVEL_3;
3060 			break;
3061 		case BOTH_LUNGE2_B__T_:
3062 			if ( mySaberHit )
3063 			{//someone else hit my saber, not asking for damage level, but defense strength
3064 				return FORCE_LEVEL_1;
3065 			}
3066 			if ( animTimer < 400 )
3067 			{//end of anim
3068 				return FORCE_LEVEL_0;
3069 			}
3070 			else if ( animTimeElapsed < 150 )
3071 			{//beginning of anim
3072 				return FORCE_LEVEL_0;
3073 			}
3074 			return FORCE_LEVEL_3;
3075 			break;
3076 		case BOTH_FORCELEAP2_T__B_:
3077 			if ( animTimer < 400 )
3078 			{//end of anim
3079 				return FORCE_LEVEL_0;
3080 			}
3081 			else if ( animTimeElapsed < 550 )
3082 			{//beginning of anim
3083 				return FORCE_LEVEL_0;
3084 			}
3085 			return FORCE_LEVEL_3;
3086 			break;
3087 		case BOTH_VS_ATR_S:
3088 		case BOTH_VS_ATL_S:
3089 		case BOTH_VT_ATR_S:
3090 		case BOTH_VT_ATL_S:
3091 			return FORCE_LEVEL_3;//???
3092 			break;
3093 		case BOTH_JUMPFLIPSLASHDOWN1:
3094 			if ( animTimer <= 1000 )
3095 			{//end of anim
3096 				return FORCE_LEVEL_0;
3097 			}
3098 			else if ( animTimeElapsed < 600 )
3099 			{//beginning of anim
3100 				return FORCE_LEVEL_0;
3101 			}
3102 			return FORCE_LEVEL_3;
3103 			break;
3104 		case BOTH_JUMPFLIPSTABDOWN:
3105 			if ( animTimer <= 1300 )
3106 			{//end of anim
3107 				return FORCE_LEVEL_0;
3108 			}
3109 			else if ( animTimeElapsed <= 300 )
3110 			{//beginning of anim
3111 				return FORCE_LEVEL_0;
3112 			}
3113 			return FORCE_LEVEL_3;
3114 			break;
3115 		case BOTH_JUMPATTACK6:
3116 			/*
3117 			if (pm->ps)
3118 			{
3119 				if ( ( pm->ps->legsAnimTimer >= 1450
3120 						&& BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 400 )
3121 					||(pm->ps->legsAnimTimer >= 400
3122 						&& BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 1100 ) )
3123 				{//pretty much sideways
3124 					return FORCE_LEVEL_3;
3125 				}
3126 			}
3127 			*/
3128 			if ( ( animTimer >= 1450
3129 					&& animTimeElapsed >= 400 )
3130 				||(animTimer >= 400
3131 					&& animTimeElapsed >= 1100 ) )
3132 			{//pretty much sideways
3133 				return FORCE_LEVEL_3;
3134 			}
3135 			return FORCE_LEVEL_0;
3136 			break;
3137 		case BOTH_JUMPATTACK7:
3138 			if ( animTimer <= 1200 )
3139 			{//end of anim
3140 				return FORCE_LEVEL_0;
3141 			}
3142 			else if ( animTimeElapsed < 200 )
3143 			{//beginning of anim
3144 				return FORCE_LEVEL_0;
3145 			}
3146 			return FORCE_LEVEL_3;
3147 			break;
3148 		case BOTH_SPINATTACK6:
3149 			if ( animTimeElapsed <= 200 )
3150 			{//beginning of anim
3151 				return FORCE_LEVEL_0;
3152 			}
3153 			return FORCE_LEVEL_2;//FORCE_LEVEL_3;
3154 			break;
3155 		case BOTH_SPINATTACK7:
3156 			if ( animTimer <= 500 )
3157 			{//end of anim
3158 				return FORCE_LEVEL_0;
3159 			}
3160 			else if ( animTimeElapsed < 500 )
3161 			{//beginning of anim
3162 				return FORCE_LEVEL_0;
3163 			}
3164 			return FORCE_LEVEL_2;//FORCE_LEVEL_3;
3165 			break;
3166 		case BOTH_FORCELONGLEAP_ATTACK:
3167 			if ( animTimeElapsed <= 200 )
3168 			{//1st four frames of anim
3169 				return FORCE_LEVEL_3;
3170 			}
3171 			break;
3172 		/*
3173 		case BOTH_A7_KICK_F://these kicks attack, too
3174 		case BOTH_A7_KICK_B:
3175 		case BOTH_A7_KICK_R:
3176 		case BOTH_A7_KICK_L:
3177 			//FIXME: break up
3178 			return FORCE_LEVEL_3;
3179 			break;
3180 		*/
3181 		case BOTH_STABDOWN:
3182 			if ( animTimer <= 900 )
3183 			{//end of anim
3184 				return FORCE_LEVEL_3;
3185 			}
3186 			break;
3187 		case BOTH_STABDOWN_STAFF:
3188 			if ( animTimer <= 850 )
3189 			{//end of anim
3190 				return FORCE_LEVEL_3;
3191 			}
3192 			break;
3193 		case BOTH_STABDOWN_DUAL:
3194 			if ( animTimer <= 900 )
3195 			{//end of anim
3196 				return FORCE_LEVEL_3;
3197 			}
3198 			break;
3199 		case BOTH_A6_SABERPROTECT:
3200 			if ( animTimer < 650 )
3201 			{//end of anim
3202 				return FORCE_LEVEL_0;
3203 			}
3204 			else if ( animTimeElapsed < 250 )
3205 			{//start of anim
3206 				return FORCE_LEVEL_0;
3207 			}
3208 			return FORCE_LEVEL_3;
3209 			break;
3210 		case BOTH_A7_SOULCAL:
3211 			if ( animTimer < 650 )
3212 			{//end of anim
3213 				return FORCE_LEVEL_0;
3214 			}
3215 			else if ( animTimeElapsed < 600 )
3216 			{//beginning of anim
3217 				return FORCE_LEVEL_0;
3218 			}
3219 			return FORCE_LEVEL_3;
3220 			break;
3221 		case BOTH_A1_SPECIAL:
3222 			if ( animTimer < 600 )
3223 			{//end of anim
3224 				return FORCE_LEVEL_0;
3225 			}
3226 			else if ( animTimeElapsed < 200 )
3227 			{//beginning of anim
3228 				return FORCE_LEVEL_0;
3229 			}
3230 			return FORCE_LEVEL_3;
3231 			break;
3232 		case BOTH_A2_SPECIAL:
3233 			if ( animTimer < 300 )
3234 			{//end of anim
3235 				return FORCE_LEVEL_0;
3236 			}
3237 			else if ( animTimeElapsed < 200 )
3238 			{//beginning of anim
3239 				return FORCE_LEVEL_0;
3240 			}
3241 			return FORCE_LEVEL_3;
3242 			break;
3243 		case BOTH_A3_SPECIAL:
3244 			if ( animTimer < 700 )
3245 			{//end of anim
3246 				return FORCE_LEVEL_0;
3247 			}
3248 			else if ( animTimeElapsed < 200 )
3249 			{//beginning of anim
3250 				return FORCE_LEVEL_0;
3251 			}
3252 			return FORCE_LEVEL_3;
3253 			break;
3254 		case BOTH_FLIP_ATTACK7:
3255 			return FORCE_LEVEL_3;
3256 			break;
3257 		case BOTH_PULL_IMPALE_STAB:
3258 			if ( mySaberHit )
3259 			{//someone else hit my saber, not asking for damage level, but defense strength
3260 				return FORCE_LEVEL_1;
3261 			}
3262 			if ( animTimer < 1000 )
3263 			{//end of anim
3264 				return FORCE_LEVEL_0;
3265 			}
3266 			return FORCE_LEVEL_3;
3267 			break;
3268 		case BOTH_PULL_IMPALE_SWING:
3269 			if ( animTimer < 500 )
3270 			{//end of anim
3271 				return FORCE_LEVEL_0;
3272 			}
3273 			else if ( animTimeElapsed < 650 )
3274 			{//beginning of anim
3275 				return FORCE_LEVEL_0;
3276 			}
3277 			return FORCE_LEVEL_3;
3278 			break;
3279 		case BOTH_ALORA_SPIN_SLASH:
3280 			if ( animTimer < 900 )
3281 			{//end of anim
3282 				return FORCE_LEVEL_0;
3283 			}
3284 			else if ( animTimeElapsed < 250 )
3285 			{//beginning of anim
3286 				return FORCE_LEVEL_0;
3287 			}
3288 			return FORCE_LEVEL_3;
3289 			break;
3290 		case BOTH_A6_FB:
3291 			if ( mySaberHit )
3292 			{//someone else hit my saber, not asking for damage level, but defense strength
3293 				return FORCE_LEVEL_1;
3294 			}
3295 			if ( animTimer < 250 )
3296 			{//end of anim
3297 				return FORCE_LEVEL_0;
3298 			}
3299 			else if ( animTimeElapsed < 250 )
3300 			{//beginning of anim
3301 				return FORCE_LEVEL_0;
3302 			}
3303 			return FORCE_LEVEL_3;
3304 			break;
3305 		case BOTH_A6_LR:
3306 			if ( mySaberHit )
3307 			{//someone else hit my saber, not asking for damage level, but defense strength
3308 				return FORCE_LEVEL_1;
3309 			}
3310 			if ( animTimer < 250 )
3311 			{//end of anim
3312 				return FORCE_LEVEL_0;
3313 			}
3314 			else if ( animTimeElapsed < 250 )
3315 			{//beginning of anim
3316 				return FORCE_LEVEL_0;
3317 			}
3318 			return FORCE_LEVEL_3;
3319 			break;
3320 		case BOTH_A7_HILT:
3321 			return FORCE_LEVEL_0;
3322 			break;
3323 	//===SABERLOCK SUPERBREAKS START===========================================================================
3324 		case BOTH_LK_S_DL_T_SB_1_W:
3325 			if ( animTimer < 700 )
3326 			{//end of anim
3327 				return FORCE_LEVEL_0;
3328 			}
3329 			return FORCE_LEVEL_5;
3330 			break;
3331 		case BOTH_LK_S_ST_S_SB_1_W:
3332 			if ( animTimer < 300 )
3333 			{//end of anim
3334 				return FORCE_LEVEL_0;
3335 			}
3336 			return FORCE_LEVEL_5;
3337 			break;
3338 		case BOTH_LK_S_DL_S_SB_1_W:
3339 		case BOTH_LK_S_S_S_SB_1_W:
3340 			if ( animTimer < 700 )
3341 			{//end of anim
3342 				return FORCE_LEVEL_0;
3343 			}
3344 			else if ( animTimeElapsed < 400 )
3345 			{//beginning of anim
3346 				return FORCE_LEVEL_0;
3347 			}
3348 			return FORCE_LEVEL_5;
3349 			break;
3350 		case BOTH_LK_S_ST_T_SB_1_W:
3351 		case BOTH_LK_S_S_T_SB_1_W:
3352 			if ( animTimer < 150 )
3353 			{//end of anim
3354 				return FORCE_LEVEL_0;
3355 			}
3356 			else if ( animTimeElapsed < 400 )
3357 			{//beginning of anim
3358 				return FORCE_LEVEL_0;
3359 			}
3360 			return FORCE_LEVEL_5;
3361 			break;
3362 		case BOTH_LK_DL_DL_T_SB_1_W:
3363 			return FORCE_LEVEL_5;
3364 			break;
3365 		case BOTH_LK_DL_DL_S_SB_1_W:
3366 		case BOTH_LK_DL_ST_S_SB_1_W:
3367 			if ( animTimeElapsed < 1000 )
3368 			{//beginning of anim
3369 				return FORCE_LEVEL_0;
3370 			}
3371 			return FORCE_LEVEL_5;
3372 			break;
3373 		case BOTH_LK_DL_ST_T_SB_1_W:
3374 			if ( animTimer < 950 )
3375 			{//end of anim
3376 				return FORCE_LEVEL_0;
3377 			}
3378 			else if ( animTimeElapsed < 650 )
3379 			{//beginning of anim
3380 				return FORCE_LEVEL_0;
3381 			}
3382 			return FORCE_LEVEL_5;
3383 			break;
3384 		case BOTH_LK_DL_S_S_SB_1_W:
3385 			if ( saberNum != 0 )
3386 			{//only right hand saber does damage in this suberbreak
3387 				return FORCE_LEVEL_0;
3388 			}
3389 			if ( animTimer < 900 )
3390 			{//end of anim
3391 				return FORCE_LEVEL_0;
3392 			}
3393 			else if ( animTimeElapsed < 450 )
3394 			{//beginning of anim
3395 				return FORCE_LEVEL_0;
3396 			}
3397 			return FORCE_LEVEL_5;
3398 			break;
3399 		case BOTH_LK_DL_S_T_SB_1_W:
3400 			if ( saberNum != 0 )
3401 			{//only right hand saber does damage in this suberbreak
3402 				return FORCE_LEVEL_0;
3403 			}
3404 			if ( animTimer < 250 )
3405 			{//end of anim
3406 				return FORCE_LEVEL_0;
3407 			}
3408 			else if ( animTimeElapsed < 150 )
3409 			{//beginning of anim
3410 				return FORCE_LEVEL_0;
3411 			}
3412 			return FORCE_LEVEL_5;
3413 			break;
3414 		case BOTH_LK_ST_DL_S_SB_1_W:
3415 			return FORCE_LEVEL_5;
3416 			break;
3417 		case BOTH_LK_ST_DL_T_SB_1_W:
3418 			//special suberbreak - doesn't kill, just kicks them backwards
3419 			return FORCE_LEVEL_0;
3420 			break;
3421 		case BOTH_LK_ST_ST_S_SB_1_W:
3422 		case BOTH_LK_ST_S_S_SB_1_W:
3423 			if ( animTimer < 800 )
3424 			{//end of anim
3425 				return FORCE_LEVEL_0;
3426 			}
3427 			else if ( animTimeElapsed < 350 )
3428 			{//beginning of anim
3429 				return FORCE_LEVEL_0;
3430 			}
3431 			return FORCE_LEVEL_5;
3432 			break;
3433 		case BOTH_LK_ST_ST_T_SB_1_W:
3434 		case BOTH_LK_ST_S_T_SB_1_W:
3435 			return FORCE_LEVEL_5;
3436 			break;
3437 	//===SABERLOCK SUPERBREAKS START===========================================================================
3438 		case BOTH_HANG_ATTACK:
3439 			//FIME: break up
3440 			if ( animTimer < 1000 )
3441 			{//end of anim
3442 				return FORCE_LEVEL_0;
3443 			}
3444 			else if ( animTimeElapsed < 250 )
3445 			{//beginning of anim
3446 				return FORCE_LEVEL_0;
3447 			}
3448 			else
3449 			{//sweet spot
3450 				return FORCE_LEVEL_5;
3451 			}
3452 			break;
3453 		case BOTH_ROLL_STAB:
3454 			if ( mySaberHit )
3455 			{//someone else hit my saber, not asking for damage level, but defense strength
3456 				return FORCE_LEVEL_1;
3457 			}
3458 			if ( animTimeElapsed > 400 )
3459 			{//end of anim
3460 				return FORCE_LEVEL_0;
3461 			}
3462 			else
3463 			{
3464 				return FORCE_LEVEL_3;
3465 			}
3466 			break;
3467 		}
3468 		return FORCE_LEVEL_0;
3469 	}
3470 }
3471 
3472 #define MAX_SABER_VICTIMS 16
3473 static int		victimEntityNum[MAX_SABER_VICTIMS];
3474 static qboolean victimHitEffectDone[MAX_SABER_VICTIMS];
3475 static float	totalDmg[MAX_SABER_VICTIMS];
3476 static vec3_t	dmgDir[MAX_SABER_VICTIMS];
3477 static vec3_t	dmgSpot[MAX_SABER_VICTIMS];
3478 static qboolean dismemberDmg[MAX_SABER_VICTIMS];
3479 static int saberKnockbackFlags[MAX_SABER_VICTIMS];
3480 static int numVictims = 0;
WP_SaberClearDamage(void)3481 void WP_SaberClearDamage( void )
3482 {
3483 	int ven;
3484 	for ( ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
3485 	{
3486 		victimEntityNum[ven] = ENTITYNUM_NONE;
3487 	}
3488 	memset( victimHitEffectDone, 0, sizeof( victimHitEffectDone ) );
3489 	memset( totalDmg, 0, sizeof( totalDmg ) );
3490 	memset( dmgDir, 0, sizeof( dmgDir ) );
3491 	memset( dmgSpot, 0, sizeof( dmgSpot ) );
3492 	memset( dismemberDmg, 0, sizeof( dismemberDmg ) );
3493 	memset( saberKnockbackFlags, 0, sizeof( saberKnockbackFlags ) );
3494 	numVictims = 0;
3495 }
3496 
WP_SaberDamageAdd(int trVictimEntityNum,vec3_t trDmgDir,vec3_t trDmgSpot,int trDmg,qboolean doDismemberment,int knockBackFlags)3497 void WP_SaberDamageAdd( int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot,
3498 					   int trDmg, qboolean doDismemberment, int knockBackFlags )
3499 {
3500 	if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
3501 	{
3502 		return;
3503 	}
3504 
3505 	if ( trDmg )
3506 	{//did some damage to something
3507 		int curVictim = 0;
3508 		int i;
3509 
3510 		for ( i = 0; i < numVictims; i++ )
3511 		{
3512 			if ( victimEntityNum[i] == trVictimEntityNum )
3513 			{//already hit this guy before
3514 				curVictim = i;
3515 				break;
3516 			}
3517 		}
3518 		if ( i == numVictims )
3519 		{//haven't hit his guy before
3520 			if ( numVictims + 1 >= MAX_SABER_VICTIMS )
3521 			{//can't add another victim at this time
3522 				return;
3523 			}
3524 			//add a new victim to the list
3525 			curVictim = numVictims;
3526 			victimEntityNum[numVictims++] = trVictimEntityNum;
3527 		}
3528 
3529 		totalDmg[curVictim] += trDmg;
3530 		if ( VectorCompare( dmgDir[curVictim], vec3_origin ) )
3531 		{
3532 			VectorCopy( trDmgDir, dmgDir[curVictim] );
3533 		}
3534 		if ( VectorCompare( dmgSpot[curVictim], vec3_origin ) )
3535 		{
3536 			VectorCopy( trDmgSpot, dmgSpot[curVictim] );
3537 		}
3538 		if ( doDismemberment )
3539 		{
3540 			dismemberDmg[curVictim] = qtrue;
3541 		}
3542 		saberKnockbackFlags[curVictim] |= knockBackFlags;
3543 	}
3544 }
3545 
WP_SaberApplyDamage(gentity_t * self)3546 void WP_SaberApplyDamage( gentity_t *self )
3547 {
3548 	int i;
3549 	if ( !numVictims )
3550 	{
3551 		return;
3552 	}
3553 	for ( i = 0; i < numVictims; i++ )
3554 	{
3555 		gentity_t *victim = NULL;
3556 		int dflags = 0;
3557 
3558 		victim = &g_entities[victimEntityNum[i]];
3559 
3560 // nmckenzie: SABER_DAMAGE_WALLS
3561 		if ( !victim->client )
3562 		{
3563 			totalDmg[i] *= g_saberWallDamageScale.value;
3564 		}
3565 
3566 		if ( !dismemberDmg[i] )
3567 		{//don't do dismemberment!
3568 			dflags |= DAMAGE_NO_DISMEMBER;
3569 		}
3570 		dflags |= saberKnockbackFlags[i];
3571 
3572 		G_Damage( victim, self, self, dmgDir[i], dmgSpot[i], totalDmg[i], dflags, MOD_SABER );
3573 	}
3574 }
3575 
3576 
WP_SaberDoHit(gentity_t * self,int saberNum,int bladeNum)3577 void WP_SaberDoHit( gentity_t *self, int saberNum, int bladeNum )
3578 {
3579 	int i;
3580 	if ( !numVictims )
3581 	{
3582 		return;
3583 	}
3584 	for ( i = 0; i < numVictims; i++ )
3585 	{
3586 		gentity_t *te = NULL, *victim = NULL;
3587 		qboolean isDroid = qfalse;
3588 
3589 		if ( victimHitEffectDone[i] )
3590 		{
3591 			continue;
3592 		}
3593 
3594 		victimHitEffectDone[i] = qtrue;
3595 
3596 		victim = &g_entities[victimEntityNum[i]];
3597 
3598 		if ( victim->client )
3599 		{
3600 			class_t npc_class = victim->client->NPC_class;
3601 
3602 			if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
3603 					npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
3604 					npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
3605 					npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
3606 			{ //don't make "blood" sparks for droids.
3607 				isDroid = qtrue;
3608 			}
3609 		}
3610 
3611 		te = G_TempEntity( dmgSpot[i], EV_SABER_HIT );
3612 		if ( te )
3613 		{
3614 			te->s.otherEntityNum = victimEntityNum[i];
3615 			te->s.otherEntityNum2 = self->s.number;
3616 			te->s.weapon = saberNum;
3617 			te->s.legsAnim = bladeNum;
3618 
3619 			VectorCopy(dmgSpot[i], te->s.origin);
3620 			//VectorCopy(tr.plane.normal, te->s.angles);
3621 			VectorScale( dmgDir[i], -1, te->s.angles);
3622 
3623 			if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
3624 			{ //don't let it play with no direction
3625 				te->s.angles[1] = 1;
3626 			}
3627 
3628 			if (!isDroid && (victim->client || victim->s.eType == ET_NPC ||
3629 				victim->s.eType == ET_BODY))
3630 			{
3631 				if ( totalDmg[i] < 5 )
3632 				{
3633 					te->s.eventParm = 3;
3634 				}
3635 				else if (totalDmg[i] < 20 )
3636 				{
3637 					te->s.eventParm = 2;
3638 				}
3639 				else
3640 				{
3641 					te->s.eventParm = 1;
3642 				}
3643 			}
3644 			else
3645 			{
3646 				if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[saberNum], bladeNum )
3647 					&& (self->client->saber[saberNum].saberFlags2&SFL2_NO_CLASH_FLARE) )
3648 				{//don't do clash flare
3649 				}
3650 				else if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[saberNum], bladeNum )
3651 					&& (self->client->saber[saberNum].saberFlags2&SFL2_NO_CLASH_FLARE2) )
3652 				{//don't do clash flare
3653 				}
3654 				else
3655 				{
3656 					if (totalDmg[i] > SABER_NONATTACK_DAMAGE)
3657 					{ //I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing.
3658 						gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE );
3659 						VectorCopy(te->s.origin, teS->s.origin);
3660 					}
3661 					te->s.eventParm = 0;
3662 				}
3663 			}
3664 		}
3665 	}
3666 }
3667 
3668 extern qboolean G_EntIsBreakable( int entityNum );
3669 extern void G_Knockdown( gentity_t *victim );
WP_SaberRadiusDamage(gentity_t * ent,vec3_t point,float radius,int damage,float knockBack)3670 void WP_SaberRadiusDamage( gentity_t *ent, vec3_t point, float radius, int damage, float knockBack )
3671 {
3672 	if ( !ent || !ent->client )
3673 	{
3674 		return;
3675 	}
3676 	else if ( radius <= 0.0f || (damage <= 0 && knockBack <= 0) )
3677 	{
3678 		return;
3679 	}
3680 	else
3681 	{
3682 		vec3_t		mins, maxs, entDir;
3683 		int			radiusEnts[128];
3684 		gentity_t	*radiusEnt = NULL;
3685 		int			numEnts, i;
3686 		float		dist;
3687 
3688 		//Setup the bbox to search in
3689 		for ( i = 0; i < 3; i++ )
3690 		{
3691 			mins[i] = point[i] - radius;
3692 			maxs[i] = point[i] + radius;
3693 		}
3694 
3695 		//Get the number of entities in a given space
3696 		numEnts = trap->EntitiesInBox( mins, maxs, radiusEnts, 128 );
3697 
3698 		for ( i = 0; i < numEnts; i++ )
3699 		{
3700 			radiusEnt = &g_entities[radiusEnts[i]];
3701 			if ( !radiusEnt->inuse )
3702 			{
3703 				continue;
3704 			}
3705 
3706 			if ( radiusEnt == ent )
3707 			{//Skip myself
3708 				continue;
3709 			}
3710 
3711 			if ( radiusEnt->client == NULL )
3712 			{//must be a client
3713 				if ( G_EntIsBreakable( radiusEnt->s.number ) )
3714 				{//damage breakables within range, but not as much
3715 					G_Damage( radiusEnt, ent, ent, vec3_origin, radiusEnt->r.currentOrigin, 10, 0, MOD_MELEE );
3716 				}
3717 				continue;
3718 			}
3719 
3720 			if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
3721 			{//can't be one being held
3722 				continue;
3723 			}
3724 
3725 			VectorSubtract( radiusEnt->r.currentOrigin, point, entDir );
3726 			dist = VectorNormalize( entDir );
3727 			if ( dist <= radius )
3728 			{//in range
3729 				if ( damage > 0 )
3730 				{//do damage
3731 					int points = ceil((float)damage*dist/radius);
3732 					G_Damage( radiusEnt, ent, ent, vec3_origin, radiusEnt->r.currentOrigin, points, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
3733 				}
3734 				if ( knockBack > 0 )
3735 				{//do knockback
3736 					if ( radiusEnt->client
3737 						&& radiusEnt->client->NPC_class != CLASS_RANCOR
3738 						&& radiusEnt->client->NPC_class != CLASS_ATST
3739 						&& !(radiusEnt->flags&FL_NO_KNOCKBACK) )//don't throw them back
3740 					{
3741 						float knockbackStr = knockBack*dist/radius;
3742 						entDir[2] += 0.1f;
3743 						VectorNormalize( entDir );
3744 						G_Throw( radiusEnt, entDir, knockbackStr );
3745 						if ( radiusEnt->health > 0 )
3746 						{//still alive
3747 							if ( knockbackStr > 50 )
3748 							{//close enough and knockback high enough to possibly knock down
3749 								if ( dist < (radius*0.5f)
3750 									|| radiusEnt->client->ps.groundEntityNum != ENTITYNUM_NONE )
3751 								{//within range of my fist or within ground-shaking range and not in the air
3752 									G_Knockdown( radiusEnt );//, ent, entDir, 500, qtrue );
3753 								}
3754 							}
3755 						}
3756 					}
3757 				}
3758 			}
3759 		}
3760 	}
3761 }
3762 
3763 static qboolean saberDoClashEffect = qfalse;
3764 static vec3_t saberClashPos = {0};
3765 static vec3_t saberClashNorm = {0};
3766 static int saberClashEventParm = 1;
WP_SaberDoClash(gentity_t * self,int saberNum,int bladeNum)3767 void WP_SaberDoClash( gentity_t *self, int saberNum, int bladeNum )
3768 {
3769 	if ( saberDoClashEffect )
3770 	{
3771 		gentity_t *te = G_TempEntity( saberClashPos, EV_SABER_BLOCK );
3772 		VectorCopy(saberClashPos, te->s.origin);
3773 		VectorCopy(saberClashNorm, te->s.angles);
3774 		te->s.eventParm = saberClashEventParm;
3775 		te->s.otherEntityNum2 = self->s.number;
3776 		te->s.weapon = saberNum;
3777 		te->s.legsAnim = bladeNum;
3778 	}
3779 }
3780 
WP_SaberBounceSound(gentity_t * ent,int saberNum,int bladeNum)3781 void WP_SaberBounceSound( gentity_t *ent, int saberNum, int bladeNum )
3782 {
3783 	int index = 1;
3784 	if ( !ent || !ent->client )
3785 	{
3786 		return;
3787 	}
3788 	index = Q_irand( 1, 9 );
3789 	if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
3790 		&& ent->client->saber[saberNum].bounceSound[0] )
3791 	{
3792 		G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].bounceSound[Q_irand( 0, 2 )] );
3793 	}
3794 	else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
3795 		&& ent->client->saber[saberNum].bounce2Sound[0] )
3796 	{
3797 		G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].bounce2Sound[Q_irand( 0, 2 )] );
3798 	}
3799 
3800 	else if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
3801 		&& ent->client->saber[saberNum].blockSound[0] )
3802 	{
3803 		G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].blockSound[Q_irand( 0, 2 )] );
3804 	}
3805 	else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->saber[saberNum], bladeNum )
3806 		&& ent->client->saber[saberNum].block2Sound[0] )
3807 	{
3808 		G_Sound( ent, CHAN_AUTO, ent->client->saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
3809 	}
3810 	else
3811 	{
3812 		G_Sound( ent, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
3813 	}
3814 }
3815 
3816 static qboolean saberHitWall = qfalse;
3817 static qboolean saberHitSaber = qfalse;
3818 static float saberHitFraction = 1.0f;
3819 //rww - MP version of the saber damage function. This is where all the things like blocking, triggering a parry,
3820 //triggering a broken parry, doing actual damage, etc. are done for the saber. It doesn't resemble the SP
3821 //version very much, but functionality is (hopefully) about the same.
3822 //This is a large function. I feel sort of bad inlining it. But it does get called tons of times per frame.
3823 qboolean BG_SuperBreakWinAnim( int anim );
3824 
CheckSaberDamage(gentity_t * self,int rSaberNum,int rBladeNum,vec3_t saberStart,vec3_t saberEnd,qboolean doInterpolate,int trMask,qboolean extrapolate)3825 static QINLINE qboolean CheckSaberDamage(gentity_t *self, int rSaberNum, int rBladeNum, vec3_t saberStart, vec3_t saberEnd, qboolean doInterpolate, int trMask, qboolean extrapolate )
3826 {
3827 	static trace_t tr;
3828 	static vec3_t dir;
3829 	static vec3_t saberTrMins, saberTrMaxs;
3830 	static vec3_t lastValidStart;
3831 	static vec3_t lastValidEnd;
3832 	static int selfSaberLevel;
3833 	static int otherSaberLevel;
3834 	int dmg = 0;
3835 	int attackStr = 0;
3836 	float saberBoxSize = d_saberBoxTraceSize.value;
3837 	qboolean idleDamage = qfalse;
3838 	qboolean didHit = qfalse;
3839 	qboolean sabersClashed = qfalse;
3840 	qboolean unblockable = qfalse;
3841 	qboolean didDefense = qfalse;
3842 	qboolean didOffense = qfalse;
3843 	qboolean saberTraceDone = qfalse;
3844 	qboolean otherUnblockable = qfalse;
3845 	qboolean tryDeflectAgain = qfalse;
3846 
3847 	gentity_t *otherOwner;
3848 
3849 	if (BG_SabersOff( &self->client->ps ))
3850 	{
3851 		return qfalse;
3852 	}
3853 
3854 	selfSaberLevel = G_SaberAttackPower(self, SaberAttacking(self));
3855 
3856 	//Add the standard radius into the box size
3857 	saberBoxSize += (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.5f);
3858 
3859 	if (self->client->ps.weaponTime <= 0)
3860 	{ //if not doing any attacks or anything, just use point traces.
3861 		VectorClear(saberTrMins);
3862 		VectorClear(saberTrMaxs);
3863 	}
3864 	else if (d_saberGhoul2Collision.integer)
3865 	{
3866 		if ( d_saberSPStyleDamage.integer )
3867 		{//SP-size saber damage traces
3868 			VectorSet(saberTrMins, -2, -2, -2 );
3869 			VectorSet(saberTrMaxs, 2, 2, 2 );
3870 		}
3871 		else
3872 		{
3873 			VectorSet(saberTrMins, -saberBoxSize*3, -saberBoxSize*3, -saberBoxSize*3);
3874 			VectorSet(saberTrMaxs, saberBoxSize*3, saberBoxSize*3, saberBoxSize*3);
3875 		}
3876 	}
3877 	else if (self->client->ps.fd.saberAnimLevel < FORCE_LEVEL_2)
3878 	{ //box trace for fast, because it doesn't get updated so often
3879 		VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
3880 		VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
3881 	}
3882 	else if (d_saberAlwaysBoxTrace.integer)
3883 	{
3884 		VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
3885 		VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
3886 	}
3887 	else
3888 	{ //just trace the minimum blade radius
3889 		saberBoxSize = (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.4f);
3890 
3891 		VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize);
3892 		VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize);
3893 	}
3894 
3895 	while (!saberTraceDone)
3896 	{
3897 		if ( doInterpolate
3898 			&& !d_saberSPStyleDamage.integer )
3899 		{ //This didn't quite work out like I hoped. But it's better than nothing. Sort of.
3900 			vec3_t oldSaberStart, oldSaberEnd, saberDif, oldSaberDif;
3901 			int traceTests = 0;
3902 			float trDif = 8;
3903 
3904 			if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
3905 			{//no valid last pos, use current
3906 				VectorCopy(saberStart, oldSaberStart);
3907 				VectorCopy(saberEnd, oldSaberEnd);
3908 			}
3909 			else
3910 			{//trace from last pos
3911 				VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
3912 				VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd);
3913 			}
3914 
3915 			VectorSubtract(saberStart, saberEnd, saberDif);
3916 			VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif);
3917 
3918 			VectorNormalize(saberDif);
3919 			VectorNormalize(oldSaberDif);
3920 
3921 			saberEnd[0] = saberStart[0] - (saberDif[0]*trDif);
3922 			saberEnd[1] = saberStart[1] - (saberDif[1]*trDif);
3923 			saberEnd[2] = saberStart[2] - (saberDif[2]*trDif);
3924 
3925 			oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif);
3926 			oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif);
3927 			oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif);
3928 
3929 			trap->Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask, qfalse, 0, 0);
3930 
3931 			VectorCopy(saberEnd, lastValidStart);
3932 			VectorCopy(saberStart, lastValidEnd);
3933 			if (tr.entityNum < MAX_CLIENTS)
3934 			{
3935 				G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
3936 			}
3937 			else if (tr.entityNum < ENTITYNUM_WORLD)
3938 			{
3939 				gentity_t *trHit = &g_entities[tr.entityNum];
3940 
3941 				if (trHit->inuse && trHit->ghoul2)
3942 				{ //hit a non-client entity with a g2 instance
3943 					G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
3944 				}
3945 			}
3946 
3947 			trDif++;
3948 
3949 			while (tr.fraction == 1.0 && traceTests < 4 && tr.entityNum >= ENTITYNUM_NONE)
3950 			{
3951 				if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
3952 				{//no valid last pos, use current
3953 					VectorCopy(saberStart, oldSaberStart);
3954 					VectorCopy(saberEnd, oldSaberEnd);
3955 				}
3956 				else
3957 				{//trace from last pos
3958 					VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
3959 					VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd);
3960 				}
3961 
3962 				VectorSubtract(saberStart, saberEnd, saberDif);
3963 				VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif);
3964 
3965 				VectorNormalize(saberDif);
3966 				VectorNormalize(oldSaberDif);
3967 
3968 				saberEnd[0] = saberStart[0] - (saberDif[0]*trDif);
3969 				saberEnd[1] = saberStart[1] - (saberDif[1]*trDif);
3970 				saberEnd[2] = saberStart[2] - (saberDif[2]*trDif);
3971 
3972 				oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif);
3973 				oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif);
3974 				oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif);
3975 
3976 				trap->Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask, qfalse, 0, 0);
3977 
3978 				VectorCopy(saberEnd, lastValidStart);
3979 				VectorCopy(saberStart, lastValidEnd);
3980 				if (tr.entityNum < MAX_CLIENTS)
3981 				{
3982 					G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
3983 				}
3984 				else if (tr.entityNum < ENTITYNUM_WORLD)
3985 				{
3986 					gentity_t *trHit = &g_entities[tr.entityNum];
3987 
3988 					if (trHit->inuse && trHit->ghoul2)
3989 					{ //hit a non-client entity with a g2 instance
3990 						G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
3991 					}
3992 				}
3993 
3994 				traceTests++;
3995 				trDif += 8;
3996 			}
3997 		}
3998 		else
3999 		{
4000 			vec3_t saberEndExtrapolated;
4001 			if ( extrapolate )
4002 			{//extrapolate 16
4003 				vec3_t diff;
4004 				VectorSubtract( saberEnd, saberStart, diff );
4005 				VectorNormalize( diff );
4006 				VectorMA( saberStart, SABER_EXTRAPOLATE_DIST, diff, saberEndExtrapolated );
4007 			}
4008 			else
4009 			{
4010 				VectorCopy( saberEnd, saberEndExtrapolated );
4011 			}
4012 			trap->Trace(&tr, saberStart, saberTrMins, saberTrMaxs, saberEndExtrapolated, self->s.number, trMask, qfalse, 0, 0);
4013 
4014 			VectorCopy(saberStart, lastValidStart);
4015 			VectorCopy(saberEndExtrapolated, lastValidEnd);
4016 			/*
4017 			if ( tr.allsolid || tr.startsolid )
4018 			{
4019 				if ( tr.entityNum == ENTITYNUM_NONE )
4020 				{
4021 					qboolean whah = qtrue;
4022 				}
4023 				Com_Printf( "saber trace start/all solid - ent is %d\n", tr.entityNum );
4024 			}
4025 			*/
4026 			if (tr.entityNum < MAX_CLIENTS)
4027 			{
4028 				G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
4029 			}
4030 			else if (tr.entityNum < ENTITYNUM_WORLD)
4031 			{
4032 				gentity_t *trHit = &g_entities[tr.entityNum];
4033 
4034 				if (trHit->inuse && trHit->ghoul2)
4035 				{ //hit a non-client entity with a g2 instance
4036 					G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs);
4037 				}
4038 			}
4039 		}
4040 
4041 		saberTraceDone = qtrue;
4042 	}
4043 
4044 	if ( self->client->ps.saberAttackWound < level.time
4045 		&& (SaberAttacking(self)
4046 			|| BG_SuperBreakWinAnim(self->client->ps.torsoAnim)
4047 			|| (d_saberSPStyleDamage.integer&&self->client->ps.saberInFlight&&rSaberNum==0)
4048 			|| (WP_SaberBladeDoTransitionDamage( &self->client->saber[rSaberNum], rBladeNum )&&BG_SaberInTransitionAny(self->client->ps.saberMove))
4049 			|| (self->client->ps.m_iVehicleNum && self->client->ps.saberMove > LS_READY) )
4050 	   )
4051 	{ //this animation is that of the last attack movement, and so it should do full damage
4052 		qboolean saberInSpecial = BG_SaberInSpecial(self->client->ps.saberMove);
4053 		qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove);
4054 
4055 		if ( d_saberSPStyleDamage.integer )
4056 		{
4057 			float fDmg = 0.0f;
4058 			if ( self->client->ps.saberInFlight )
4059 			{
4060 				gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum];
4061 				if ( !saberEnt
4062 					|| !saberEnt->s.saberInFlight )
4063 				{//does less damage on the way back
4064 					fDmg = 1.0f;
4065 					attackStr = FORCE_LEVEL_0;
4066 				}
4067 				else
4068 				{
4069 					fDmg = 2.5f*self->client->ps.fd.forcePowerLevel[FP_SABERTHROW];
4070 					attackStr = FORCE_LEVEL_1;
4071 				}
4072 			}
4073 			else
4074 			{
4075 				attackStr = G_PowerLevelForSaberAnim( self, rSaberNum, qfalse );
4076 				if ( g_saberRealisticCombat.integer )
4077 				{
4078 					switch ( attackStr )
4079 					{
4080 					default:
4081 					case FORCE_LEVEL_3:
4082 						fDmg = 10.0f;
4083 						break;
4084 					case FORCE_LEVEL_2:
4085 						fDmg = 5.0f;
4086 						break;
4087 					case FORCE_LEVEL_1:
4088 					case FORCE_LEVEL_0:
4089 						fDmg = 2.5f;
4090 						break;
4091 					}
4092 				}
4093 				else
4094 				{
4095 					if ( self->client->ps.torsoAnim == BOTH_SPINATTACK6
4096 						|| self->client->ps.torsoAnim == BOTH_SPINATTACK7 )
4097 					{//too easy to do, lower damage
4098 						fDmg = 2.5f;
4099 					}
4100 					else
4101 					{
4102 						fDmg = 2.5f * (float)attackStr;
4103 					}
4104 				}
4105 			}
4106 			if ( g_saberRealisticCombat.integer > 1 )
4107 			{//always do damage, and lots of it
4108 				if ( g_saberRealisticCombat.integer > 2 )
4109 				{//always do damage, and lots of it
4110 					fDmg = 25.0f;
4111 				}
4112 				else if ( fDmg > 0.1f )
4113 				{//only do super damage if we would have done damage according to normal rules
4114 					fDmg = 25.0f;
4115 				}
4116 			}
4117 			/*
4118 			if ( dmg > 0.1f )
4119 			{
4120 				if ( (self->client->ps.forcePowersActive&(1<<FP_RAGE)) )
4121 				{//add some damage if raged
4122 					dmg += self->client->ps.forcePowerLevel[FP_RAGE] * 5.0f;
4123 				}
4124 				else if ( self->client->ps.forceRageRecoveryTime )
4125 				{//halve it if recovering
4126 					dmg *= 0.5f;
4127 				}
4128 			}
4129 			*/
4130 			if ( level.gametype != GT_DUEL
4131 				&& level.gametype != GT_POWERDUEL
4132 				&& level.gametype != GT_SIEGE )
4133 			{//in faster-paced games, sabers do more damage
4134 				fDmg *= 2.0f;
4135 			}
4136 			if ( fDmg )
4137 			{//the longer the trace, the more damage it does
4138 				//FIXME: in SP, we only use the part of the trace that's actually *inside* the hit ent...
4139 				float traceLength = Distance( saberEnd, saberStart );
4140 				if ( tr.fraction >= 1.0f )
4141 				{//allsolid?
4142 					dmg = ceil( fDmg*traceLength*0.1f*0.33f );
4143 				}
4144 				else
4145 				{//fractional hit, the sooner you hit in the trace, the more damage you did
4146 					dmg = ceil( fDmg*traceLength*(1.0f-tr.fraction)*0.1f*0.33f );//(1.0f-tr.fraction) isn't really accurate, but kind of simulates what we have in SP
4147 				}
4148 #ifdef DEBUG_SABER_BOX
4149 				if ( g_saberDebugBox.integer == 3 || g_saberDebugBox.integer == 4 )
4150 				{
4151 					G_TestLine( saberStart, saberEnd, 0x0000ff, 50 );
4152 				}
4153 #endif
4154 			}
4155 			/*
4156 			if ( dmg )
4157 			{
4158 				Com_Printf("CL %i SABER DMG: %i, anim %s, torsoTimer %i\n", self->s.number, dmg, animTable[self->client->ps.torsoAnim].name, self->client->ps.torsoTimer );
4159 			}
4160 			*/
4161 			if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL
4162 				|| self->client->ps.torsoAnim == BOTH_A2_SPECIAL
4163 				|| self->client->ps.torsoAnim == BOTH_A3_SPECIAL )
4164 			{//parry/block/break-parry bonus for single-style kata moves
4165 				attackStr++;
4166 			}
4167 			if ( BG_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
4168 			{
4169 				trMask &= ~CONTENTS_LIGHTSABER;
4170 			}
4171 		}
4172 		else
4173 		{
4174 			dmg = SABER_HITDAMAGE;
4175 
4176 			if (self->client->ps.fd.saberAnimLevel == SS_STAFF ||
4177 				self->client->ps.fd.saberAnimLevel == SS_DUAL)
4178 			{
4179 				if (saberInSpecial)
4180 				{
4181 					//it will get auto-ramped based on the point in the attack, later on
4182 					if (self->client->ps.saberMove == LS_SPINATTACK ||
4183 						self->client->ps.saberMove == LS_SPINATTACK_DUAL)
4184 					{ //these attacks are long and have the potential to hit a lot so they will do less damage.
4185 						dmg = 10;
4186 					}
4187 					else
4188 					{
4189 						if ( BG_KickingAnim( self->client->ps.legsAnim ) ||
4190 							BG_KickingAnim( self->client->ps.torsoAnim ) )
4191 						{ //saber shouldn't do more than min dmg during kicks
4192 							dmg = 2;
4193 						}
4194 						else if (BG_SaberInKata(self->client->ps.saberMove))
4195 						{ //special kata move
4196 							if (self->client->ps.fd.saberAnimLevel == SS_DUAL)
4197 							{ //this is the nasty saber twirl, do big damage cause it makes you vulnerable
4198 								dmg = 90;
4199 							}
4200 							else
4201 							{ //staff kata
4202 								dmg = G_GetAttackDamage(self, 60, 70, 0.5f);
4203 							}
4204 						}
4205 						else
4206 						{
4207 							//dmg = 90;
4208 							//ramp from 2 to 90 by default for other specials
4209 							dmg = G_GetAttackDamage(self, 2, 90, 0.5f);
4210 						}
4211 					}
4212 				}
4213 				else
4214 				{ //otherwise we'll ramp up to 70 I guess, for both dual and staff
4215 					dmg = G_GetAttackDamage(self, 2, 70, 0.5f);
4216 				}
4217 			}
4218 			else if (self->client->ps.fd.saberAnimLevel == 3)
4219 			{
4220 				//new damage-ramping system
4221 				if (!saberInSpecial && !inBackAttack)
4222 				{
4223 					dmg = G_GetAttackDamage(self, 2, 120, 0.5f);
4224 				}
4225 				else if (saberInSpecial &&
4226 						(self->client->ps.saberMove == LS_A_JUMP_T__B_))
4227 				{
4228 					dmg = G_GetAttackDamage(self, 2, 180, 0.65f);
4229 				}
4230 				else if (inBackAttack)
4231 				{
4232 					dmg = G_GetAttackDamage(self, 2, 30, 0.5f); //can hit multiple times (and almost always does), so..
4233 				}
4234 				else
4235 				{
4236 					dmg = 100;
4237 				}
4238 			}
4239 			else if (self->client->ps.fd.saberAnimLevel == 2)
4240 			{
4241 				if (saberInSpecial &&
4242 					(self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH))
4243 				{ //a well-timed hit with this can do a full 85
4244 					dmg = G_GetAttackDamage(self, 2, 80, 0.5f);
4245 				}
4246 				else if (inBackAttack)
4247 				{
4248 					dmg = G_GetAttackDamage(self, 2, 25, 0.5f);
4249 				}
4250 				else
4251 				{
4252 					dmg = 60;
4253 				}
4254 			}
4255 			else if (self->client->ps.fd.saberAnimLevel == 1)
4256 			{
4257 				if (saberInSpecial &&
4258 					(self->client->ps.saberMove == LS_A_LUNGE))
4259 				{
4260 					dmg = G_GetAttackDamage(self, 2, SABER_HITDAMAGE-5, 0.3f);
4261 				}
4262 				else if (inBackAttack)
4263 				{
4264 					dmg = G_GetAttackDamage(self, 2, 30, 0.5f);
4265 				}
4266 				else
4267 				{
4268 					dmg = SABER_HITDAMAGE;
4269 				}
4270 			}
4271 
4272 			attackStr = self->client->ps.fd.saberAnimLevel;
4273 		}
4274 	}
4275 	else if (self->client->ps.saberAttackWound < level.time &&
4276 		self->client->ps.saberIdleWound < level.time)
4277 	{ //just touching, do minimal damage and only check for it every 200ms (mainly to cut down on network traffic for hit events)
4278 		if ( (self->client->saber[0].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4279 		{//no idle damage or effects
4280 			return qtrue;//true cause even though we didn't get a hit, we don't want to do those extra traces because the debounce time says not to.
4281 		}
4282 		trMask &= ~CONTENTS_LIGHTSABER;
4283 		if ( d_saberSPStyleDamage.integer )
4284 		{
4285 			if ( BG_SaberInReturn( self->client->ps.saberMove ) )
4286 			{
4287 				dmg = SABER_NONATTACK_DAMAGE;
4288 			}
4289 			else
4290 			{
4291 				if (d_saberSPStyleDamage.integer == 2)
4292 				{
4293 					dmg = SABER_NONATTACK_DAMAGE;
4294 				}
4295 				else
4296 				{
4297 					dmg = 0;
4298 				}
4299 			}
4300 		}
4301 		else
4302 		{
4303 			dmg = SABER_NONATTACK_DAMAGE;
4304 		}
4305 		idleDamage = qtrue;
4306 	}
4307 	else
4308 	{
4309 		return qtrue; //true cause even though we didn't get a hit, we don't want to do those extra traces because the debounce time says not to.
4310 	}
4311 
4312 	if (BG_SaberInSpecial(self->client->ps.saberMove))
4313 	{
4314 		qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove);
4315 
4316 		unblockable = qtrue;
4317 		self->client->ps.saberBlocked = 0;
4318 
4319 		if ( d_saberSPStyleDamage.integer )
4320 		{
4321 		}
4322 		else if (!inBackAttack)
4323 		{
4324 			if (self->client->ps.saberMove == LS_A_JUMP_T__B_)
4325 			{ //do extra damage for special unblockables
4326 				dmg += 5; //This is very tiny, because this move has a huge damage ramp
4327 			}
4328 			else if (self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH)
4329 			{
4330 				dmg += 5; //ditto
4331 				if (dmg <= 40 || G_GetAnimPoint(self) <= 0.4f)
4332 				{ //sort of a hack, don't want it doing big damage in the off points of the anim
4333 					dmg = 2;
4334 				}
4335 			}
4336 			else if (self->client->ps.saberMove == LS_A_LUNGE)
4337 			{
4338 				dmg += 2; //and ditto again
4339 				if (G_GetAnimPoint(self) <= 0.4f)
4340 				{ //same as above
4341 					dmg = 2;
4342 				}
4343 			}
4344 			else if (self->client->ps.saberMove == LS_SPINATTACK ||
4345 				self->client->ps.saberMove == LS_SPINATTACK_DUAL)
4346 			{ //do a constant significant amount of damage but ramp up a little to the mid-point
4347 				dmg = G_GetAttackDamage(self, 2, dmg+3, 0.5f);
4348 				dmg += 10;
4349 			}
4350 			else
4351 			{
4352 				//dmg += 20;
4353 				if ( BG_KickingAnim( self->client->ps.legsAnim ) ||
4354 					BG_KickingAnim( self->client->ps.torsoAnim ) )
4355 				{ //saber shouldn't do more than min dmg during kicks
4356 					dmg = 2;
4357 				}
4358 				else
4359 				{ //auto-ramp it I guess since it's a special we don't have a special case for.
4360 					dmg = G_GetAttackDamage(self, 5, dmg+5, 0.5f);
4361 				}
4362 			}
4363 		}
4364 	}
4365 
4366 	if (!dmg)
4367 	{
4368 		if (tr.entityNum < MAX_CLIENTS ||
4369 			(g_entities[tr.entityNum].inuse && (g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER)))
4370 		{
4371 			return qtrue;
4372 		}
4373 		return qfalse;
4374 	}
4375 
4376 	if (dmg > SABER_NONATTACK_DAMAGE)
4377 	{
4378 		dmg *= g_saberDamageScale.value;
4379 
4380 		//see if this specific saber has a damagescale
4381 		if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
4382 			&& self->client->saber[rSaberNum].damageScale != 1.0f )
4383 		{
4384 			dmg = ceil( (float)dmg*self->client->saber[rSaberNum].damageScale );
4385 		}
4386 		else if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
4387 			&& self->client->saber[rSaberNum].damageScale2 != 1.0f )
4388 		{
4389 			dmg = ceil( (float)dmg*self->client->saber[rSaberNum].damageScale2 );
4390 		}
4391 
4392 		if ((self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) ||
4393 			(self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
4394 		{ //weaken it if an arm is broken
4395 			dmg *= 0.3;
4396 			if (dmg <= SABER_NONATTACK_DAMAGE)
4397 			{
4398 				dmg = SABER_NONATTACK_DAMAGE+1;
4399 			}
4400 		}
4401 	}
4402 
4403 	if (dmg > SABER_NONATTACK_DAMAGE && self->client->ps.isJediMaster)
4404 	{ //give the Jedi Master more saber attack power
4405 		dmg *= 2;
4406 	}
4407 
4408 	if (dmg > SABER_NONATTACK_DAMAGE && level.gametype == GT_SIEGE &&
4409 		self->client->siegeClass != -1 && (bgSiegeClasses[self->client->siegeClass].classflags & (1<<CFL_MORESABERDMG)))
4410 	{ //this class is flagged to do extra saber damage. I guess 2x will do for now.
4411 		dmg *= 2;
4412 	}
4413 
4414 	if (level.gametype == GT_POWERDUEL &&
4415 		self->client->sess.duelTeam == DUELTEAM_LONE)
4416 	{ //always x2 when we're powerdueling alone... er, so, we apparently no longer want this?  So they say.
4417 		if ( duel_fraglimit.integer )
4418 		{
4419 			//dmg *= 1.5 - (.4 * (float)self->client->sess.wins / (float)duel_fraglimit.integer);
4420 
4421 		}
4422 		//dmg *= 2;
4423 	}
4424 
4425 #ifndef FINAL_BUILD
4426 	if (g_saberDebugPrint.integer > 2 && dmg > 1)
4427 	{
4428 		Com_Printf("CL %i SABER DMG: %i\n", self->s.number, dmg);
4429 	}
4430 #endif
4431 
4432 	VectorSubtract(saberEnd, saberStart, dir);
4433 	VectorNormalize(dir);
4434 
4435 	if (tr.entityNum == ENTITYNUM_WORLD ||
4436 		g_entities[tr.entityNum].s.eType == ET_TERRAIN)
4437 	{ //register this as a wall hit for jedi AI
4438         self->client->ps.saberEventFlags |= SEF_HITWALL;
4439 		saberHitWall = qtrue;
4440 	}
4441 
4442 	if (saberHitWall
4443 		&& (self->client->saber[rSaberNum].saberFlags & SFL_BOUNCE_ON_WALLS)
4444 		&& (BG_SaberInAttackPure( self->client->ps.saberMove ) //only in a normal attack anim
4445 			|| self->client->ps.saberMove == LS_A_JUMP_T__B_ ) //or in the strong jump-fwd-attack "death from above" move
4446 		)
4447 	{ //then bounce off
4448 		/*
4449 		qboolean onlyIfNotSpecial = qfalse;
4450 		qboolean skipIt = qfalse;
4451 		if (tr.plane.normal[2] >= 0.8f ||
4452 			tr.plane.normal[2] <= -0.8f ||
4453 			VectorCompare(tr.plane.normal, vec3_origin))
4454 		{
4455 			if ((tr.plane.normal[2] >= 0.8f || VectorCompare(tr.plane.normal, vec3_origin)) &&
4456 				self->client->ps.viewangles[PITCH] <= 30.0f &&
4457 				self->client->pers.cmd.upmove >= 0)
4458 			{ //don't hit the ground if we are not looking down a lot/crouched
4459 				skipIt = qtrue;
4460 			}
4461 			else
4462 			{
4463 				onlyIfNotSpecial = qtrue;
4464 			}
4465 		}
4466 		if (!skipIt && (!onlyIfNotSpecial || !BG_SaberInSpecial(self->client->ps.saberMove)))
4467 		*/
4468 		{
4469 			gentity_t *te = NULL;
4470 			/*
4471 			qboolean pre = saberDoClashEffect;
4472 
4473 			VectorCopy( tr.endpos, saberClashPos );
4474 			VectorCopy( tr.plane.normal, saberClashNorm );
4475 			saberClashEventParm = 1;
4476 			saberDoClashEffect = qtrue;
4477 			WP_SaberDoClash( self, rSaberNum, rBladeNum );
4478 			saberDoClashEffect = pre;
4479 			*/
4480 
4481 			self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove);
4482 			self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
4483 			if (self->client->ps.torsoAnim == self->client->ps.legsAnim)
4484 			{ //set anim now on both parts
4485 				int anim = saberMoveData[self->client->ps.saberMove].animToUse;
4486 				G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
4487 			}
4488 
4489 			//do bounce sound & force feedback
4490 			WP_SaberBounceSound( self, rSaberNum, rBladeNum );
4491 			//do hit effect
4492 			te = G_TempEntity( tr.endpos, EV_SABER_HIT );
4493 			te->s.otherEntityNum = ENTITYNUM_NONE;//we didn't hit anyone in particular
4494 			te->s.otherEntityNum2 = self->s.number;//send this so it knows who we are
4495 			te->s.weapon = rSaberNum;
4496 			te->s.legsAnim = rBladeNum;
4497 			VectorCopy(tr.endpos, te->s.origin);
4498 			VectorCopy(tr.plane.normal, te->s.angles);
4499 			if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
4500 			{ //don't let it play with no direction
4501 				te->s.angles[1] = 1;
4502 			}
4503 			//do radius damage/knockback, if any
4504 			if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum ) )
4505 			{
4506 				WP_SaberRadiusDamage( self, tr.endpos, self->client->saber[rSaberNum].splashRadius, self->client->saber[rSaberNum].splashDamage, self->client->saber[rSaberNum].splashKnockback );
4507 			}
4508 			else
4509 			{
4510 				WP_SaberRadiusDamage( self, tr.endpos, self->client->saber[rSaberNum].splashRadius2, self->client->saber[rSaberNum].splashDamage2, self->client->saber[rSaberNum].splashKnockback2 );
4511 			}
4512 
4513 			return qtrue;
4514 		}
4515 	}
4516 
4517 	//rww - I'm saying || tr.startsolid here, because otherwise your saber tends to skip positions and go through
4518 	//people, and the compensation traces start in their bbox too. Which results in the saber passing through people
4519 	//when you visually cut right through them. Which sucks.
4520 
4521 	if ((tr.fraction != 1 || tr.startsolid) &&
4522 		g_entities[tr.entityNum].takedamage &&
4523 		(g_entities[tr.entityNum].health > 0 || !(g_entities[tr.entityNum].s.eFlags & EF_DISINTEGRATION)) &&
4524 		tr.entityNum != self->s.number &&
4525 		g_entities[tr.entityNum].inuse)
4526 	{//hit something that had health and takes damage
4527 		if (idleDamage &&
4528 			g_entities[tr.entityNum].client &&
4529 			OnSameTeam(self, &g_entities[tr.entityNum]) &&
4530 			!g_friendlySaber.integer)
4531 		{
4532 			return qfalse;
4533 		}
4534 
4535 		if (g_entities[tr.entityNum].client &&
4536 			g_entities[tr.entityNum].client->ps.duelInProgress &&
4537 			g_entities[tr.entityNum].client->ps.duelIndex != self->s.number)
4538 		{
4539 			return qfalse;
4540 		}
4541 
4542 		if (g_entities[tr.entityNum].client &&
4543 			self->client->ps.duelInProgress &&
4544 			self->client->ps.duelIndex != g_entities[tr.entityNum].s.number)
4545 		{
4546 			return qfalse;
4547 		}
4548 
4549 		if ( BG_StabDownAnim( self->client->ps.torsoAnim )
4550 			&& g_entities[tr.entityNum].client
4551 			&& !BG_InKnockDownOnGround( &g_entities[tr.entityNum].client->ps ) )
4552 		{//stabdowns only damage people who are actually on the ground...
4553 			return qfalse;
4554 		}
4555 		self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
4556 
4557 		didHit = qtrue;
4558 
4559 		if ( !d_saberSPStyleDamage.integer//let's trying making blocks have to be blocked by a saber
4560 			&& g_entities[tr.entityNum].client
4561 			&& !unblockable
4562 			&& WP_SaberCanBlock(&g_entities[tr.entityNum], tr.endpos, 0, MOD_SABER, qfalse, attackStr))
4563 		{//hit a client who blocked the attack (fake: didn't actually hit their saber)
4564 			if (dmg <= SABER_NONATTACK_DAMAGE)
4565 			{
4566 				self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
4567 			}
4568 			saberDoClashEffect = qtrue;
4569 			VectorCopy( tr.endpos, saberClashPos );
4570 			VectorCopy( tr.plane.normal, saberClashNorm );
4571 			saberClashEventParm = 1;
4572 
4573 			if (dmg > SABER_NONATTACK_DAMAGE)
4574 			{
4575 				int lockFactor = g_saberLockFactor.integer;
4576 
4577 				if ((g_entities[tr.entityNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] - self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) > 1 &&
4578 					Q_irand(1, 10) < lockFactor*2)
4579 				{ //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)
4580 					if (!G_ClientIdleInWorld(&g_entities[tr.entityNum]))
4581 					{
4582 						if ( (trMask&CONTENTS_LIGHTSABER)
4583 							&& WP_SabersCheckLock(self, &g_entities[tr.entityNum]))
4584 						{
4585 							self->client->ps.saberBlocked = BLOCKED_NONE;
4586 							g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE;
4587 							return didHit;
4588 						}
4589 					}
4590 				}
4591 				else if (Q_irand(1, 20) < lockFactor)
4592 				{
4593 					if (!G_ClientIdleInWorld(&g_entities[tr.entityNum]))
4594 					{
4595 						if ((trMask&CONTENTS_LIGHTSABER)
4596 							&& WP_SabersCheckLock(self, &g_entities[tr.entityNum]))
4597 						{
4598 							self->client->ps.saberBlocked = BLOCKED_NONE;
4599 							g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE;
4600 							return didHit;
4601 						}
4602 					}
4603 				}
4604 			}
4605 			otherOwner = &g_entities[tr.entityNum];
4606 			goto blockStuff;
4607 		}
4608 		else
4609 		{//damage the thing we hit
4610 			qboolean doDismemberment = qfalse;
4611 			int	knockbackFlags = 0;
4612 
4613 			if (g_entities[tr.entityNum].client)
4614 			{ //not a "jedi", so make them suffer more
4615 				if ( dmg > SABER_NONATTACK_DAMAGE )
4616 				{ //don't bother increasing just for idle touch damage
4617 					dmg *= 1.5;
4618 				}
4619 			}
4620 			/*
4621 			if (g_entities[tr.entityNum].client
4622 				&& g_entities[tr.entityNum].client->ps.weapon != WP_SABER )//fd.forcePowerLevel[FP_SABER_OFFENSE])
4623 			{ //not a "jedi", so make them suffer more
4624 				if ( dmg > SABER_NONATTACK_DAMAGE )
4625 				{ //don't bother increasing just for idle touch damage
4626 					dmg *= 1.5;
4627 				}
4628 			}
4629 			*/
4630 
4631 			if ( !d_saberSPStyleDamage.integer )
4632 			{
4633 				if (g_entities[tr.entityNum].client && g_entities[tr.entityNum].client->ps.weapon == WP_SABER)
4634 				{ //for jedi using the saber, half the damage (this comes with the increased default dmg debounce time)
4635 					if (level.gametype != GT_SIEGE)
4636 					{ //unless siege..
4637 						if (dmg > SABER_NONATTACK_DAMAGE && !unblockable)
4638 						{ //don't reduce damage if it's only 1, or if this is an unblockable attack
4639 							if (dmg == SABER_HITDAMAGE)
4640 							{ //level 1 attack
4641 								dmg *= 0.7;
4642 							}
4643 							else
4644 							{
4645 								dmg *= 0.5;
4646 							}
4647 						}
4648 					}
4649 				}
4650 			}
4651 
4652 			if (self->s.eType == ET_NPC &&
4653 				g_entities[tr.entityNum].client &&
4654 				self->client->playerTeam == g_entities[tr.entityNum].client->playerTeam)
4655 			{ //Oops. Since he's an NPC, we'll be forgiving and cut the damage down.
4656 				dmg *= 0.2f;
4657 			}
4658 
4659 			//store the damage, we'll apply it later
4660 			if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
4661 				&& !(self->client->saber[rSaberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
4662 			{
4663 				doDismemberment = qtrue;
4664 			}
4665 			if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
4666 				&& !(self->client->saber[rSaberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
4667 			{
4668 				doDismemberment = qtrue;
4669 			}
4670 
4671 			if ( !WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
4672 				&& self->client->saber[rSaberNum].knockbackScale > 0.0f )
4673 			{
4674 				if ( rSaberNum < 1 )
4675 				{
4676 					knockbackFlags = DAMAGE_SABER_KNOCKBACK1;
4677 				}
4678 				else
4679 				{
4680 					knockbackFlags = DAMAGE_SABER_KNOCKBACK2;
4681 				}
4682 			}
4683 
4684 			if ( WP_SaberBladeUseSecondBladeStyle( &self->client->saber[rSaberNum], rBladeNum )
4685 				&& self->client->saber[rSaberNum].knockbackScale > 0.0f )
4686 			{
4687 				if ( rSaberNum < 1 )
4688 				{
4689 					knockbackFlags = DAMAGE_SABER_KNOCKBACK1_B2;
4690 				}
4691 				else
4692 				{
4693 					knockbackFlags = DAMAGE_SABER_KNOCKBACK2_B2;
4694 				}
4695 			}
4696 
4697 			WP_SaberDamageAdd( tr.entityNum, dir, tr.endpos, dmg, doDismemberment, knockbackFlags );
4698 
4699 			if (g_entities[tr.entityNum].client)
4700 			{
4701 				//Let jedi AI know if it hit an enemy
4702 				if ( self->enemy && self->enemy == &g_entities[tr.entityNum] )
4703 				{
4704 					self->client->ps.saberEventFlags |= SEF_HITENEMY;
4705 				}
4706 				else
4707 				{
4708                     self->client->ps.saberEventFlags |= SEF_HITOBJECT;
4709 				}
4710 			}
4711 
4712 			if ( d_saberSPStyleDamage.integer )
4713 			{
4714 			}
4715 			else
4716 			{
4717 				self->client->ps.saberAttackWound = level.time + 100;
4718 			}
4719 		}
4720 	}
4721 	else if ((tr.fraction != 1 || tr.startsolid) &&
4722 		(g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER) &&
4723 		g_entities[tr.entityNum].r.contents != -1 &&
4724 		g_entities[tr.entityNum].inuse)
4725 	{ //saber clash
4726 		otherOwner = &g_entities[g_entities[tr.entityNum].r.ownerNum];
4727 
4728 		if (!otherOwner->inuse || !otherOwner->client)
4729 		{
4730 			return qfalse;
4731 		}
4732 
4733 		if ( otherOwner
4734 			&& otherOwner->client
4735 			&& otherOwner->client->ps.saberInFlight )
4736 		{//don't do extra collision checking vs sabers in air
4737 		}
4738 		else
4739 		{//hit an in-hand saber, do extra collision check against it
4740 			if ( d_saberSPStyleDamage.integer )
4741 			{//use SP-style blade-collision test
4742 				if ( !WP_SabersIntersect( self, rSaberNum, rBladeNum, otherOwner, qfalse ) )
4743 				{//sabers did not actually intersect
4744 					return qfalse;
4745 				}
4746 			}
4747 			else
4748 			{//MP-style
4749 				if (!G_SaberCollide(self, otherOwner, lastValidStart,
4750 					lastValidEnd, saberTrMins, saberTrMaxs, tr.endpos))
4751 				{ //detailed collision did not produce results...
4752 					return qfalse;
4753 				}
4754 			}
4755 		}
4756 
4757 		if (OnSameTeam(self, otherOwner) &&
4758 			!g_friendlySaber.integer)
4759 		{
4760 			return qfalse;
4761 		}
4762 
4763 		if ((self->s.eType == ET_NPC || otherOwner->s.eType == ET_NPC) && //just make sure one of us is an npc
4764 			self->client->playerTeam == otherOwner->client->playerTeam &&
4765 			level.gametype != GT_SIEGE)
4766 		{ //don't hit your teammate's sabers if you are an NPC. It can be rather annoying.
4767 			return qfalse;
4768 		}
4769 
4770 		if (otherOwner->client->ps.duelInProgress &&
4771 			otherOwner->client->ps.duelIndex != self->s.number)
4772 		{
4773 			return qfalse;
4774 		}
4775 
4776 		if (self->client->ps.duelInProgress &&
4777 			self->client->ps.duelIndex != otherOwner->s.number)
4778 		{
4779 			return qfalse;
4780 		}
4781 
4782 		if ( g_debugSaberLocks.integer )
4783 		{
4784 			WP_SabersCheckLock2( self, otherOwner, LOCK_RANDOM );
4785 			return qtrue;
4786 		}
4787 		didHit = qtrue;
4788 		self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
4789 
4790 		if (dmg <= SABER_NONATTACK_DAMAGE)
4791 		{
4792 			self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer;
4793 		}
4794 
4795 		saberDoClashEffect = qtrue;
4796 		VectorCopy( tr.endpos, saberClashPos );
4797 		VectorCopy( tr.plane.normal, saberClashNorm );
4798 		saberClashEventParm = 1;
4799 
4800 		sabersClashed = qtrue;
4801 		saberHitSaber = qtrue;
4802 		saberHitFraction = tr.fraction;
4803 
4804 		if (saberCheckKnockdown_Smashed(&g_entities[tr.entityNum], otherOwner, self, dmg))
4805 		{ //smashed it out of the air
4806 			return qfalse;
4807 		}
4808 
4809 		//is this my thrown saber?
4810 		if ( self->client->ps.saberEntityNum
4811 			&& self->client->ps.saberInFlight
4812 			&& rSaberNum == 0
4813 			&& saberCheckKnockdown_Smashed( &g_entities[self->client->ps.saberEntityNum], self, otherOwner, dmg))
4814 		{ //they smashed it out of the air
4815 			return qfalse;
4816 		}
4817 
4818 blockStuff:
4819 		otherUnblockable = qfalse;
4820 
4821 		if (otherOwner && otherOwner->client && otherOwner->client->ps.saberInFlight)
4822 		{
4823 			return qfalse;
4824 		}
4825 
4826 		//this is a thrown saber, don't do any fancy saber-saber collision stuff
4827 		if ( self->client->ps.saberEntityNum
4828 			&& self->client->ps.saberInFlight
4829 			&& rSaberNum == 0 )
4830 		{
4831 			return qfalse;
4832 		}
4833 
4834 		otherSaberLevel = G_SaberAttackPower(otherOwner, SaberAttacking(otherOwner));
4835 
4836 		if (dmg > SABER_NONATTACK_DAMAGE && !unblockable && !otherUnblockable)
4837 		{
4838 			int lockFactor = g_saberLockFactor.integer;
4839 
4840 			if (sabersClashed && Q_irand(1, 20) <= lockFactor)
4841 			{
4842 				if (!G_ClientIdleInWorld(otherOwner))
4843 				{
4844 					if (WP_SabersCheckLock(self, otherOwner))
4845 					{
4846 						self->client->ps.saberBlocked = BLOCKED_NONE;
4847 						otherOwner->client->ps.saberBlocked = BLOCKED_NONE;
4848 						return didHit;
4849 					}
4850 				}
4851 			}
4852 		}
4853 
4854 		if (!otherOwner || !otherOwner->client)
4855 		{
4856 			return didHit;
4857 		}
4858 
4859 		if (BG_SaberInSpecial(otherOwner->client->ps.saberMove))
4860 		{
4861 			otherUnblockable = qtrue;
4862 			otherOwner->client->ps.saberBlocked = 0;
4863 		}
4864 
4865 		if ( sabersClashed &&
4866 			dmg > SABER_NONATTACK_DAMAGE &&
4867 			 selfSaberLevel < FORCE_LEVEL_3 &&
4868 			 !PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
4869 			 !PM_SaberInParry(self->client->ps.saberMove) &&
4870 			 !PM_SaberInBrokenParry(self->client->ps.saberMove) &&
4871 			 !BG_SaberInSpecial(self->client->ps.saberMove) &&
4872 			 !PM_SaberInBounce(self->client->ps.saberMove) &&
4873 			 !PM_SaberInDeflect(self->client->ps.saberMove) &&
4874 			 !PM_SaberInReflect(self->client->ps.saberMove) &&
4875 			 !unblockable )
4876 		{
4877 			//if (Q_irand(1, 10) <= 6)
4878 			if (1) //for now, just always try a deflect. (deflect func can cause bounces too)
4879 			{
4880 				if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction))
4881 				{
4882 					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
4883 				}
4884 				else
4885 				{
4886 					self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
4887 					didOffense = qtrue;
4888 				}
4889 			}
4890 			else
4891 			{
4892 				self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
4893 				didOffense = qtrue;
4894 
4895 #ifndef FINAL_BUILD
4896 				if (g_saberDebugPrint.integer)
4897 				{
4898 					Com_Printf("Client %i clashed into client %i's saber, did BLOCKED_ATK_BOUNCE\n", self->s.number, otherOwner->s.number);
4899 				}
4900 #endif
4901 			}
4902 		}
4903 
4904 		if ( ((selfSaberLevel < FORCE_LEVEL_3 && ((tryDeflectAgain && Q_irand(1, 10) <= 3) || (!tryDeflectAgain && Q_irand(1, 10) <= 7))) || (Q_irand(1, 10) <= 1 && otherSaberLevel >= FORCE_LEVEL_3))
4905 			&& !PM_SaberInBounce(self->client->ps.saberMove)
4906 
4907 			&& !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove)
4908 			&& !BG_SaberInSpecial(otherOwner->client->ps.saberMove)
4909 			&& !PM_SaberInBounce(otherOwner->client->ps.saberMove)
4910 			&& !PM_SaberInDeflect(otherOwner->client->ps.saberMove)
4911 			&& !PM_SaberInReflect(otherOwner->client->ps.saberMove)
4912 
4913 			&& (otherSaberLevel > FORCE_LEVEL_2 || ( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= 3 && Q_irand(0, otherSaberLevel) ))
4914 			&& !unblockable
4915 			&& !otherUnblockable
4916 			&& dmg > SABER_NONATTACK_DAMAGE
4917 			&& !didOffense) //don't allow the person we're attacking to do this if we're making an unblockable attack
4918 		{//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.
4919 			//Going to go ahead and let idle damage do simple knockaways. Looks sort of good that way.
4920 			//turn the parry into a knockaway
4921 			if (self->client->ps.saberEntityNum) //make sure he has his saber still
4922 			{
4923 				saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner);
4924 			}
4925 
4926 			if (!PM_SaberInParry(otherOwner->client->ps.saberMove))
4927 			{
4928 				WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
4929 				otherOwner->client->ps.saberMove = BG_KnockawayForParry( otherOwner->client->ps.saberBlocked );
4930 				otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
4931 			}
4932 			else
4933 			{
4934 				otherOwner->client->ps.saberMove = G_KnockawayForParry(otherOwner->client->ps.saberMove); //BG_KnockawayForParry( otherOwner->client->ps.saberBlocked );
4935 				otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
4936 			}
4937 
4938 			//make them (me) go into a broken parry
4939 			self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove );
4940 			self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
4941 
4942 #ifndef FINAL_BUILD
4943 			if (g_saberDebugPrint.integer)
4944 			{
4945 				Com_Printf("Client %i sent client %i into a reflected attack with a knockaway\n", otherOwner->s.number, self->s.number);
4946 			}
4947 #endif
4948 
4949 			didDefense = qtrue;
4950 		}
4951 		else if ((selfSaberLevel > FORCE_LEVEL_2 || unblockable) && //if we're doing a special attack, we can send them into a broken parry too (MP only)
4952 				 ( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < selfSaberLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == selfSaberLevel && (Q_irand(1, 10) >= otherSaberLevel*1.5 || unblockable)) ) &&
4953 				 PM_SaberInParry(otherOwner->client->ps.saberMove) &&
4954 				 !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
4955 				 !PM_SaberInParry(self->client->ps.saberMove) &&
4956 				 !PM_SaberInBrokenParry(self->client->ps.saberMove) &&
4957 				 !PM_SaberInBounce(self->client->ps.saberMove) &&
4958 				 dmg > SABER_NONATTACK_DAMAGE &&
4959 				 !didOffense &&
4960 				 !otherUnblockable)
4961 		{ //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.
4962 			if (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still
4963 			{
4964 				saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self);
4965 			}
4966 
4967 #ifndef FINAL_BUILD
4968 			if (g_saberDebugPrint.integer)
4969 			{
4970 				Com_Printf("Client %i sent client %i into a broken parry\n", self->s.number, otherOwner->s.number);
4971 			}
4972 #endif
4973 
4974 			otherOwner->client->ps.saberMove = BG_BrokenParryForParry( otherOwner->client->ps.saberMove );
4975 			otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
4976 
4977 			didDefense = qtrue;
4978 		}
4979 		else if ((selfSaberLevel > FORCE_LEVEL_2) && //if we're doing a special attack, we can send them into a broken parry too (MP only)
4980 				 //( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < selfSaberLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == selfSaberLevel && (Q_irand(1, 10) >= otherSaberLevel*3 || unblockable)) ) &&
4981 				 otherSaberLevel >= FORCE_LEVEL_3 &&
4982 				 PM_SaberInParry(otherOwner->client->ps.saberMove) &&
4983 				 !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
4984 				 !PM_SaberInParry(self->client->ps.saberMove) &&
4985 				 !PM_SaberInBrokenParry(self->client->ps.saberMove) &&
4986 				 !PM_SaberInBounce(self->client->ps.saberMove) &&
4987 				 !PM_SaberInDeflect(self->client->ps.saberMove) &&
4988 				 !PM_SaberInReflect(self->client->ps.saberMove) &&
4989 				 dmg > SABER_NONATTACK_DAMAGE &&
4990 				 !didOffense &&
4991 				 !unblockable)
4992 		{ //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.
4993 #ifndef FINAL_BUILD
4994 			if (g_saberDebugPrint.integer)
4995 			{
4996 				Com_Printf("Client %i bounced off of client %i's saber\n", self->s.number, otherOwner->s.number);
4997 			}
4998 #endif
4999 
5000 			if (!tryDeflectAgain)
5001 			{
5002 				if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction))
5003 				{
5004 					tryDeflectAgain = qtrue;
5005 				}
5006 			}
5007 
5008 			didOffense = qtrue;
5009 		}
5010 		else if (SaberAttacking(otherOwner) && dmg > SABER_NONATTACK_DAMAGE && !BG_SaberInSpecial(otherOwner->client->ps.saberMove) && !didOffense && !otherUnblockable)
5011 		{ //they were attacking and our saber hit their saber, make them bounce. But if they're in a special attack, leave them.
5012 			if (!PM_SaberInBounce(self->client->ps.saberMove) &&
5013 				!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
5014 				!PM_SaberInDeflect(self->client->ps.saberMove) &&
5015 				!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
5016 
5017 				!PM_SaberInReflect(self->client->ps.saberMove) &&
5018 				!PM_SaberInReflect(otherOwner->client->ps.saberMove))
5019 			{
5020 				int attackAdv, defendStr = G_PowerLevelForSaberAnim( otherOwner, 0, qtrue ), attackBonus = 0;
5021 				if ( otherOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL
5022 					|| otherOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL
5023 					|| otherOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL )
5024 				{//parry/block/break-parry bonus for single-style kata moves
5025 					defendStr++;
5026 				}
5027 				defendStr += Q_irand(0, otherOwner->client->saber[0].parryBonus );
5028 				if ( otherOwner->client->saber[1].model[0]
5029 					&& !otherOwner->client->ps.saberHolstered )
5030 				{
5031 					defendStr += Q_irand(0, otherOwner->client->saber[1].parryBonus );
5032 				}
5033 
5034 #ifndef FINAL_BUILD
5035 				if (g_saberDebugPrint.integer)
5036 				{
5037 					Com_Printf("Client %i and client %i bounced off of each other's sabers\n", self->s.number, otherOwner->s.number);
5038 				}
5039 #endif
5040 
5041 				attackBonus = Q_irand(0, self->client->saber[0].breakParryBonus );
5042 				if ( self->client->saber[1].model[0]
5043 					&& !self->client->ps.saberHolstered )
5044 				{
5045 					attackBonus += Q_irand(0, self->client->saber[1].breakParryBonus );
5046 				}
5047 				attackAdv = (attackStr+attackBonus+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])-(defendStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]);
5048 
5049 				if ( attackAdv > 1 )
5050 				{//I won, he should knockaway
5051 					otherOwner->client->ps.saberMove = BG_BrokenParryForAttack( otherOwner->client->ps.saberMove );
5052 					otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
5053 				}
5054 				else if ( attackAdv > 0 )
5055 				{//I won, he should bounce, I should continue
5056 					otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5057 				}
5058 				else if ( attackAdv < 1 )
5059 				{//I lost, I get knocked away
5060 					self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove );
5061 					self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
5062 				}
5063 				else if ( attackAdv < 0 )
5064 				{//I lost, I bounce off
5065 					self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5066 				}
5067 				else
5068 				{//even, both bounce off
5069 					self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5070 					otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5071 				}
5072 
5073 				didOffense = qtrue;
5074 			}
5075 		}
5076 
5077 		if (d_saberGhoul2Collision.integer && !didDefense && dmg <= SABER_NONATTACK_DAMAGE && !otherUnblockable) //with perpoly, it looks pretty weird to have clash flares coming off the guy's face and whatnot
5078 		{
5079 			if (!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
5080 				!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
5081 				!BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
5082 				!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
5083 				!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
5084 				!PM_SaberInReflect(otherOwner->client->ps.saberMove))
5085 			{
5086 				WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
5087 				otherOwner->client->ps.saberEventFlags |= SEF_PARRIED;
5088 			}
5089 		}
5090 		else if (!didDefense && dmg > SABER_NONATTACK_DAMAGE && !otherUnblockable) //if not more than idle damage, don't even bother blocking.
5091 		{ //block
5092 			if (!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
5093 				!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
5094 				!BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
5095 				!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
5096 				!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
5097 				!PM_SaberInReflect(otherOwner->client->ps.saberMove))
5098 			{
5099 				qboolean crushTheParry = qfalse;
5100 
5101 				if (unblockable)
5102 				{ //It's unblockable. So send us into a broken parry immediately.
5103 					crushTheParry = qtrue;
5104 				}
5105 
5106 				if (!SaberAttacking(otherOwner))
5107 				{
5108 					int otherIdleStr = otherOwner->client->ps.fd.saberAnimLevel;
5109 					if ( otherIdleStr == SS_DUAL
5110 						|| otherIdleStr == SS_STAFF )
5111 					{
5112 						otherIdleStr = SS_MEDIUM;
5113 					}
5114 
5115 					WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
5116 					otherOwner->client->ps.saberEventFlags |= SEF_PARRIED;
5117 					self->client->ps.saberEventFlags |= SEF_BLOCKED;
5118 
5119 					if ( attackStr+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > otherIdleStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] )
5120 					{
5121 						crushTheParry = qtrue;
5122 					}
5123 					else
5124 					{
5125 						tryDeflectAgain = qtrue;
5126 					}
5127 				}
5128 				else if (selfSaberLevel > otherSaberLevel ||
5129 					(selfSaberLevel == otherSaberLevel && Q_irand(1, 10) <= 2))
5130 				{ //they are attacking, and we managed to make them break
5131 					//Give them a parry, so we can later break it.
5132 					WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse);
5133 					crushTheParry = qtrue;
5134 
5135 					if (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still
5136 					{
5137 						saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self);
5138 					}
5139 
5140 #ifndef FINAL_BUILD
5141 					if (g_saberDebugPrint.integer)
5142 					{
5143 						Com_Printf("Client %i forced client %i into a broken parry with a stronger attack\n", self->s.number, otherOwner->s.number);
5144 					}
5145 #endif
5146 				}
5147 				else
5148 				{ //They are attacking, so are we, and obviously they have an attack level higher than or equal to ours
5149 					if (selfSaberLevel == otherSaberLevel)
5150 					{ //equal level, try to bounce off each other's sabers
5151 						if (!didOffense &&
5152 							!PM_SaberInParry(self->client->ps.saberMove) &&
5153 							!PM_SaberInBrokenParry(self->client->ps.saberMove) &&
5154 							!BG_SaberInSpecial(self->client->ps.saberMove) &&
5155 							!PM_SaberInBounce(self->client->ps.saberMove) &&
5156 							!PM_SaberInDeflect(self->client->ps.saberMove) &&
5157 							!PM_SaberInReflect(self->client->ps.saberMove) &&
5158 							!unblockable)
5159 						{
5160 							self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5161 							didOffense = qtrue;
5162 						}
5163 						if (!didDefense &&
5164 							!PM_SaberInParry(otherOwner->client->ps.saberMove) &&
5165 							!PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) &&
5166 							!BG_SaberInSpecial(otherOwner->client->ps.saberMove) &&
5167 							!PM_SaberInBounce(otherOwner->client->ps.saberMove) &&
5168 							!PM_SaberInDeflect(otherOwner->client->ps.saberMove) &&
5169 							!PM_SaberInReflect(otherOwner->client->ps.saberMove) &&
5170 							!unblockable)
5171 						{
5172 							otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5173 						}
5174 #ifndef FINAL_BUILD
5175 						if (g_saberDebugPrint.integer)
5176 						{
5177 							Com_Printf("Equal attack level bounce/deflection for clients %i and %i\n", self->s.number, otherOwner->s.number);
5178 						}
5179 #endif
5180 
5181 						self->client->ps.saberEventFlags |= SEF_DEFLECTED;
5182 						otherOwner->client->ps.saberEventFlags |= SEF_DEFLECTED;
5183 					}
5184 					else if ((level.time - otherOwner->client->lastSaberStorageTime) < 500 && !unblockable) //make sure the stored saber data is updated
5185 					{ //They are higher, this means they can actually smash us into a broken parry
5186 						//Using reflected anims instead now
5187 						self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove);
5188 						self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5189 
5190 						if (self->client->ps.saberEntityNum) //make sure he has his saber still
5191 						{
5192 							saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner);
5193 						}
5194 
5195 #ifndef FINAL_BUILD
5196 						if (g_saberDebugPrint.integer)
5197 						{
5198 							Com_Printf("Client %i hit client %i's stronger attack, was forced into a broken parry\n", self->s.number, otherOwner->s.number);
5199 						}
5200 #endif
5201 
5202 						otherOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
5203 
5204 						didOffense = qtrue;
5205 					}
5206 				}
5207 
5208 				if (crushTheParry && PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked)))
5209 				{ //This means that the attack actually hit our saber, and we went to block it.
5210 				  //But, one of the above cases says we actually can't. So we will be smashed into a broken parry instead.
5211 					otherOwner->client->ps.saberMove = BG_BrokenParryForParry( G_GetParryForBlock(otherOwner->client->ps.saberBlocked) );
5212 					otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5213 
5214 					otherOwner->client->ps.saberEventFlags &= ~SEF_PARRIED;
5215 					self->client->ps.saberEventFlags &= ~SEF_BLOCKED;
5216 
5217 #ifndef FINAL_BUILD
5218 					if (g_saberDebugPrint.integer)
5219 					{
5220 						Com_Printf("Client %i broke through %i's parry with a special or stronger attack\n", self->s.number, otherOwner->s.number);
5221 					}
5222 #endif
5223 				}
5224 				else if (PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked)) && !didOffense && tryDeflectAgain)
5225 				{ //We want to try deflecting again because the other is in the parry and we haven't made any new moves
5226 					int preMove = otherOwner->client->ps.saberMove;
5227 
5228 					otherOwner->client->ps.saberMove = G_GetParryForBlock(otherOwner->client->ps.saberBlocked);
5229 					WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction);
5230 					otherOwner->client->ps.saberMove = preMove;
5231 				}
5232 			}
5233 		}
5234 
5235 		self->client->ps.saberAttackWound = level.time + g_saberDmgDelay_Wound.integer;
5236 	}
5237 
5238 	return didHit;
5239 }
5240 
5241 #define MAX_SABER_SWING_INC 0.33f
G_SPSaberDamageTraceLerped(gentity_t * self,int saberNum,int bladeNum,vec3_t baseNew,vec3_t endNew,int clipmask)5242 void G_SPSaberDamageTraceLerped( gentity_t *self, int saberNum, int bladeNum, vec3_t baseNew, vec3_t endNew, int clipmask )
5243 {
5244 	vec3_t baseOld, endOld;
5245 	vec3_t mp1, mp2;
5246 	vec3_t md1, md2;
5247 
5248 	if ( (level.time-self->client->saber[saberNum].blade[bladeNum].trail.lastTime) > 100 )
5249 	{//no valid last pos, use current
5250 		VectorCopy(baseNew, baseOld);
5251 		VectorCopy(endNew, endOld);
5252 	}
5253 	else
5254 	{//trace from last pos
5255 		VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.base, baseOld );
5256 		VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.tip, endOld );
5257 	}
5258 
5259 	VectorCopy( baseOld, mp1 );
5260 	VectorCopy( baseNew, mp2 );
5261 	VectorSubtract( endOld, baseOld, md1 );
5262 	VectorNormalize( md1 );
5263 	VectorSubtract( endNew, baseNew, md2 );
5264 	VectorNormalize( md2 );
5265 
5266 	saberHitWall = qfalse;
5267 	saberHitSaber = qfalse;
5268 	saberHitFraction = 1.0f;
5269 	if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
5270 	{//no diff
5271 		CheckSaberDamage( self, saberNum, bladeNum, baseNew, endNew, qfalse, clipmask, qfalse );
5272 	}
5273 	else
5274 	{//saber moved, lerp
5275 		float step = 8, stepsize = 8;//aveLength,
5276 		vec3_t	ma1, ma2, md2ang, curBase1, curBase2;
5277 		int	xx;
5278 		vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
5279 		float dirInc, curDirFrac;
5280 		vec3_t baseDiff, bladePointOld, bladePointNew;
5281 		qboolean extrapolate = qtrue;
5282 
5283 		//do the trace at the base first
5284 		VectorCopy( baseOld, bladePointOld );
5285 		VectorCopy( baseNew, bladePointNew );
5286 		CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, qtrue );
5287 
5288 		//if hit a saber, shorten rest of traces to match
5289 		if ( saberHitFraction < 1.0f )
5290 		{
5291 			//adjust muzzleDir...
5292 			vectoangles( md1, ma1 );
5293 			vectoangles( md2, ma2 );
5294 			for ( xx = 0; xx < 3; xx++ )
5295 			{
5296 				md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
5297 			}
5298 			AngleVectors( md2ang, md2, NULL, NULL );
5299 			//shorten the base pos
5300 			VectorSubtract( mp2, mp1, baseDiff );
5301 			VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
5302 			VectorMA( baseNew, self->client->saber[saberNum].blade[bladeNum].lengthMax, md2, endNew );
5303 		}
5304 
5305 		//If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
5306 		if ( BG_SaberInAttack( self->client->ps.saberMove )
5307 			|| BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
5308 			|| BG_SpinningSaberAnim( self->client->ps.torsoAnim )
5309 			|| BG_InSpecialJump( self->client->ps.torsoAnim ) )
5310 			//|| (g_timescale->value<1.0f&&BG_SaberInTransitionAny( ent->client->ps.saberMove )) )
5311 		{
5312 			curDirFrac = DotProduct( md1, md2 );
5313 		}
5314 		else
5315 		{
5316 			curDirFrac = 1.0f;
5317 		}
5318 		//NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
5319 		if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
5320 		{//the saber blade spun more than 33 degrees since the last damage trace
5321 			curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
5322 		}
5323 		else
5324 		{
5325 			curDirFrac = 1.0f;
5326 			dirInc = 0.0f;
5327 		}
5328 		//qboolean hit_saber = qfalse;
5329 
5330 		vectoangles( md1, ma1 );
5331 		vectoangles( md2, ma2 );
5332 
5333 		//VectorSubtract( md2, md1, mdDiff );
5334 		VectorCopy( md1, curMD2 );
5335 		VectorCopy( baseOld, curBase2 );
5336 
5337 		while ( 1 )
5338 		{
5339 			VectorCopy( curMD2, curMD1 );
5340 			VectorCopy( curBase2, curBase1 );
5341 			if ( curDirFrac >= 1.0f )
5342 			{
5343 				VectorCopy( md2, curMD2 );
5344 				VectorCopy( baseNew, curBase2 );
5345 			}
5346 			else
5347 			{
5348 				for ( xx = 0; xx < 3; xx++ )
5349 				{
5350 					md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
5351 				}
5352 				AngleVectors( md2ang, curMD2, NULL, NULL );
5353 				//VectorMA( md1, curDirFrac, mdDiff, curMD2 );
5354 				VectorSubtract( baseNew, baseOld, baseDiff );
5355 				VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
5356 			}
5357 			// Move up the blade in intervals of stepsize
5358 			for ( step = stepsize; step <= self->client->saber[saberNum].blade[bladeNum].lengthMax /*&& step < self->client->saber[saberNum].blade[bladeNum].lengthOld*/; step += stepsize )
5359 			{
5360 				VectorMA( curBase1, step, curMD1, bladePointOld );
5361 				VectorMA( curBase2, step, curMD2, bladePointNew );
5362 
5363 				if ( step+stepsize >= self->client->saber[saberNum].blade[bladeNum].lengthMax )
5364 				{
5365 					extrapolate = qfalse;
5366 				}
5367 				//do the damage trace
5368 				CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, extrapolate );
5369 				/*
5370 				if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2,
5371 					qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
5372 					saberNum, bladeNum ) )
5373 				{
5374 					hit_wall = qtrue;
5375 				}
5376 				*/
5377 
5378 				//if hit a saber, shorten rest of traces to match
5379 				if ( saberHitFraction < 1.0f )
5380 				{
5381 					vec3_t curMA1, curMA2;
5382 					//adjust muzzle endpoint
5383 					VectorSubtract( mp2, mp1, baseDiff );
5384 					VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
5385 					VectorMA( baseNew, self->client->saber[saberNum].blade[bladeNum].lengthMax, curMD2, endNew );
5386 					//adjust muzzleDir...
5387 					vectoangles( curMD1, curMA1 );
5388 					vectoangles( curMD2, curMA2 );
5389 					for ( xx = 0; xx < 3; xx++ )
5390 					{
5391 						md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
5392 					}
5393 					AngleVectors( md2ang, curMD2, NULL, NULL );
5394 					saberHitSaber = qtrue;
5395 				}
5396 				if (saberHitWall)
5397 				{
5398 					break;
5399 				}
5400 			}
5401 			if ( saberHitWall || saberHitSaber )
5402 			{
5403 				break;
5404 			}
5405 			if ( curDirFrac >= 1.0f )
5406 			{
5407 				break;
5408 			}
5409 			else
5410 			{
5411 				curDirFrac += dirInc;
5412 				if ( curDirFrac >= 1.0f )
5413 				{
5414 					curDirFrac = 1.0f;
5415 				}
5416 			}
5417 		}
5418 
5419 		//do the trace at the end last
5420 		//Special check- adjust for length of blade not being a multiple of 12
5421 		/*
5422 		aveLength = (ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld + ent->client->ps.saber[saberNum].blade[bladeNum].length)/2;
5423 		if ( step > aveLength )
5424 		{//less dmg if the last interval was not stepsize
5425 			tipDmgMod = (stepsize-(step-aveLength))/stepsize;
5426 		}
5427 		//NOTE: since this is the tip, we do not extrapolate the extra 16
5428 		if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2,
5429 			qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
5430 			saberNum, bladeNum ) )
5431 		{
5432 			hit_wall = qtrue;
5433 		}
5434 		*/
5435 	}
5436 }
5437 
5438 qboolean BG_SaberInTransitionAny( int move );
5439 
5440 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower );
5441 qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV );
5442 qboolean Jedi_WaitingAmbush( gentity_t *self );
5443 void Jedi_Ambush( gentity_t *self );
5444 evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist );
5445 void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
WP_SaberStartMissileBlockCheck(gentity_t * self,usercmd_t * ucmd)5446 void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd  )
5447 {
5448 	float		dist;
5449 	gentity_t	*ent, *incoming = NULL;
5450 	int			entityList[MAX_GENTITIES];
5451 	int			numListedEntities;
5452 	vec3_t		mins, maxs;
5453 	int			i, e;
5454 	float		closestDist, radius = 256;
5455 	vec3_t		forward, dir, missile_dir, fwdangles = {0};
5456 	trace_t		trace;
5457 	vec3_t		traceTo, entDir;
5458 	float		dot1, dot2;
5459 	float		lookTDist = -1;
5460 	gentity_t	*lookT = NULL;
5461 	qboolean	doFullRoutine = qtrue;
5462 
5463 	//keep this updated even if we don't get below
5464 	if ( !(self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
5465 	{//lookTarget is set by and to the monster that's holding you, no other operations can change that
5466 		self->client->ps.hasLookTarget = qfalse;
5467 	}
5468 
5469 	if ( self->client->ps.weapon != WP_SABER && self->client->NPC_class != CLASS_BOBAFETT )
5470 	{
5471 		doFullRoutine = qfalse;
5472 	}
5473 	else if ( self->client->ps.saberInFlight )
5474 	{
5475 		doFullRoutine = qfalse;
5476 	}
5477 	else if ( self->client->ps.fd.forcePowersActive&(1<<FP_LIGHTNING) )
5478 	{//can't block while zapping
5479 		doFullRoutine = qfalse;
5480 	}
5481 	else if ( self->client->ps.fd.forcePowersActive&(1<<FP_DRAIN) )
5482 	{//can't block while draining
5483 		doFullRoutine = qfalse;
5484 	}
5485 	else if ( self->client->ps.fd.forcePowersActive&(1<<FP_PUSH) )
5486 	{//can't block while shoving
5487 		doFullRoutine = qfalse;
5488 	}
5489 	else if ( self->client->ps.fd.forcePowersActive&(1<<FP_GRIP) )
5490 	{//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
5491 		doFullRoutine = qfalse;
5492 	}
5493 
5494 	if (self->client->ps.weaponTime > 0)
5495 	{ //don't autoblock while busy with stuff
5496 		return;
5497 	}
5498 
5499 	if ( (self->client->saber[0].saberFlags&SFL_NOT_ACTIVE_BLOCKING) )
5500 	{//can't actively block with this saber type
5501 		return;
5502 	}
5503 
5504 	if ( self->health <= 0 )
5505 	{//dead don't try to block (NOTE: actual deflection happens in missile code)
5506 		return;
5507 	}
5508 	if ( PM_InKnockDown( &self->client->ps ) )
5509 	{//can't block when knocked down
5510 		return;
5511 	}
5512 
5513 	if ( BG_SabersOff( &self->client->ps ) && self->client->NPC_class != CLASS_BOBAFETT )
5514 	{
5515 		if ( self->s.eType != ET_NPC )
5516 		{//player doesn't auto-activate
5517 			doFullRoutine = qfalse;
5518 		}
5519 	}
5520 
5521 	if ( self->s.eType == ET_PLAYER )
5522 	{//don't do this if already attacking!
5523 		if ( ucmd->buttons & BUTTON_ATTACK
5524 			|| BG_SaberInAttack( self->client->ps.saberMove )
5525 			|| BG_SaberInSpecialAttack( self->client->ps.torsoAnim )
5526 			|| BG_SaberInTransitionAny( self->client->ps.saberMove ))
5527 		{
5528 			doFullRoutine = qfalse;
5529 		}
5530 	}
5531 
5532 	if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
5533 	{//can't block while gripping (FIXME: or should it break the grip?  Pain should break the grip, I think...)
5534 		doFullRoutine = qfalse;
5535 	}
5536 
5537 	fwdangles[1] = self->client->ps.viewangles[1];
5538 	AngleVectors( fwdangles, forward, NULL, NULL );
5539 
5540 	for ( i = 0 ; i < 3 ; i++ )
5541 	{
5542 		mins[i] = self->r.currentOrigin[i] - radius;
5543 		maxs[i] = self->r.currentOrigin[i] + radius;
5544 	}
5545 
5546 	numListedEntities = trap->EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
5547 
5548 	closestDist = radius;
5549 
5550 	for ( e = 0 ; e < numListedEntities ; e++ )
5551 	{
5552 		ent = &g_entities[entityList[ e ]];
5553 
5554 		if (ent == self)
5555 			continue;
5556 
5557 		//as long as we're here I'm going to get a looktarget too, I guess. -rww
5558 		if (self->s.eType == ET_PLAYER &&
5559 			ent->client &&
5560 			(ent->s.eType == ET_NPC || ent->s.eType == ET_PLAYER) &&
5561 			!OnSameTeam(ent, self) &&
5562 			ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
5563 			!(ent->client->ps.pm_flags & PMF_FOLLOW) &&
5564 			(ent->s.eType != ET_NPC || ent->s.NPC_class != CLASS_VEHICLE) && //don't look at vehicle NPCs
5565 			ent->health > 0)
5566 		{ //seems like a valid enemy to look at.
5567 			vec3_t vecSub;
5568 			float vecLen;
5569 
5570 			VectorSubtract(self->client->ps.origin, ent->client->ps.origin, vecSub);
5571 			vecLen = VectorLength(vecSub);
5572 
5573 			if (lookTDist == -1 || vecLen < lookTDist)
5574 			{
5575 				trace_t tr;
5576 				vec3_t myEyes;
5577 
5578 				VectorCopy(self->client->ps.origin, myEyes);
5579 				myEyes[2] += self->client->ps.viewheight;
5580 
5581 				trap->Trace(&tr, myEyes, NULL, NULL, ent->client->ps.origin, self->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
5582 
5583 				if (tr.fraction == 1.0f || tr.entityNum == ent->s.number)
5584 				{ //we have a clear line of sight to him, so it's all good.
5585 					lookT = ent;
5586 					lookTDist = vecLen;
5587 				}
5588 			}
5589 		}
5590 
5591 		if (!doFullRoutine)
5592 		{ //don't care about the rest then
5593 			continue;
5594 		}
5595 
5596 		if (ent->r.ownerNum == self->s.number)
5597 			continue;
5598 		if ( !(ent->inuse) )
5599 			continue;
5600 		if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
5601 		{//not a normal projectile
5602 			gentity_t *pOwner;
5603 
5604 			if (ent->r.ownerNum < 0 || ent->r.ownerNum >= ENTITYNUM_WORLD)
5605 			{ //not going to be a client then.
5606 				continue;
5607 			}
5608 
5609 			pOwner = &g_entities[ent->r.ownerNum];
5610 
5611 			if (!pOwner->inuse || !pOwner->client)
5612 			{
5613 				continue; //not valid cl owner
5614 			}
5615 
5616 			if (!pOwner->client->ps.saberEntityNum ||
5617 				!pOwner->client->ps.saberInFlight ||
5618 				pOwner->client->ps.saberEntityNum != ent->s.number)
5619 			{ //the saber is knocked away and/or not flying actively, or this ent is not the cl's saber ent at all
5620 				continue;
5621 			}
5622 
5623 			//If we get here then it's ok to be treated as a thrown saber, I guess.
5624 		}
5625 		else
5626 		{
5627 			if ( ent->s.pos.trType == TR_STATIONARY && self->s.eType == ET_PLAYER )
5628 			{//nothing you can do with a stationary missile if you're the player
5629 				continue;
5630 			}
5631 		}
5632 
5633 		//see if they're in front of me
5634 		VectorSubtract( ent->r.currentOrigin, self->r.currentOrigin, dir );
5635 		dist = VectorNormalize( dir );
5636 		//FIXME: handle detpacks, proximity mines and tripmines
5637 		if ( ent->s.weapon == WP_THERMAL )
5638 		{//thermal detonator!
5639 			if ( self->NPC && dist < ent->splashRadius )
5640 			{
5641 				if ( dist < ent->splashRadius &&
5642 					ent->nextthink < level.time + 600 &&
5643 					ent->count &&
5644 					self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
5645 						(ent->s.pos.trType == TR_STATIONARY||
5646 						ent->s.pos.trType == TR_INTERPOLATE||
5647 						(dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
5648 						!WP_ForcePowerUsable( self, FP_PUSH )) )
5649 				{//TD is close enough to hurt me, I'm on the ground and the thing is at rest or behind me and about to blow up, or I don't have force-push so force-jump!
5650 					//FIXME: sometimes this might make me just jump into it...?
5651 					self->client->ps.fd.forceJumpCharge = 480;
5652 				}
5653 				else if ( self->client->NPC_class != CLASS_BOBAFETT )
5654 				{//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
5655 					ForceThrow( self, qfalse );
5656 				}
5657 			}
5658 			continue;
5659 		}
5660 		else if ( ent->splashDamage && ent->splashRadius )
5661 		{//exploding missile
5662 			//FIXME: handle tripmines and detpacks somehow...
5663 			//			maybe do a force-gesture that makes them explode?
5664 			//			But what if we're within it's splashradius?
5665 			if ( self->s.eType == ET_PLAYER )
5666 			{//players don't auto-handle these at all
5667 				continue;
5668 			}
5669 			else
5670 			{
5671 				//if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK)
5672 				//	&& 	self->client->NPC_class != CLASS_BOBAFETT )
5673 				if (0) //Maybe handle this later?
5674 				{//a placed explosive like a tripmine or detpack
5675 					if ( InFOV3( ent->r.currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
5676 					{//in front of me
5677 						if ( G_ClearLOS4( self, ent ) )
5678 						{//can see it
5679 							vec3_t throwDir;
5680 							//make the gesture
5681 							ForceThrow( self, qfalse );
5682 							//take it off the wall and toss it
5683 							ent->s.pos.trType = TR_GRAVITY;
5684 							ent->s.eType = ET_MISSILE;
5685 							ent->s.eFlags &= ~EF_MISSILE_STICK;
5686 							ent->flags |= FL_BOUNCE_HALF;
5687 							AngleVectors( ent->r.currentAngles, throwDir, NULL, NULL );
5688 							VectorMA( ent->r.currentOrigin, ent->r.maxs[0]+4, throwDir, ent->r.currentOrigin );
5689 							VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
5690 							VectorScale( throwDir, 300, ent->s.pos.trDelta );
5691 							ent->s.pos.trDelta[2] += 150;
5692 							VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
5693 							ent->s.pos.trTime = level.time;		// move a bit on the very first frame
5694 							VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
5695 							ent->r.ownerNum = self->s.number;
5696 							// make it explode, but with less damage
5697 							ent->splashDamage /= 3;
5698 							ent->splashRadius /= 3;
5699 							//ent->think = WP_Explode;
5700 							ent->nextthink = level.time + Q_irand( 500, 3000 );
5701 						}
5702 					}
5703 				}
5704 				else if ( dist < ent->splashRadius &&
5705 				self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
5706 					(DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE||
5707 					!WP_ForcePowerUsable( self, FP_PUSH )) )
5708 				{//NPCs try to evade it
5709 					self->client->ps.fd.forceJumpCharge = 480;
5710 				}
5711 				else if ( self->client->NPC_class != CLASS_BOBAFETT )
5712 				{//else, try to force-throw it away
5713 					//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
5714 					ForceThrow( self, qfalse );
5715 				}
5716 			}
5717 			//otherwise, can't block it, so we're screwed
5718 			continue;
5719 		}
5720 
5721 		if ( ent->s.weapon != WP_SABER )
5722 		{//only block shots coming from behind
5723 			if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
5724 				continue;
5725 		}
5726 		else if ( self->s.eType == ET_PLAYER )
5727 		{//player never auto-blocks thrown sabers
5728 			continue;
5729 		}//NPCs always try to block sabers coming from behind!
5730 
5731 		//see if they're heading towards me
5732 		VectorCopy( ent->s.pos.trDelta, missile_dir );
5733 		VectorNormalize( missile_dir );
5734 		if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
5735 			continue;
5736 
5737 		//FIXME: must have a clear trace to me, too...
5738 		if ( dist < closestDist )
5739 		{
5740 			VectorCopy( self->r.currentOrigin, traceTo );
5741 			traceTo[2] = self->r.absmax[2] - 4;
5742 			trap->Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, traceTo, ent->s.number, ent->clipmask, qfalse, 0, 0 );
5743 			if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
5744 			{//okay, try one more check
5745 				VectorNormalize2( ent->s.pos.trDelta, entDir );
5746 				VectorMA( ent->r.currentOrigin, radius, entDir, traceTo );
5747 				trap->Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, traceTo, ent->s.number, ent->clipmask, qfalse, 0, 0 );
5748 				if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
5749 				{//can't hit me, ignore it
5750 					continue;
5751 				}
5752 			}
5753 			if ( self->s.eType == ET_NPC )
5754 			{//An NPC
5755 				if ( self->NPC && !self->enemy && ent->r.ownerNum != ENTITYNUM_NONE )
5756 				{
5757 					gentity_t *owner = &g_entities[ent->r.ownerNum];
5758 					if ( owner->health >= 0 && (!owner->client || owner->client->playerTeam != self->client->playerTeam) )
5759 					{
5760 						G_SetEnemy( self, owner );
5761 					}
5762 				}
5763 			}
5764 			//FIXME: if NPC, predict the intersection between my current velocity/path and the missile's, see if it intersects my bounding box (+/-saberLength?), don't try to deflect unless it does?
5765 			closestDist = dist;
5766 			incoming = ent;
5767 		}
5768 	}
5769 
5770 	if (self->s.eType == ET_NPC && self->localAnimIndex <= 1)
5771 	{ //humanoid NPCs don't set angles based on server angles for looking, unlike other NPCs
5772 		if (self->client && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD)
5773 		{
5774 			lookT = &g_entities[self->client->renderInfo.lookTarget];
5775 		}
5776 	}
5777 
5778 	if (lookT)
5779 	{ //we got a looktarget at some point so we'll assign it then.
5780 		if ( !(self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
5781 		{//lookTarget is set by and to the monster that's holding you, no other operations can change that
5782 			self->client->ps.hasLookTarget = qtrue;
5783 			self->client->ps.lookTarget = lookT->s.number;
5784 		}
5785 	}
5786 
5787 	if (!doFullRoutine)
5788 	{ //then we're done now
5789 		return;
5790 	}
5791 
5792 	if ( incoming )
5793 	{
5794 		if ( self->NPC /*&& !G_ControlledByPlayer( self )*/ )
5795 		{
5796 			if ( Jedi_WaitingAmbush( self ) )
5797 			{
5798 				Jedi_Ambush( self );
5799 			}
5800 			if ( self->client->NPC_class == CLASS_BOBAFETT
5801 				&& (self->client->ps.eFlags2&EF2_FLYING)//moveType == MT_FLYSWIM
5802 				&& incoming->methodOfDeath != MOD_ROCKET_HOMING )
5803 			{//a hovering Boba Fett, not a tracking rocket
5804 				if ( !Q_irand( 0, 1 ) )
5805 				{//strafe
5806 					self->NPC->standTime = 0;
5807 					self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
5808 				}
5809 				if ( !Q_irand( 0, 1 ) )
5810 				{//go up/down
5811 					TIMER_Set( self, "heightChange", Q_irand( 1000, 3000 ) );
5812 					self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
5813 				}
5814 			}
5815 			else if ( Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming, 0.0f ) != EVASION_NONE )
5816 			{//make sure to turn on your saber if it's not on
5817 				if ( self->client->NPC_class != CLASS_BOBAFETT )
5818 				{
5819 					//self->client->ps.SaberActivate();
5820 					WP_ActivateSaber(self);
5821 				}
5822 			}
5823 		}
5824 		else//player
5825 		{
5826 			gentity_t *owner = &g_entities[incoming->r.ownerNum];
5827 
5828 			WP_SaberBlockNonRandom( self, incoming->r.currentOrigin, qtrue );
5829 			if ( owner && owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
5830 			{
5831 				self->enemy = owner;
5832 				//NPC_SetLookTarget( self, owner->s.number, level.time+1000 );
5833 				//player looktargetting done differently
5834 			}
5835 		}
5836 	}
5837 }
5838 
5839 #define MIN_SABER_SLICE_DISTANCE 50
5840 
5841 #define MIN_SABER_SLICE_RETURN_DISTANCE 30
5842 
5843 #define SABER_THROWN_HIT_DAMAGE 30
5844 #define SABER_THROWN_RETURN_HIT_DAMAGE 5
5845 
5846 void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace);
5847 
CheckThrownSaberDamaged(gentity_t * saberent,gentity_t * saberOwner,gentity_t * ent,int dist,int returning,qboolean noDCheck)5848 static QINLINE qboolean CheckThrownSaberDamaged(gentity_t *saberent, gentity_t *saberOwner, gentity_t *ent, int dist, int returning, qboolean noDCheck)
5849 {
5850 	vec3_t vecsub;
5851 	float veclen;
5852 	gentity_t *te;
5853 
5854 	if (!saberOwner || !saberOwner->client)
5855 	{
5856 		return qfalse;
5857 	}
5858 
5859 	if (saberOwner->client->ps.saberAttackWound > level.time)
5860 	{
5861 		return qfalse;
5862 	}
5863 
5864 	if (ent && ent->client && ent->inuse && ent->s.number != saberOwner->s.number &&
5865 		ent->health > 0 && ent->takedamage &&
5866 		trap->InPVS(ent->client->ps.origin, saberent->r.currentOrigin) &&
5867 		ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
5868 		(ent->client->pers.connected || ent->s.eType == ET_NPC))
5869 	{ //hit a client
5870 		if (ent->inuse && ent->client &&
5871 			ent->client->ps.duelInProgress &&
5872 			ent->client->ps.duelIndex != saberOwner->s.number)
5873 		{
5874 			return qfalse;
5875 		}
5876 
5877 		if (ent->inuse && ent->client &&
5878 			saberOwner->client->ps.duelInProgress &&
5879 			saberOwner->client->ps.duelIndex != ent->s.number)
5880 		{
5881 			return qfalse;
5882 		}
5883 
5884 		VectorSubtract(saberent->r.currentOrigin, ent->client->ps.origin, vecsub);
5885 		veclen = VectorLength(vecsub);
5886 
5887 		if (veclen < dist)
5888 		{ //within range
5889 			trace_t tr;
5890 
5891 			trap->Trace(&tr, saberent->r.currentOrigin, NULL, NULL, ent->client->ps.origin, saberent->s.number, MASK_SHOT, qfalse, 0, 0);
5892 
5893 			if (tr.fraction == 1 || tr.entityNum == ent->s.number)
5894 			{ //Slice them
5895 				if (!saberOwner->client->ps.isJediMaster && WP_SaberCanBlock(ent, tr.endpos, 0, MOD_SABER, qfalse, 999))
5896 				{ //they blocked it
5897 					WP_SaberBlockNonRandom(ent, tr.endpos, qfalse);
5898 
5899 					te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
5900 					VectorCopy(tr.endpos, te->s.origin);
5901 					VectorCopy(tr.plane.normal, te->s.angles);
5902 					if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
5903 					{
5904 						te->s.angles[1] = 1;
5905 					}
5906 					te->s.eventParm = 1;
5907 					te->s.weapon = 0;//saberNum
5908 					te->s.legsAnim = 0;//bladeNum
5909 
5910 					if (saberCheckKnockdown_Thrown(saberent, saberOwner, &g_entities[tr.entityNum]))
5911 					{ //it was knocked out of the air
5912 						return qfalse;
5913 					}
5914 
5915 					if (!returning)
5916 					{ //return to owner if blocked
5917 						thrownSaberTouch(saberent, saberent, NULL);
5918 					}
5919 
5920 					saberOwner->client->ps.saberAttackWound = level.time + 500;
5921 					return qfalse;
5922 				}
5923 				else
5924 				{ //a good hit
5925 					vec3_t dir;
5926 					int dflags = 0;
5927 
5928 					VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
5929 					VectorNormalize(dir);
5930 
5931 					if (!dir[0] && !dir[1] && !dir[2])
5932 					{
5933 						dir[1] = 1;
5934 					}
5935 
5936 					if ( (saberOwner->client->saber[0].saberFlags2&SFL2_NO_DISMEMBERMENT) )
5937 					{
5938 						dflags |= DAMAGE_NO_DISMEMBER;
5939 					}
5940 
5941 					if ( saberOwner->client->saber[0].knockbackScale > 0.0f )
5942 					{
5943 						dflags |= DAMAGE_SABER_KNOCKBACK1;
5944 					}
5945 
5946 					if (saberOwner->client->ps.isJediMaster)
5947 					{ //2x damage for the Jedi Master
5948 						G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage*2, dflags, MOD_SABER);
5949 					}
5950 					else
5951 					{
5952 						G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage, dflags, MOD_SABER);
5953 					}
5954 
5955 					te = G_TempEntity( tr.endpos, EV_SABER_HIT );
5956 					te->s.otherEntityNum = ent->s.number;
5957 					te->s.otherEntityNum2 = saberOwner->s.number;
5958 					te->s.weapon = 0;//saberNum
5959 					te->s.legsAnim = 0;//bladeNum
5960 					VectorCopy(tr.endpos, te->s.origin);
5961 					VectorCopy(tr.plane.normal, te->s.angles);
5962 					if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
5963 					{
5964 						te->s.angles[1] = 1;
5965 					}
5966 
5967 					te->s.eventParm = 1;
5968 
5969 					if (!returning)
5970 					{ //return to owner if blocked
5971 						thrownSaberTouch(saberent, saberent, NULL);
5972 					}
5973 				}
5974 
5975 				saberOwner->client->ps.saberAttackWound = level.time + 500;
5976 			}
5977 		}
5978 	}
5979 	else if (ent && !ent->client && ent->inuse && ent->takedamage && ent->health > 0 && ent->s.number != saberOwner->s.number &&
5980 		ent->s.number != saberent->s.number && (noDCheck ||trap->InPVS(ent->r.currentOrigin, saberent->r.currentOrigin)))
5981 	{ //hit a non-client
5982 
5983 		if (noDCheck)
5984 		{
5985 			veclen = 0;
5986 		}
5987 		else
5988 		{
5989 			VectorSubtract(saberent->r.currentOrigin, ent->r.currentOrigin, vecsub);
5990 			veclen = VectorLength(vecsub);
5991 		}
5992 
5993 		if (veclen < dist)
5994 		{
5995 			trace_t tr;
5996 			vec3_t entOrigin;
5997 
5998 			if (ent->s.eType == ET_MOVER)
5999 			{
6000 				VectorSubtract( ent->r.absmax, ent->r.absmin, entOrigin );
6001 				VectorMA( ent->r.absmin, 0.5, entOrigin, entOrigin );
6002 				VectorAdd( ent->r.absmin, ent->r.absmax, entOrigin );
6003 				VectorScale( entOrigin, 0.5f, entOrigin );
6004 			}
6005 			else
6006 			{
6007 				VectorCopy(ent->r.currentOrigin, entOrigin);
6008 			}
6009 
6010 			trap->Trace(&tr, saberent->r.currentOrigin, NULL, NULL, entOrigin, saberent->s.number, MASK_SHOT, qfalse, 0, 0);
6011 
6012 			if (tr.fraction == 1 || tr.entityNum == ent->s.number)
6013 			{
6014 				vec3_t dir;
6015 				int dflags = 0;
6016 
6017 				VectorSubtract(tr.endpos, entOrigin, dir);
6018 				VectorNormalize(dir);
6019 
6020 				if ( (saberOwner->client->saber[0].saberFlags2&SFL2_NO_DISMEMBERMENT) )
6021 				{
6022 					dflags |= DAMAGE_NO_DISMEMBER;
6023 				}
6024 				if ( saberOwner->client->saber[0].knockbackScale > 0.0f )
6025 				{
6026 					dflags |= DAMAGE_SABER_KNOCKBACK1;
6027 				}
6028 
6029 				if (ent->s.eType == ET_NPC)
6030 				{ //an animent
6031 					G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 40, dflags, MOD_SABER);
6032 				}
6033 				else
6034 				{
6035 					G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 5, dflags, MOD_SABER);
6036 				}
6037 
6038 				te = G_TempEntity( tr.endpos, EV_SABER_HIT );
6039 				te->s.otherEntityNum = ENTITYNUM_NONE; //don't do this for throw damage
6040 				//te->s.otherEntityNum = ent->s.number;
6041 				te->s.otherEntityNum2 = saberOwner->s.number;//actually, do send this, though - for the overridden per-saber hit effects/sounds
6042 				te->s.weapon = 0;//saberNum
6043 				te->s.legsAnim = 0;//bladeNum
6044 				VectorCopy(tr.endpos, te->s.origin);
6045 				VectorCopy(tr.plane.normal, te->s.angles);
6046 				if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
6047 				{
6048 					te->s.angles[1] = 1;
6049 				}
6050 
6051 				if ( ent->s.eType == ET_MOVER )
6052 				{
6053 					if ( saberOwner
6054 						&& saberOwner->client
6055 						&& (saberOwner->client->saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
6056 					{//don't do clash flare - NOTE: assumes same is true for both sabers if using dual sabers!
6057 						G_FreeEntity( te );//kind of a waste, but...
6058 					}
6059 					else
6060 					{
6061 						//I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing.
6062 						gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE );
6063 						VectorCopy(te->s.origin, teS->s.origin);
6064 
6065 						te->s.eventParm = 0;
6066 					}
6067 				}
6068 				else
6069 				{
6070 					te->s.eventParm = 1;
6071 				}
6072 
6073 				if (!returning)
6074 				{ //return to owner if blocked
6075 					thrownSaberTouch(saberent, saberent, NULL);
6076 				}
6077 
6078 				saberOwner->client->ps.saberAttackWound = level.time + 500;
6079 			}
6080 		}
6081 	}
6082 
6083 	return qtrue;
6084 }
6085 
saberCheckRadiusDamage(gentity_t * saberent,int returning)6086 static QINLINE void saberCheckRadiusDamage(gentity_t *saberent, int returning)
6087 { //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)
6088 	int i = 0;
6089 	int dist = 0;
6090 	gentity_t *ent;
6091 	gentity_t *saberOwner = &g_entities[saberent->r.ownerNum];
6092 
6093 	if (returning && returning != 2)
6094 	{
6095 		dist = MIN_SABER_SLICE_RETURN_DISTANCE;
6096 	}
6097 	else
6098 	{
6099 		dist = MIN_SABER_SLICE_DISTANCE;
6100 	}
6101 
6102 	if (!saberOwner || !saberOwner->client)
6103 	{
6104 		return;
6105 	}
6106 
6107 	if (saberOwner->client->ps.saberAttackWound > level.time)
6108 	{
6109 		return;
6110 	}
6111 
6112 	while (i < level.num_entities)
6113 	{
6114 		ent = &g_entities[i];
6115 
6116 		CheckThrownSaberDamaged(saberent, saberOwner, ent, dist, returning, qfalse);
6117 
6118 		i++;
6119 	}
6120 }
6121 
6122 #define THROWN_SABER_COMP
6123 
saberMoveBack(gentity_t * ent,qboolean goingBack)6124 static QINLINE void saberMoveBack( gentity_t *ent, qboolean goingBack )
6125 {
6126 	vec3_t		origin, oldOrg;
6127 
6128 	ent->s.pos.trType = TR_LINEAR;
6129 
6130 	VectorCopy( ent->r.currentOrigin, oldOrg );
6131 	// get current position
6132 	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
6133 	//Get current angles?
6134 	BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles );
6135 
6136 	//compensation test code..
6137 #ifdef THROWN_SABER_COMP
6138 	if (!goingBack && ent->s.pos.trType != TR_GRAVITY)
6139 	{ //acts as a fallback in case touch code fails, keeps saber from going through things between predictions
6140 		float originalLength = 0;
6141 		int iCompensationLength = 32;
6142 		trace_t tr;
6143 		vec3_t mins, maxs;
6144 		vec3_t calcComp, compensatedOrigin;
6145 		VectorSet( mins, -24.0f, -24.0f, -8.0f );
6146 		VectorSet( maxs, 24.0f, 24.0f, 8.0f );
6147 
6148 		VectorSubtract(origin, oldOrg, calcComp);
6149 		originalLength = VectorLength(calcComp);
6150 
6151 		VectorNormalize(calcComp);
6152 
6153 		compensatedOrigin[0] = oldOrg[0] + calcComp[0]*(originalLength+iCompensationLength);
6154 		compensatedOrigin[1] = oldOrg[1] + calcComp[1]*(originalLength+iCompensationLength);
6155 		compensatedOrigin[2] = oldOrg[2] + calcComp[2]*(originalLength+iCompensationLength);
6156 
6157 		trap->Trace(&tr, oldOrg, mins, maxs, compensatedOrigin, ent->r.ownerNum, MASK_PLAYERSOLID, qfalse, 0, 0);
6158 
6159 		if ((tr.fraction != 1 || tr.startsolid || tr.allsolid) && tr.entityNum != ent->r.ownerNum && !(g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER))
6160 		{
6161 			VectorClear(ent->s.pos.trDelta);
6162 
6163 			//Unfortunately doing this would defeat the purpose of the compensation. We will have to settle for a jerk on the client.
6164 			//VectorCopy( origin, ent->r.currentOrigin );
6165 
6166 			//we'll skip the dist check, since we don't really care about that (we just hit it physically)
6167 			CheckThrownSaberDamaged(ent, &g_entities[ent->r.ownerNum], &g_entities[tr.entityNum], 256, 0, qtrue);
6168 
6169 			if (ent->s.pos.trType == TR_GRAVITY)
6170 			{ //got blocked and knocked away in the damage func
6171 				return;
6172 			}
6173 
6174 			tr.startsolid = 0;
6175 			if (tr.entityNum == ENTITYNUM_NONE)
6176 			{ //eh, this is a filthy lie. (obviously it had to hit something or it wouldn't be in here, so we'll say it hit the world)
6177 				tr.entityNum = ENTITYNUM_WORLD;
6178 			}
6179 			thrownSaberTouch(ent, &g_entities[tr.entityNum], &tr);
6180 			return;
6181 		}
6182 	}
6183 #endif
6184 
6185 	VectorCopy( origin, ent->r.currentOrigin );
6186 }
6187 
SaberBounceSound(gentity_t * self,gentity_t * other,trace_t * trace)6188 void SaberBounceSound( gentity_t *self, gentity_t *other, trace_t *trace )
6189 {
6190 	VectorCopy(self->r.currentAngles, self->s.apos.trBase);
6191 	self->s.apos.trBase[PITCH] = 90;
6192 }
6193 
DeadSaberThink(gentity_t * saberent)6194 void DeadSaberThink(gentity_t *saberent)
6195 {
6196 	if (saberent->speed < level.time)
6197 	{
6198 		saberent->think = G_FreeEntity;
6199 		saberent->nextthink = level.time;
6200 		return;
6201 	}
6202 
6203 	G_RunObject(saberent);
6204 }
6205 
MakeDeadSaber(gentity_t * ent)6206 void MakeDeadSaber(gentity_t *ent)
6207 {	//spawn a "dead" saber entity here so it looks like the saber fell out of the air.
6208 	//This entity will remove itself after a very short time period.
6209 	vec3_t startorg;
6210 	vec3_t startang;
6211 	gentity_t *saberent;
6212 	gentity_t *owner = NULL;
6213 	//trace stuct used for determining if it's safe to spawn at current location
6214 	trace_t		tr;
6215 
6216 	if (level.gametype == GT_JEDIMASTER)
6217 	{ //never spawn a dead saber in JM, because the only saber on the level is really a world object
6218 		//G_Sound(ent, CHAN_AUTO, saberOffSound);
6219 		return;
6220 	}
6221 
6222 	saberent = G_Spawn();
6223 
6224 	VectorCopy(ent->r.currentOrigin, startorg);
6225 	VectorCopy(ent->r.currentAngles, startang);
6226 
6227 	saberent->classname = "deadsaber";
6228 
6229 	saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
6230 	saberent->r.ownerNum = ent->s.number;
6231 
6232 	saberent->clipmask = MASK_PLAYERSOLID;
6233 	saberent->r.contents = CONTENTS_TRIGGER;//0;
6234 
6235 	VectorSet( saberent->r.mins, -3.0f, -3.0f, -1.5f );
6236 	VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f );
6237 
6238 	saberent->touch = SaberBounceSound;
6239 
6240 	saberent->think = DeadSaberThink;
6241 	saberent->nextthink = level.time;
6242 
6243 	//perform a trace before attempting to spawn at currently location.
6244 	//unfortunately, it's a fairly regular occurance that current saber location
6245 	//(normally at the player's right hand) could result in the saber being stuck
6246 	//in the the map and then freaking out.
6247 	trap->Trace(&tr, startorg, saberent->r.mins, saberent->r.maxs, startorg, saberent->s.number, saberent->clipmask, qfalse, 0, 0);
6248 	if(tr.startsolid || tr.fraction != 1)
6249 	{//bad position, try popping our origin up a bit
6250 		startorg[2] += 20;
6251 		trap->Trace(&tr, startorg, saberent->r.mins, saberent->r.maxs, startorg, saberent->s.number, saberent->clipmask, qfalse, 0, 0);
6252 		if(tr.startsolid || tr.fraction != 1)
6253 		{//still no luck, try using our owner's origin
6254 			owner = &g_entities[ent->r.ownerNum];
6255 			if( owner->inuse && owner->client )
6256 			{
6257 				G_SetOrigin(saberent, owner->client->ps.origin);
6258 			}
6259 
6260 			//since this is our last chance, we don't care if this works or not.
6261 		}
6262 	}
6263 
6264 	VectorCopy(startorg, saberent->s.pos.trBase);
6265 	VectorCopy(startang, saberent->s.apos.trBase);
6266 
6267 	VectorCopy(startorg, saberent->s.origin);
6268 	VectorCopy(startang, saberent->s.angles);
6269 
6270 	VectorCopy(startorg, saberent->r.currentOrigin);
6271 	VectorCopy(startang, saberent->r.currentAngles);
6272 
6273 	saberent->s.apos.trType = TR_GRAVITY;
6274 	saberent->s.apos.trDelta[0] = Q_irand(200, 800);
6275 	saberent->s.apos.trDelta[1] = Q_irand(200, 800);
6276 	saberent->s.apos.trDelta[2] = Q_irand(200, 800);
6277 	saberent->s.apos.trTime = level.time-50;
6278 
6279 	saberent->s.pos.trType = TR_GRAVITY;
6280 	saberent->s.pos.trTime = level.time-50;
6281 	saberent->flags = FL_BOUNCE_HALF;
6282 	if (ent->r.ownerNum >= 0 && ent->r.ownerNum < ENTITYNUM_WORLD)
6283 	{
6284 		owner = &g_entities[ent->r.ownerNum];
6285 
6286 		if (owner->inuse && owner->client &&
6287 			owner->client->saber[0].model[0])
6288 		{
6289 			WP_SaberAddG2Model( saberent, owner->client->saber[0].model, owner->client->saber[0].skin );
6290 		}
6291 		else
6292 		{
6293 			//WP_SaberAddG2Model( saberent, NULL, 0 );
6294 			//argh!!!!
6295 			G_FreeEntity(saberent);
6296 			return;
6297 		}
6298 	}
6299 
6300 	saberent->s.modelGhoul2 = 1;
6301 	saberent->s.g2radius = 20;
6302 
6303 	saberent->s.eType = ET_MISSILE;
6304 	saberent->s.weapon = WP_SABER;
6305 
6306 	saberent->speed = level.time + 4000;
6307 
6308 	saberent->bounceCount = 12;
6309 
6310 	//fall off in the direction the real saber was headed
6311 	VectorCopy(ent->s.pos.trDelta, saberent->s.pos.trDelta);
6312 
6313 	saberMoveBack(saberent, qtrue);
6314 	saberent->s.pos.trType = TR_GRAVITY;
6315 
6316 	trap->LinkEntity((sharedEntity_t *)saberent);
6317 }
6318 
6319 #define MAX_LEAVE_TIME 20000
6320 
6321 void saberReactivate(gentity_t *saberent, gentity_t *saberOwner);
6322 void saberBackToOwner(gentity_t *saberent);
6323 
DownedSaberThink(gentity_t * saberent)6324 void DownedSaberThink(gentity_t *saberent)
6325 {
6326 	gentity_t *saberOwn = NULL;
6327 	qboolean notDisowned = qfalse;
6328 	qboolean pullBack = qfalse;
6329 
6330 	saberent->nextthink = level.time;
6331 
6332 	if (saberent->r.ownerNum == ENTITYNUM_NONE)
6333 	{
6334 		MakeDeadSaber(saberent);
6335 
6336 		saberent->think = G_FreeEntity;
6337 		saberent->nextthink = level.time;
6338 		return;
6339 	}
6340 
6341 	saberOwn = &g_entities[saberent->r.ownerNum];
6342 
6343 	if (!saberOwn ||
6344 		!saberOwn->inuse ||
6345 		!saberOwn->client ||
6346 		saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR ||
6347 		(saberOwn->client->ps.pm_flags & PMF_FOLLOW))
6348 	{
6349 		MakeDeadSaber(saberent);
6350 
6351 		saberent->think = G_FreeEntity;
6352 		saberent->nextthink = level.time;
6353 		return;
6354 	}
6355 
6356 	if (saberOwn->client->ps.saberEntityNum)
6357 	{
6358 		if (saberOwn->client->ps.saberEntityNum == saberent->s.number)
6359 		{ //owner shouldn't have this set if we're thinking in here. Must've fallen off a cliff and instantly respawned or something.
6360 			notDisowned = qtrue;
6361 		}
6362 		else
6363 		{ //This should never happen, but just in case..
6364 			assert(!"ULTRA BAD THING");
6365 			MakeDeadSaber(saberent);
6366 
6367 			saberent->think = G_FreeEntity;
6368 			saberent->nextthink = level.time;
6369 			return;
6370 		}
6371 	}
6372 
6373 	if (notDisowned || saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
6374 	{ //He's dead, just go back to our normal saber status
6375 		saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex;
6376 
6377 		//MakeDeadSaber(saberent); //spawn a dead saber on top of where we are now. The "bodyqueue" method.
6378 		//Actually this will get taken care of when the thrown saber func sees we're dead.
6379 
6380 #ifdef _DEBUG
6381 		if (saberOwn->client->saberStoredIndex != saberent->s.number)
6382 		{ //I'm paranoid.
6383 			assert(!"Bad saber index!!!");
6384 		}
6385 #endif
6386 
6387 		saberReactivate(saberent, saberOwn);
6388 
6389 		if (saberOwn->health < 1)
6390 		{
6391 			saberOwn->client->ps.saberInFlight = qfalse;
6392 			MakeDeadSaber(saberent);
6393 		}
6394 
6395 		saberent->touch = SaberGotHit;
6396 		saberent->think = SaberUpdateSelf;
6397 		saberent->genericValue5 = 0;
6398 		saberent->nextthink = level.time;
6399 
6400 		saberent->r.svFlags |= (SVF_NOCLIENT);
6401 		//saberent->r.contents = CONTENTS_LIGHTSABER;
6402 		saberent->s.loopSound = 0;
6403 		saberent->s.loopIsSoundset = qfalse;
6404 
6405 		if (saberOwn->health > 0)
6406 		{ //only set this if he's alive. If dead we want to reflect the lack of saber on the corpse, as he died with his saber out.
6407 			saberOwn->client->ps.saberInFlight = qfalse;
6408 			WP_SaberRemoveG2Model( saberent );
6409 		}
6410 		saberOwn->client->ps.saberEntityState = 0;
6411 		saberOwn->client->ps.saberThrowDelay = level.time + 500;
6412 		saberOwn->client->ps.saberCanThrow = qfalse;
6413 
6414 		return;
6415 	}
6416 
6417 	if (saberOwn->client->saberKnockedTime < level.time && (saberOwn->client->pers.cmd.buttons & BUTTON_ATTACK))
6418 	{ //He wants us back
6419 		pullBack = qtrue;
6420 	}
6421 	else if ((level.time - saberOwn->client->saberKnockedTime) > MAX_LEAVE_TIME)
6422 	{ //Been sitting around for too long, go back no matter what he wants.
6423 		pullBack = qtrue;
6424 	}
6425 
6426 	if (pullBack)
6427 	{ //Get going back to the owner.
6428 		saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex;
6429 
6430 #ifdef _DEBUG
6431 		if (saberOwn->client->saberStoredIndex != saberent->s.number)
6432 		{ //I'm paranoid.
6433 			assert(!"Bad saber index!!!");
6434 		}
6435 #endif
6436 		saberReactivate(saberent, saberOwn);
6437 
6438 		saberent->touch = SaberGotHit;
6439 
6440 		saberent->think = saberBackToOwner;
6441 		saberent->speed = 0;
6442 		saberent->genericValue5 = 0;
6443 		saberent->nextthink = level.time;
6444 
6445 		saberent->r.contents = CONTENTS_LIGHTSABER;
6446 
6447 		G_Sound( saberOwn, CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
6448 		if (saberOwn->client->saber[0].soundOn)
6449 		{
6450 			G_Sound( saberent, CHAN_BODY, saberOwn->client->saber[0].soundOn );
6451 		}
6452 		if (saberOwn->client->saber[1].soundOn)
6453 		{
6454 			G_Sound( saberOwn, CHAN_BODY, saberOwn->client->saber[1].soundOn );
6455 		}
6456 
6457 		return;
6458 	}
6459 
6460 	G_RunObject(saberent);
6461 	saberent->nextthink = level.time;
6462 }
6463 
saberReactivate(gentity_t * saberent,gentity_t * saberOwner)6464 void saberReactivate(gentity_t *saberent, gentity_t *saberOwner)
6465 {
6466 	saberent->s.saberInFlight = qtrue;
6467 
6468 	saberent->s.apos.trType = TR_LINEAR;
6469 	saberent->s.apos.trDelta[0] = 0;
6470 	saberent->s.apos.trDelta[1] = 800;
6471 	saberent->s.apos.trDelta[2] = 0;
6472 
6473 	saberent->s.pos.trType = TR_LINEAR;
6474 	saberent->s.eType = ET_GENERAL;
6475 	saberent->s.eFlags = 0;
6476 
6477 	saberent->parent = saberOwner;
6478 
6479 	saberent->genericValue5 = 0;
6480 
6481 	SetSaberBoxSize(saberent);
6482 
6483 	saberent->touch = thrownSaberTouch;
6484 
6485 	saberent->s.weapon = WP_SABER;
6486 
6487 	saberOwner->client->ps.saberEntityState = 1;
6488 
6489 	trap->LinkEntity((sharedEntity_t *)saberent);
6490 }
6491 
6492 #define SABER_RETRIEVE_DELAY 3000 //3 seconds for now. This will leave you nice and open if you lose your saber.
6493 
saberKnockDown(gentity_t * saberent,gentity_t * saberOwner,gentity_t * other)6494 void saberKnockDown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
6495 {
6496 	trace_t		tr;
6497 
6498 	saberOwner->client->ps.saberEntityNum = 0; //still stored in client->saberStoredIndex
6499 	saberOwner->client->saberKnockedTime = level.time + SABER_RETRIEVE_DELAY;
6500 
6501 	saberent->clipmask = MASK_SOLID;
6502 	saberent->r.contents = CONTENTS_TRIGGER;//0;
6503 
6504 	VectorSet( saberent->r.mins, -3.0f, -3.0f, -1.5f );
6505 	VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f );
6506 
6507 	//perform a trace before attempting to spawn at currently location.
6508 	//unfortunately, it's a fairly regular occurance that current saber location
6509 	//(normally at the player's right hand) could result in the saber being stuck
6510 	//in the the map and then freaking out.
6511 	trap->Trace(&tr, saberent->r.currentOrigin, saberent->r.mins, saberent->r.maxs, saberent->r.currentOrigin, saberent->s.number, saberent->clipmask, qfalse, 0, 0);
6512 	if(tr.startsolid || tr.fraction != 1)
6513 	{//bad position, try popping our origin up a bit
6514 		saberent->r.currentOrigin[2] += 20;
6515 		G_SetOrigin(saberent, saberent->r.currentOrigin);
6516 		trap->Trace(&tr, saberent->r.currentOrigin, saberent->r.mins, saberent->r.maxs, saberent->r.currentOrigin, saberent->s.number, saberent->clipmask, qfalse, 0, 0);
6517 		if(tr.startsolid || tr.fraction != 1)
6518 		{//still no luck, try using our owner's origin
6519 			G_SetOrigin(saberent, saberOwner->client->ps.origin);
6520 
6521 			//since this is our last chance, we don't care if this works or not.
6522 		}
6523 	}
6524 
6525 	saberent->s.apos.trType = TR_GRAVITY;
6526 	saberent->s.apos.trDelta[0] = Q_irand(200, 800);
6527 	saberent->s.apos.trDelta[1] = Q_irand(200, 800);
6528 	saberent->s.apos.trDelta[2] = Q_irand(200, 800);
6529 	saberent->s.apos.trTime = level.time-50;
6530 
6531 	saberent->s.pos.trType = TR_GRAVITY;
6532 	saberent->s.pos.trTime = level.time-50;
6533 	saberent->flags |= FL_BOUNCE_HALF;
6534 
6535 	WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin );
6536 
6537 	saberent->s.modelGhoul2 = 1;
6538 	saberent->s.g2radius = 20;
6539 
6540 	saberent->s.eType = ET_MISSILE;
6541 	saberent->s.weapon = WP_SABER;
6542 
6543 	saberent->speed = level.time + 4000;
6544 
6545 	saberent->bounceCount = -5;//8;
6546 
6547 	saberMoveBack(saberent, qtrue);
6548 	saberent->s.pos.trType = TR_GRAVITY;
6549 
6550 	saberent->s.loopSound = 0; //kill this in case it was spinning.
6551 	saberent->s.loopIsSoundset = qfalse;
6552 
6553 	saberent->r.svFlags &= ~(SVF_NOCLIENT); //make sure the client is getting updates on where it is and such.
6554 
6555 	saberent->touch = SaberBounceSound;
6556 	saberent->think = DownedSaberThink;
6557 	saberent->nextthink = level.time;
6558 
6559 	if (saberOwner != other)
6560 	{ //if someone knocked it out of the air and it wasn't turned off, go in the direction they were facing.
6561 		if (other->inuse && other->client)
6562 		{
6563 			vec3_t otherFwd;
6564 			float deflectSpeed = 200;
6565 
6566 			AngleVectors(other->client->ps.viewangles, otherFwd, 0, 0);
6567 
6568 			saberent->s.pos.trDelta[0] = otherFwd[0]*deflectSpeed;
6569 			saberent->s.pos.trDelta[1] = otherFwd[1]*deflectSpeed;
6570 			saberent->s.pos.trDelta[2] = otherFwd[2]*deflectSpeed;
6571 		}
6572 	}
6573 
6574 	trap->LinkEntity((sharedEntity_t *)saberent);
6575 
6576 	if (saberOwner->client->saber[0].soundOff)
6577 	{
6578 		G_Sound( saberent, CHAN_BODY, saberOwner->client->saber[0].soundOff );
6579 	}
6580 
6581 	if (saberOwner->client->saber[1].soundOff &&
6582 		saberOwner->client->saber[1].model[0])
6583 	{
6584 		G_Sound( saberOwner, CHAN_BODY, saberOwner->client->saber[1].soundOff );
6585 	}
6586 }
6587 
6588 //sort of a silly macro I guess. But if I change anything in here I'll probably want it to be everywhere.
6589 #define SABERINVALID (!saberent || !saberOwner || !other || !saberent->inuse || !saberOwner->inuse || !other->inuse || !saberOwner->client || !other->client || !saberOwner->client->ps.saberEntityNum || saberOwner->client->ps.saberLockTime > (level.time-100))
6590 
WP_SaberRemoveG2Model(gentity_t * saberent)6591 void WP_SaberRemoveG2Model( gentity_t *saberent )
6592 {
6593 	if ( saberent->ghoul2 )
6594 	{
6595 		trap->G2API_RemoveGhoul2Models( &saberent->ghoul2 );
6596 	}
6597 }
6598 
WP_SaberAddG2Model(gentity_t * saberent,const char * saberModel,qhandle_t saberSkin)6599 void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin )
6600 {
6601 	WP_SaberRemoveG2Model( saberent );
6602 	if ( saberModel && saberModel[0] )
6603 	{
6604 		saberent->s.modelindex = G_ModelIndex(saberModel);
6605 	}
6606 	else
6607 	{
6608 		saberent->s.modelindex = G_ModelIndex( DEFAULT_SABER_MODEL );
6609 	}
6610 	//FIXME: use customSkin?
6611 	trap->G2API_InitGhoul2Model( &saberent->ghoul2, saberModel, saberent->s.modelindex, saberSkin, 0, 0, 0 );
6612 }
6613 
6614 //Make the saber go flying directly out of the owner's hand in the specified direction
saberKnockOutOfHand(gentity_t * saberent,gentity_t * saberOwner,vec3_t velocity)6615 qboolean saberKnockOutOfHand(gentity_t *saberent, gentity_t *saberOwner, vec3_t velocity)
6616 {
6617 	if (!saberent || !saberOwner ||
6618 		!saberent->inuse || !saberOwner->inuse ||
6619 		!saberOwner->client)
6620 	{
6621 		return qfalse;
6622 	}
6623 
6624 	if (!saberOwner->client->ps.saberEntityNum)
6625 	{ //already gone
6626 		return qfalse;
6627 	}
6628 
6629 	if ((level.time - saberOwner->client->lastSaberStorageTime) > 50)
6630 	{ //must have a reasonably updated saber base pos
6631 		return qfalse;
6632 	}
6633 
6634 	if (saberOwner->client->ps.saberLockTime > (level.time-100))
6635 	{
6636 		return qfalse;
6637 	}
6638 	if ( (saberOwner->client->saber[0].saberFlags&SFL_NOT_DISARMABLE) )
6639 	{
6640 		return qfalse;
6641 	}
6642 
6643 	saberOwner->client->ps.saberInFlight = qtrue;
6644 	saberOwner->client->ps.saberEntityState = 1;
6645 
6646 	saberent->s.saberInFlight = qfalse;//qtrue;
6647 
6648 	saberent->s.pos.trType = TR_LINEAR;
6649 	saberent->s.eType = ET_GENERAL;
6650 	saberent->s.eFlags = 0;
6651 
6652 	WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin );
6653 
6654 	saberent->s.modelGhoul2 = 127;
6655 
6656 	saberent->parent = saberOwner;
6657 
6658 	saberent->damage = SABER_THROWN_HIT_DAMAGE;
6659 	saberent->methodOfDeath = MOD_SABER;
6660 	saberent->splashMethodOfDeath = MOD_SABER;
6661 	saberent->s.solid = 2;
6662 	saberent->r.contents = CONTENTS_LIGHTSABER;
6663 
6664 	saberent->genericValue5 = 0;
6665 
6666 	VectorSet( saberent->r.mins, -24.0f, -24.0f, -8.0f );
6667 	VectorSet( saberent->r.maxs, 24.0f, 24.0f, 8.0f );
6668 
6669 	saberent->s.genericenemyindex = saberOwner->s.number+1024;
6670 	saberent->s.weapon = WP_SABER;
6671 
6672 	saberent->genericValue5 = 0;
6673 
6674 	G_SetOrigin(saberent, saberOwner->client->lastSaberBase_Always); //use this as opposed to the right hand bolt,
6675 	//because I don't want to risk reconstructing the skel again to get it here. And it isn't worth storing.
6676 	saberKnockDown(saberent, saberOwner, saberOwner);
6677 	VectorCopy(velocity, saberent->s.pos.trDelta); //override the velocity on the knocked away saber.
6678 
6679 	return qtrue;
6680 }
6681 
6682 //Called at the result of a circle lock duel - the loser gets his saber tossed away and is put into a reflected attack anim
saberCheckKnockdown_DuelLoss(gentity_t * saberent,gentity_t * saberOwner,gentity_t * other)6683 qboolean saberCheckKnockdown_DuelLoss(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
6684 {
6685 	vec3_t dif;
6686 	float totalDistance = 1;
6687 	float distScale = 6.5f;
6688 	qboolean validMomentum = qtrue;
6689 	int	disarmChance = 1;
6690 
6691 	if (SABERINVALID)
6692 	{
6693 		return qfalse;
6694 	}
6695 
6696 	VectorClear(dif);
6697 
6698 	if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200)
6699 	{ //see if the spots are valid
6700 		validMomentum = qfalse;
6701 	}
6702 
6703 	if (validMomentum)
6704 	{
6705 		//Get the difference
6706 		VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif);
6707 		totalDistance = VectorNormalize(dif);
6708 
6709 		if (!totalDistance)
6710 		{ //fine, try our own
6711 			if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200)
6712 			{
6713 				validMomentum = qfalse;
6714 			}
6715 
6716 			if (validMomentum)
6717 			{
6718 				VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif);
6719 				totalDistance = VectorNormalize(dif);
6720 			}
6721 		}
6722 
6723 		if (validMomentum)
6724 		{
6725 			if (!totalDistance)
6726 			{ //try the difference between the two blades
6727 				VectorSubtract(saberOwner->client->lastSaberBase_Always, other->client->lastSaberBase_Always, dif);
6728 				totalDistance = VectorNormalize(dif);
6729 			}
6730 
6731 			if (totalDistance)
6732 			{ //if we still have no difference somehow, just let it fall to the ground when the time comes.
6733 				if (totalDistance < 20)
6734 				{
6735 					totalDistance = 20;
6736 				}
6737 				VectorScale(dif, totalDistance*distScale, dif);
6738 			}
6739 		}
6740 	}
6741 
6742 	saberOwner->client->ps.saberMove = LS_V1_BL; //rwwFIXMEFIXME: Ideally check which lock it was exactly and use the proper anim (same goes for the attacker)
6743 	saberOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE;
6744 
6745 	if ( other && other->client )
6746 	{
6747 		disarmChance += other->client->saber[0].disarmBonus;
6748 		if ( other->client->saber[1].model[0]
6749 			&& !other->client->ps.saberHolstered )
6750 		{
6751 			disarmChance += other->client->saber[1].disarmBonus;
6752 		}
6753 	}
6754 	if ( Q_irand( 0, disarmChance ) )
6755 	{
6756 		return saberKnockOutOfHand(saberent, saberOwner, dif);
6757 	}
6758 	else
6759 	{
6760 		return qfalse;
6761 	}
6762 }
6763 
6764 //Called when we want to try knocking the saber out of the owner's hand upon them going into a broken parry.
6765 //Also called on reflected attacks.
saberCheckKnockdown_BrokenParry(gentity_t * saberent,gentity_t * saberOwner,gentity_t * other)6766 qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
6767 {
6768 	int myAttack;
6769 	int otherAttack;
6770 	qboolean doKnock = qfalse;
6771 	int	disarmChance = 1;
6772 
6773 	if (SABERINVALID)
6774 	{
6775 		return qfalse;
6776 	}
6777 
6778 	//Neither gets an advantage based on attack state, when it comes to knocking
6779 	//saber out of hand.
6780 	myAttack = G_SaberAttackPower(saberOwner, qfalse);
6781 	otherAttack = G_SaberAttackPower(other, qfalse);
6782 
6783 	if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200)
6784 	{ //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it
6785 		return qfalse;
6786 	}
6787 
6788 	//only knock the saber out of the hand if they're in a stronger stance I suppose. Makes strong more advantageous.
6789 	if (otherAttack > myAttack+1 && Q_irand(1, 10) <= 7)
6790 	{ //This would be, say, strong stance against light stance.
6791 		doKnock = qtrue;
6792 	}
6793 	else if (otherAttack > myAttack && Q_irand(1, 10) <= 3)
6794 	{ //Strong vs. medium, medium vs. light
6795 		doKnock = qtrue;
6796 	}
6797 
6798 	if (doKnock)
6799 	{
6800 		vec3_t dif;
6801 		float totalDistance;
6802 		float distScale = 6.5f;
6803 
6804 		VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif);
6805 		totalDistance = VectorNormalize(dif);
6806 
6807 		if (!totalDistance)
6808 		{ //fine, try our own
6809 			if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200)
6810 			{ //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it
6811 				return qfalse;
6812 			}
6813 
6814 			VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif);
6815 			totalDistance = VectorNormalize(dif);
6816 		}
6817 
6818 		if (!totalDistance)
6819 		{ //...forget it then.
6820 			return qfalse;
6821 		}
6822 
6823 		if (totalDistance < 20)
6824 		{
6825 			totalDistance = 20;
6826 		}
6827 		VectorScale(dif, totalDistance*distScale, dif);
6828 
6829 		if ( other && other->client )
6830 		{
6831 			disarmChance += other->client->saber[0].disarmBonus;
6832 			if ( other->client->saber[1].model[0]
6833 				&& !other->client->ps.saberHolstered )
6834 			{
6835 				disarmChance += other->client->saber[1].disarmBonus;
6836 			}
6837 		}
6838 		if ( Q_irand( 0, disarmChance ) )
6839 		{
6840 			return saberKnockOutOfHand(saberent, saberOwner, dif);
6841 		}
6842 	}
6843 
6844 	return qfalse;
6845 }
6846 
6847 qboolean BG_InExtraDefenseSaberMove( int move );
6848 
6849 //Called upon an enemy actually slashing into a thrown saber
saberCheckKnockdown_Smashed(gentity_t * saberent,gentity_t * saberOwner,gentity_t * other,int damage)6850 qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage)
6851 {
6852 	if (SABERINVALID)
6853 	{
6854 		return qfalse;
6855 	}
6856 
6857 	if (!saberOwner->client->ps.saberInFlight)
6858 	{ //can only do this if the saber is already actually in flight
6859 		return qfalse;
6860 	}
6861 
6862 	if ( other
6863 		&& other->inuse
6864 		&& other->client
6865 		&& BG_InExtraDefenseSaberMove( other->client->ps.saberMove ) )
6866 	{ //make sure the blow was strong enough
6867 		saberKnockDown(saberent, saberOwner, other);
6868 		return qtrue;
6869 	}
6870 
6871 	if (damage > 10)
6872 	{ //make sure the blow was strong enough
6873 		saberKnockDown(saberent, saberOwner, other);
6874 		return qtrue;
6875 	}
6876 
6877 	return qfalse;
6878 }
6879 
6880 //Called upon blocking a thrown saber. If the throw level compared to the blocker's defense level
6881 //is inferior, or equal and a random factor is met, then the saber will be tossed to the ground.
saberCheckKnockdown_Thrown(gentity_t * saberent,gentity_t * saberOwner,gentity_t * other)6882 qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other)
6883 {
6884 	int throwLevel = 0;
6885 	int defenLevel = 0;
6886 	qboolean tossIt = qfalse;
6887 
6888 	if (SABERINVALID)
6889 	{
6890 		return qfalse;
6891 	}
6892 
6893 	defenLevel = other->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE];
6894 	throwLevel = saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW];
6895 
6896 	if (defenLevel > throwLevel)
6897 	{
6898 		tossIt = qtrue;
6899 	}
6900 	else if (defenLevel == throwLevel && Q_irand(1, 10) <= 4)
6901 	{
6902 		tossIt = qtrue;
6903 	}
6904 	//otherwise don't
6905 
6906 	if (tossIt)
6907 	{
6908 		saberKnockDown(saberent, saberOwner, other);
6909 		return qtrue;
6910 	}
6911 
6912 	return qfalse;
6913 }
6914 
saberBackToOwner(gentity_t * saberent)6915 void saberBackToOwner(gentity_t *saberent)
6916 {
6917 	gentity_t *saberOwner = &g_entities[saberent->r.ownerNum];
6918 	vec3_t dir;
6919 	float ownerLen;
6920 
6921 	if (saberent->r.ownerNum == ENTITYNUM_NONE)
6922 	{
6923 		MakeDeadSaber(saberent);
6924 
6925 		saberent->think = G_FreeEntity;
6926 		saberent->nextthink = level.time;
6927 		return;
6928 	}
6929 
6930 	if (!saberOwner->inuse ||
6931 		!saberOwner->client ||
6932 		saberOwner->client->sess.sessionTeam == TEAM_SPECTATOR)
6933 	{
6934 		MakeDeadSaber(saberent);
6935 
6936 		saberent->think = G_FreeEntity;
6937 		saberent->nextthink = level.time;
6938 		return;
6939 	}
6940 
6941 	if (saberOwner->health < 1 || !saberOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
6942 	{ //He's dead, just go back to our normal saber status
6943 		saberent->touch = SaberGotHit;
6944 		saberent->think = SaberUpdateSelf;
6945 		saberent->genericValue5 = 0;
6946 		saberent->nextthink = level.time;
6947 
6948 		if (saberOwner->client &&
6949 			saberOwner->client->saber[0].soundOff)
6950 		{
6951 			G_Sound(saberent, CHAN_AUTO, saberOwner->client->saber[0].soundOff);
6952 		}
6953 		MakeDeadSaber(saberent);
6954 
6955 		saberent->r.svFlags |= (SVF_NOCLIENT);
6956 		saberent->r.contents = CONTENTS_LIGHTSABER;
6957 		SetSaberBoxSize(saberent);
6958 		saberent->s.loopSound = 0;
6959 		saberent->s.loopIsSoundset = qfalse;
6960 		WP_SaberRemoveG2Model( saberent );
6961 
6962 		saberOwner->client->ps.saberInFlight = qfalse;
6963 		saberOwner->client->ps.saberEntityState = 0;
6964 		saberOwner->client->ps.saberThrowDelay = level.time + 500;
6965 		saberOwner->client->ps.saberCanThrow = qfalse;
6966 
6967 		return;
6968 	}
6969 
6970 	//make sure this is set alright
6971 	assert(saberOwner->client->ps.saberEntityNum == saberent->s.number ||
6972 		saberOwner->client->saberStoredIndex == saberent->s.number);
6973 	saberOwner->client->ps.saberEntityNum = saberent->s.number;
6974 
6975 	saberent->r.contents = CONTENTS_LIGHTSABER;
6976 
6977 	VectorSubtract(saberent->pos1, saberent->r.currentOrigin, dir);
6978 
6979 	ownerLen = VectorLength(dir);
6980 
6981 	if (saberent->speed < level.time)
6982 	{
6983 		float baseSpeed = 900;
6984 
6985 		VectorNormalize(dir);
6986 
6987 		saberMoveBack(saberent, qtrue);
6988 		VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
6989 
6990 		if (saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
6991 		{ //allow players with high saber throw rank to control the return speed of the saber
6992 			baseSpeed = 900;
6993 
6994 			saberent->speed = level.time;// + 200;
6995 		}
6996 		else
6997 		{
6998 			baseSpeed = 700;
6999 			saberent->speed = level.time + 50;
7000 		}
7001 
7002 		//Gradually slow down as it approaches, so it looks smoother coming into the hand.
7003 		if (ownerLen < 64)
7004 		{
7005 			VectorScale(dir, baseSpeed-200, saberent->s.pos.trDelta );
7006 		}
7007 		else if (ownerLen < 128)
7008 		{
7009 			VectorScale(dir, baseSpeed-150, saberent->s.pos.trDelta );
7010 		}
7011 		else if (ownerLen < 256)
7012 		{
7013 			VectorScale(dir, baseSpeed-100, saberent->s.pos.trDelta );
7014 		}
7015 		else
7016 		{
7017 			VectorScale(dir, baseSpeed, saberent->s.pos.trDelta );
7018 		}
7019 
7020 		saberent->s.pos.trTime = level.time;
7021 	}
7022 
7023 	/*
7024 	if (ownerLen <= 512)
7025 	{
7026 		saberent->s.saberInFlight = qfalse;
7027 		saberent->s.loopSound = saberHumSound;
7028 		saberent->s.loopIsSoundset = qfalse;
7029 	}
7030 	*/
7031 	//I'm just doing this now. I don't really like the spin on the way back. And it does weird stuff with the new saber-knocked-away code.
7032 	if (saberOwner->client->ps.saberEntityNum == saberent->s.number)
7033 	{
7034 		if ( !(saberOwner->client->saber[0].saberFlags&SFL_RETURN_DAMAGE)
7035 			|| saberOwner->client->ps.saberHolstered )
7036 		{
7037 			saberent->s.saberInFlight = qfalse;
7038 		}
7039 		saberent->s.loopSound = saberOwner->client->saber[0].soundLoop;
7040 		saberent->s.loopIsSoundset = qfalse;
7041 
7042 		if (ownerLen <= 32)
7043 		{
7044 			G_Sound( saberent, CHAN_AUTO, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
7045 
7046 			saberOwner->client->ps.saberInFlight = qfalse;
7047 			saberOwner->client->ps.saberEntityState = 0;
7048 			saberOwner->client->ps.saberCanThrow = qfalse;
7049 			saberOwner->client->ps.saberThrowDelay = level.time + 300;
7050 
7051 			saberent->touch = SaberGotHit;
7052 
7053 			saberent->think = SaberUpdateSelf;
7054 			saberent->genericValue5 = 0;
7055 			saberent->nextthink = level.time + 50;
7056 			WP_SaberRemoveG2Model( saberent );
7057 
7058 			return;
7059 		}
7060 
7061 		if (!saberent->s.saberInFlight)
7062 		{
7063 			saberCheckRadiusDamage(saberent, 1);
7064 		}
7065 		else
7066 		{
7067 			saberCheckRadiusDamage(saberent, 2);
7068 		}
7069 
7070 		saberMoveBack(saberent, qtrue);
7071 	}
7072 
7073 	saberent->nextthink = level.time;
7074 }
7075 
7076 void saberFirstThrown(gentity_t *saberent);
7077 
thrownSaberTouch(gentity_t * saberent,gentity_t * other,trace_t * trace)7078 void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace)
7079 {
7080 	gentity_t *hitEnt = other;
7081 
7082 	if (other && other->s.number == saberent->r.ownerNum)
7083 	{
7084 		return;
7085 	}
7086 	VectorClear(saberent->s.pos.trDelta);
7087 	saberent->s.pos.trTime = level.time;
7088 
7089 	saberent->s.apos.trType = TR_LINEAR;
7090 	saberent->s.apos.trDelta[0] = 0;
7091 	saberent->s.apos.trDelta[1] = 800;
7092 	saberent->s.apos.trDelta[2] = 0;
7093 
7094 	VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
7095 
7096 	saberent->think = saberBackToOwner;
7097 	saberent->nextthink = level.time;
7098 
7099 	if (other && other->r.ownerNum < MAX_CLIENTS &&
7100 		(other->r.contents & CONTENTS_LIGHTSABER) &&
7101 		g_entities[other->r.ownerNum].client &&
7102 		g_entities[other->r.ownerNum].inuse)
7103 	{
7104 		hitEnt = &g_entities[other->r.ownerNum];
7105 	}
7106 
7107 	//we'll skip the dist check, since we don't really care about that (we just hit it physically)
7108 	CheckThrownSaberDamaged(saberent, &g_entities[saberent->r.ownerNum], hitEnt, 256, 0, qtrue);
7109 
7110 	saberent->speed = 0;
7111 }
7112 
7113 #define SABER_MAX_THROW_DISTANCE 700
7114 
saberFirstThrown(gentity_t * saberent)7115 void saberFirstThrown(gentity_t *saberent)
7116 {
7117 	vec3_t		vSub;
7118 	float		vLen;
7119 	gentity_t	*saberOwn = &g_entities[saberent->r.ownerNum];
7120 
7121 	if (saberent->r.ownerNum == ENTITYNUM_NONE)
7122 	{
7123 		MakeDeadSaber(saberent);
7124 
7125 		saberent->think = G_FreeEntity;
7126 		saberent->nextthink = level.time;
7127 		return;
7128 	}
7129 
7130 	if (!saberOwn ||
7131 		!saberOwn->inuse ||
7132 		!saberOwn->client ||
7133 		saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR)
7134 	{
7135 		MakeDeadSaber(saberent);
7136 
7137 		saberent->think = G_FreeEntity;
7138 		saberent->nextthink = level.time;
7139 		return;
7140 	}
7141 
7142 	if (saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
7143 	{ //He's dead, just go back to our normal saber status
7144 		saberent->touch = SaberGotHit;
7145 		saberent->think = SaberUpdateSelf;
7146 		saberent->genericValue5 = 0;
7147 		saberent->nextthink = level.time;
7148 
7149 		if (saberOwn->client &&
7150 			saberOwn->client->saber[0].soundOff)
7151 		{
7152 			G_Sound(saberent, CHAN_AUTO, saberOwn->client->saber[0].soundOff);
7153 		}
7154 		MakeDeadSaber(saberent);
7155 
7156 		saberent->r.svFlags |= (SVF_NOCLIENT);
7157 		saberent->r.contents = CONTENTS_LIGHTSABER;
7158 		SetSaberBoxSize(saberent);
7159 		saberent->s.loopSound = 0;
7160 		saberent->s.loopIsSoundset = qfalse;
7161 		WP_SaberRemoveG2Model( saberent );
7162 
7163 		saberOwn->client->ps.saberInFlight = qfalse;
7164 		saberOwn->client->ps.saberEntityState = 0;
7165 		saberOwn->client->ps.saberThrowDelay = level.time + 500;
7166 		saberOwn->client->ps.saberCanThrow = qfalse;
7167 
7168 		return;
7169 	}
7170 
7171 	if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 500)
7172 	{
7173 		if (!(saberOwn->client->buttons & BUTTON_ALT_ATTACK))
7174 		{ //If owner releases altattack 500ms or later after throwing saber, it autoreturns
7175 			thrownSaberTouch(saberent, saberent, NULL);
7176 			goto runMin;
7177 		}
7178 		else if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 6000)
7179 		{ //if it's out longer than 6 seconds, return it
7180 			thrownSaberTouch(saberent, saberent, NULL);
7181 			goto runMin;
7182 		}
7183 	}
7184 
7185 	if (BG_HasYsalamiri(level.gametype, &saberOwn->client->ps))
7186 	{
7187 		thrownSaberTouch(saberent, saberent, NULL);
7188 		goto runMin;
7189 	}
7190 
7191 	if (!BG_CanUseFPNow(level.gametype, &saberOwn->client->ps, level.time, FP_SABERTHROW))
7192 	{
7193 		thrownSaberTouch(saberent, saberent, NULL);
7194 		goto runMin;
7195 	}
7196 
7197 	VectorSubtract(saberOwn->client->ps.origin, saberent->r.currentOrigin, vSub);
7198 	vLen = VectorLength(vSub);
7199 
7200 	if (vLen >= (SABER_MAX_THROW_DISTANCE*saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW]))
7201 	{
7202 		thrownSaberTouch(saberent, saberent, NULL);
7203 		goto runMin;
7204 	}
7205 
7206 	if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_2 &&
7207 		saberent->speed < level.time)
7208 	{ //if owner is rank 3 in saber throwing, the saber goes where he points
7209 		vec3_t fwd, traceFrom, traceTo, dir;
7210 		trace_t tr;
7211 
7212 		AngleVectors(saberOwn->client->ps.viewangles, fwd, 0, 0);
7213 
7214 		VectorCopy(saberOwn->client->ps.origin, traceFrom);
7215 		traceFrom[2] += saberOwn->client->ps.viewheight;
7216 
7217 		VectorCopy(traceFrom, traceTo);
7218 		traceTo[0] += fwd[0]*4096;
7219 		traceTo[1] += fwd[1]*4096;
7220 		traceTo[2] += fwd[2]*4096;
7221 
7222 		saberMoveBack(saberent, qfalse);
7223 		VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase);
7224 
7225 		if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
7226 		{ //if highest saber throw rank, we can direct the saber toward players directly by looking at them
7227 			trap->Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_PLAYERSOLID, qfalse, 0, 0);
7228 		}
7229 		else
7230 		{
7231 			trap->Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_SOLID, qfalse, 0, 0);
7232 		}
7233 
7234 		VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir);
7235 
7236 		VectorNormalize(dir);
7237 
7238 		VectorScale(dir, 500, saberent->s.pos.trDelta );
7239 		saberent->s.pos.trTime = level.time;
7240 
7241 		if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3)
7242 		{ //we'll treat them to a quicker update rate if their throw rank is high enough
7243 			saberent->speed = level.time + 100;
7244 		}
7245 		else
7246 		{
7247 			saberent->speed = level.time + 400;
7248 		}
7249 	}
7250 
7251 runMin:
7252 
7253 	saberCheckRadiusDamage(saberent, 0);
7254 	G_RunObject(saberent);
7255 }
7256 
UpdateClientRenderBolts(gentity_t * self,vec3_t renderOrigin,vec3_t renderAngles)7257 void UpdateClientRenderBolts(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles)
7258 {
7259 	mdxaBone_t boltMatrix;
7260 	renderInfo_t *ri = &self->client->renderInfo;
7261 
7262 	if (!self->ghoul2)
7263 	{
7264 		VectorCopy(self->client->ps.origin, ri->headPoint);
7265 		VectorCopy(self->client->ps.origin, ri->handRPoint);
7266 		VectorCopy(self->client->ps.origin, ri->handLPoint);
7267 		VectorCopy(self->client->ps.origin, ri->torsoPoint);
7268 		VectorCopy(self->client->ps.origin, ri->crotchPoint);
7269 		VectorCopy(self->client->ps.origin, ri->footRPoint);
7270 		VectorCopy(self->client->ps.origin, ri->footLPoint);
7271 	}
7272 	else
7273 	{
7274 		//head
7275 		trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7276 		ri->headPoint[0] = boltMatrix.matrix[0][3];
7277 		ri->headPoint[1] = boltMatrix.matrix[1][3];
7278 		ri->headPoint[2] = boltMatrix.matrix[2][3];
7279 
7280 		//right hand
7281 		trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7282 		ri->handRPoint[0] = boltMatrix.matrix[0][3];
7283 		ri->handRPoint[1] = boltMatrix.matrix[1][3];
7284 		ri->handRPoint[2] = boltMatrix.matrix[2][3];
7285 
7286 		//left hand
7287 		trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7288 		ri->handLPoint[0] = boltMatrix.matrix[0][3];
7289 		ri->handLPoint[1] = boltMatrix.matrix[1][3];
7290 		ri->handLPoint[2] = boltMatrix.matrix[2][3];
7291 
7292 		//chest
7293 		trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7294 		ri->torsoPoint[0] = boltMatrix.matrix[0][3];
7295 		ri->torsoPoint[1] = boltMatrix.matrix[1][3];
7296 		ri->torsoPoint[2] = boltMatrix.matrix[2][3];
7297 
7298 		//crotch
7299 		trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7300 		ri->crotchPoint[0] = boltMatrix.matrix[0][3];
7301 		ri->crotchPoint[1] = boltMatrix.matrix[1][3];
7302 		ri->crotchPoint[2] = boltMatrix.matrix[2][3];
7303 
7304 		//right foot
7305 		trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7306 		ri->footRPoint[0] = boltMatrix.matrix[0][3];
7307 		ri->footRPoint[1] = boltMatrix.matrix[1][3];
7308 		ri->footRPoint[2] = boltMatrix.matrix[2][3];
7309 
7310 		//left foot
7311 		trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7312 		ri->footLPoint[0] = boltMatrix.matrix[0][3];
7313 		ri->footLPoint[1] = boltMatrix.matrix[1][3];
7314 		ri->footLPoint[2] = boltMatrix.matrix[2][3];
7315 	}
7316 
7317 	self->client->renderInfo.boltValidityTime = level.time;
7318 }
7319 
UpdateClientRenderinfo(gentity_t * self,vec3_t renderOrigin,vec3_t renderAngles)7320 void UpdateClientRenderinfo(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles)
7321 {
7322 	renderInfo_t *ri = &self->client->renderInfo;
7323 	if ( ri->mPCalcTime < level.time )
7324 	{
7325 		//We're just going to give rough estimates on most of this stuff,
7326 		//it's not like most of it matters.
7327 
7328 	#if 0 //#if 0'd since it's a waste setting all this to 0 each frame.
7329 		//Should you wish to make any of this valid then feel free to do so.
7330 		ri->headYawRangeLeft = ri->headYawRangeRight = ri->headPitchRangeUp = ri->headPitchRangeDown = 0;
7331 		ri->torsoYawRangeLeft = ri->torsoYawRangeRight = ri->torsoPitchRangeUp = ri->torsoPitchRangeDown = 0;
7332 
7333 		ri->torsoFpsMod = ri->legsFpsMod = 0;
7334 
7335 		VectorClear(ri->customRGB);
7336 		ri->customAlpha = 0;
7337 		ri->renderFlags = 0;
7338 		ri->lockYaw = 0;
7339 
7340 		VectorClear(ri->headAngles);
7341 		VectorClear(ri->torsoAngles);
7342 
7343 		//VectorClear(ri->eyeAngles);
7344 
7345 		ri->legsYaw = 0;
7346 	#endif
7347 
7348 		if (self->ghoul2 &&
7349 			self->ghoul2 != ri->lastG2)
7350 		{ //the g2 instance changed, so update all the bolts.
7351 			//rwwFIXMEFIXME: Base on skeleton used? Assuming humanoid currently.
7352 			ri->lastG2 = self->ghoul2;
7353 
7354 			if (self->localAnimIndex <= 1)
7355 			{
7356 				ri->headBolt = trap->G2API_AddBolt(self->ghoul2, 0, "*head_eyes");
7357 				ri->handRBolt = trap->G2API_AddBolt(self->ghoul2, 0, "*r_hand");
7358 				ri->handLBolt = trap->G2API_AddBolt(self->ghoul2, 0, "*l_hand");
7359 				ri->torsoBolt = trap->G2API_AddBolt(self->ghoul2, 0, "thoracic");
7360 				ri->crotchBolt = trap->G2API_AddBolt(self->ghoul2, 0, "pelvis");
7361 				ri->footRBolt = trap->G2API_AddBolt(self->ghoul2, 0, "*r_leg_foot");
7362 				ri->footLBolt = trap->G2API_AddBolt(self->ghoul2, 0, "*l_leg_foot");
7363 				ri->motionBolt = trap->G2API_AddBolt(self->ghoul2, 0, "Motion");
7364 			}
7365 			else
7366 			{
7367 				ri->headBolt = -1;
7368 				ri->handRBolt = -1;
7369 				ri->handLBolt = -1;
7370 				ri->torsoBolt = -1;
7371 				ri->crotchBolt = -1;
7372 				ri->footRBolt = -1;
7373 				ri->footLBolt = -1;
7374 				ri->motionBolt = -1;
7375 			}
7376 
7377 			ri->lastG2 = self->ghoul2;
7378 		}
7379 
7380 		VectorCopy( self->client->ps.viewangles, self->client->renderInfo.eyeAngles );
7381 
7382 		//we'll just say the legs/torso are whatever the first frame of our current anim is.
7383 		ri->torsoFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].firstFrame;
7384 		ri->legsFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.legsAnim].firstFrame;
7385 		if (g_debugServerSkel.integer)
7386 		{	//Alright, I was doing this, but it's just too slow to do every frame.
7387 			//From now on if we want this data to be valid we're going to have to make a verify call for it before
7388 			//accessing it. I'm only doing this now if we want to debug the server skel by drawing lines from bolt
7389 			//positions every frame.
7390 			mdxaBone_t boltMatrix;
7391 
7392 			if (!self->ghoul2)
7393 			{
7394 				VectorCopy(self->client->ps.origin, ri->headPoint);
7395 				VectorCopy(self->client->ps.origin, ri->handRPoint);
7396 				VectorCopy(self->client->ps.origin, ri->handLPoint);
7397 				VectorCopy(self->client->ps.origin, ri->torsoPoint);
7398 				VectorCopy(self->client->ps.origin, ri->crotchPoint);
7399 				VectorCopy(self->client->ps.origin, ri->footRPoint);
7400 				VectorCopy(self->client->ps.origin, ri->footLPoint);
7401 			}
7402 			else
7403 			{
7404 				//head
7405 				trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7406 				ri->headPoint[0] = boltMatrix.matrix[0][3];
7407 				ri->headPoint[1] = boltMatrix.matrix[1][3];
7408 				ri->headPoint[2] = boltMatrix.matrix[2][3];
7409 
7410 				//right hand
7411 				trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7412 				ri->handRPoint[0] = boltMatrix.matrix[0][3];
7413 				ri->handRPoint[1] = boltMatrix.matrix[1][3];
7414 				ri->handRPoint[2] = boltMatrix.matrix[2][3];
7415 
7416 				//left hand
7417 				trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7418 				ri->handLPoint[0] = boltMatrix.matrix[0][3];
7419 				ri->handLPoint[1] = boltMatrix.matrix[1][3];
7420 				ri->handLPoint[2] = boltMatrix.matrix[2][3];
7421 
7422 				//chest
7423 				trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7424 				ri->torsoPoint[0] = boltMatrix.matrix[0][3];
7425 				ri->torsoPoint[1] = boltMatrix.matrix[1][3];
7426 				ri->torsoPoint[2] = boltMatrix.matrix[2][3];
7427 
7428 				//crotch
7429 				trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7430 				ri->crotchPoint[0] = boltMatrix.matrix[0][3];
7431 				ri->crotchPoint[1] = boltMatrix.matrix[1][3];
7432 				ri->crotchPoint[2] = boltMatrix.matrix[2][3];
7433 
7434 				//right foot
7435 				trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7436 				ri->footRPoint[0] = boltMatrix.matrix[0][3];
7437 				ri->footRPoint[1] = boltMatrix.matrix[1][3];
7438 				ri->footRPoint[2] = boltMatrix.matrix[2][3];
7439 
7440 				//left foot
7441 				trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale);
7442 				ri->footLPoint[0] = boltMatrix.matrix[0][3];
7443 				ri->footLPoint[1] = boltMatrix.matrix[1][3];
7444 				ri->footLPoint[2] = boltMatrix.matrix[2][3];
7445 			}
7446 
7447 			//Now draw the skel for debug
7448 			G_TestLine(ri->headPoint, ri->torsoPoint, 0x000000ff, 50);
7449 			G_TestLine(ri->torsoPoint, ri->handRPoint, 0x000000ff, 50);
7450 			G_TestLine(ri->torsoPoint, ri->handLPoint, 0x000000ff, 50);
7451 			G_TestLine(ri->torsoPoint, ri->crotchPoint, 0x000000ff, 50);
7452 			G_TestLine(ri->crotchPoint, ri->footRPoint, 0x000000ff, 50);
7453 			G_TestLine(ri->crotchPoint, ri->footLPoint, 0x000000ff, 50);
7454 		}
7455 
7456 		//muzzle point calc (we are going to be cheap here)
7457 		VectorCopy(ri->muzzlePoint, ri->muzzlePointOld);
7458 		VectorCopy(self->client->ps.origin, ri->muzzlePoint);
7459 		VectorCopy(ri->muzzleDir, ri->muzzleDirOld);
7460 		AngleVectors(self->client->ps.viewangles, ri->muzzleDir, 0, 0);
7461 		ri->mPCalcTime = level.time;
7462 
7463 		VectorCopy(self->client->ps.origin, ri->eyePoint);
7464 		ri->eyePoint[2] += self->client->ps.viewheight;
7465 	}
7466 }
7467 
7468 #define STAFF_KICK_RANGE 16
7469 extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ); //NPC_utils.c
7470 
7471 extern qboolean BG_InKnockDown( int anim );
G_KickDownable(gentity_t * ent)7472 static qboolean G_KickDownable(gentity_t *ent)
7473 {
7474 	if (!d_saberKickTweak.integer)
7475 	{
7476 		return qtrue;
7477 	}
7478 
7479 	if (!ent || !ent->inuse || !ent->client)
7480 	{
7481 		return qfalse;
7482 	}
7483 
7484 	if (BG_InKnockDown(ent->client->ps.legsAnim) ||
7485 		BG_InKnockDown(ent->client->ps.torsoAnim))
7486 	{
7487 		return qfalse;
7488 	}
7489 
7490 	if (ent->client->ps.weaponTime <= 0 &&
7491 		ent->client->ps.weapon == WP_SABER &&
7492 		ent->client->ps.groundEntityNum != ENTITYNUM_NONE)
7493 	{
7494 		return qfalse;
7495 	}
7496 
7497 	return qtrue;
7498 }
7499 
G_TossTheMofo(gentity_t * ent,vec3_t tossDir,float tossStr)7500 static void G_TossTheMofo(gentity_t *ent, vec3_t tossDir, float tossStr)
7501 {
7502 	if (!ent->inuse || !ent->client)
7503 	{ //no good
7504 		return;
7505 	}
7506 
7507 	if (ent->s.eType == ET_NPC && ent->s.NPC_class == CLASS_VEHICLE)
7508 	{ //no, silly
7509 		return;
7510 	}
7511 
7512 	VectorMA(ent->client->ps.velocity, tossStr, tossDir, ent->client->ps.velocity);
7513 	ent->client->ps.velocity[2] = 200;
7514 	if (ent->health > 0 && ent->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN &&
7515 		BG_KnockDownable(&ent->client->ps) &&
7516 		G_KickDownable(ent))
7517 	{ //if they are alive, knock them down I suppose
7518 		ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
7519 		ent->client->ps.forceHandExtendTime = level.time + 700;
7520 		ent->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim
7521 		//ent->client->ps.quickerGetup = qtrue;
7522 	}
7523 }
7524 
G_KickTrace(gentity_t * ent,vec3_t kickDir,float kickDist,vec3_t kickEnd,int kickDamage,float kickPush)7525 static gentity_t *G_KickTrace( gentity_t *ent, vec3_t kickDir, float kickDist, vec3_t kickEnd, int kickDamage, float kickPush )
7526 {
7527 	vec3_t	traceOrg, traceEnd, kickMins, kickMaxs;
7528 	trace_t	trace;
7529 	gentity_t	*hitEnt = NULL;
7530 	VectorSet(kickMins, -2.0f, -2.0f, -2.0f);
7531 	VectorSet(kickMaxs, 2.0f, 2.0f, 2.0f);
7532 	//FIXME: variable kick height?
7533 	if ( kickEnd && !VectorCompare( kickEnd, vec3_origin ) )
7534 	{//they passed us the end point of the trace, just use that
7535 		//this makes the trace flat
7536 		VectorSet( traceOrg, ent->r.currentOrigin[0], ent->r.currentOrigin[1], kickEnd[2] );
7537 		VectorCopy( kickEnd, traceEnd );
7538 	}
7539 	else
7540 	{//extrude
7541 		VectorSet( traceOrg, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2]+ent->r.maxs[2]*0.5f );
7542 		VectorMA( traceOrg, kickDist, kickDir, traceEnd );
7543 	}
7544 
7545 	if (d_saberKickTweak.integer)
7546 	{
7547 		trap->Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
7548 	}
7549 	else
7550 	{
7551 		trap->Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT, qfalse, 0, 0 );
7552 	}
7553 
7554 	//G_TestLine(traceOrg, traceEnd, 0x0000ff, 5000);
7555 	if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid )
7556 	{
7557 		if (ent->client->jediKickTime > level.time)
7558 		{
7559 			if (trace.entityNum == ent->client->jediKickIndex)
7560 			{ //we are hitting the same ent we last hit in this same anim, don't hit it again
7561 				return NULL;
7562 			}
7563 		}
7564 		ent->client->jediKickIndex = trace.entityNum;
7565 		ent->client->jediKickTime = level.time + ent->client->ps.legsTimer;
7566 
7567 		hitEnt = &g_entities[trace.entityNum];
7568 		//FIXME: regardless of what we hit, do kick hit sound and impact effect
7569 		//G_PlayEffect( "misc/kickHit", trace.endpos, trace.plane.normal );
7570 		if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
7571 		{
7572 			G_Sound( ent, CHAN_AUTO, G_SoundIndex( "sound/movers/objects/saber_slam" ) );
7573 		}
7574 		else
7575 		{
7576 			G_Sound( ent, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
7577 		}
7578 		if ( hitEnt->inuse )
7579 		{//we hit an entity
7580 			//FIXME: don't hit same ent more than once per kick
7581 			if ( hitEnt->takedamage )
7582 			{//hurt it
7583 				if (hitEnt->client)
7584 				{
7585 					hitEnt->client->ps.otherKiller = ent->s.number;
7586 					hitEnt->client->ps.otherKillerDebounceTime = level.time + 10000;
7587 					hitEnt->client->ps.otherKillerTime = level.time + 10000;
7588 				}
7589 
7590 				if (d_saberKickTweak.integer)
7591 				{
7592 					G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage*0.2f, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
7593 				}
7594 				else
7595 				{
7596 					G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage, DAMAGE_NO_KNOCKBACK, MOD_MELEE );
7597 				}
7598 			}
7599 			if ( hitEnt->client
7600 				&& !(hitEnt->client->ps.pm_flags&PMF_TIME_KNOCKBACK) //not already flying through air?  Intended to stop multiple hits, but...
7601 				&& G_CanBeEnemy(ent, hitEnt) )
7602 			{//FIXME: this should not always work
7603 				if ( hitEnt->health <= 0 )
7604 				{//we kicked a dead guy
7605 					//throw harder - FIXME: no matter how hard I push them, they don't go anywhere... corpses use less physics???
7606 				//	G_Throw( hitEnt, kickDir, kickPush*4 );
7607 					//see if we should play a better looking death on them
7608 				//	G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos );
7609 					G_TossTheMofo(hitEnt, kickDir, kickPush*4.0f);
7610 				}
7611 				else
7612 				{
7613 					/*
7614 					G_Throw( hitEnt, kickDir, kickPush );
7615 					if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) )
7616 					{
7617 						G_Knockdown( hitEnt, ent, kickDir, 300, qtrue );
7618 					}
7619 					else
7620 					{
7621 						G_Knockdown( hitEnt, ent, kickDir, kickPush, qtrue );
7622 					}
7623 					*/
7624 					if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) )
7625 					{
7626 						G_TossTheMofo(hitEnt, kickDir, 300.0f);
7627 					}
7628 					else
7629 					{
7630 						G_TossTheMofo(hitEnt, kickDir, kickPush);
7631 					}
7632 				}
7633 			}
7634 		}
7635 	}
7636 	return (hitEnt);
7637 }
7638 
G_KickSomeMofos(gentity_t * ent)7639 static void G_KickSomeMofos(gentity_t *ent)
7640 {
7641 	vec3_t	kickDir, kickEnd, fwdAngs;
7642 	float animLength = BG_AnimLength( ent->localAnimIndex, (animNumber_t)ent->client->ps.legsAnim );
7643 	float elapsedTime = (float)(animLength-ent->client->ps.legsTimer);
7644 	float remainingTime = (animLength-elapsedTime);
7645 	float kickDist = (ent->r.maxs[0]*1.5f)+STAFF_KICK_RANGE+8.0f;//fudge factor of 8
7646 	int	  kickDamage = Q_irand(10, 15);//Q_irand( 3, 8 ); //since it can only hit a guy once now
7647 	int	  kickPush = flrand( 50.0f, 100.0f );
7648 	qboolean doKick = qfalse;
7649 	renderInfo_t *ri = &ent->client->renderInfo;
7650 
7651 	VectorSet(kickDir, 0.0f, 0.0f, 0.0f);
7652 	VectorSet(kickEnd, 0.0f, 0.0f, 0.0f);
7653 	VectorSet(fwdAngs, 0.0f, ent->client->ps.viewangles[YAW], 0.0f);
7654 
7655 	//HMM... or maybe trace from origin to footRBolt/footLBolt?  Which one?  G2 trace?  Will do hitLoc, if so...
7656 	if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
7657 	{
7658 		if ( elapsedTime >= 250 && remainingTime >= 250 )
7659 		{//front
7660 			doKick = qtrue;
7661 			if ( ri->handRBolt != -1 )
7662 			{//actually trace to a bolt
7663 				G_GetBoltPosition( ent, ri->handRBolt, kickEnd, 0 );
7664 				VectorSubtract( kickEnd, ent->client->ps.origin, kickDir );
7665 				kickDir[2] = 0;//ah, flatten it, I guess...
7666 				VectorNormalize( kickDir );
7667 			}
7668 			else
7669 			{//guess
7670 				AngleVectors( fwdAngs, kickDir, NULL, NULL );
7671 			}
7672 		}
7673 	}
7674 	else
7675 	{
7676 		switch ( ent->client->ps.legsAnim )
7677 		{
7678 		case BOTH_GETUP_BROLL_B:
7679 		case BOTH_GETUP_BROLL_F:
7680 		case BOTH_GETUP_FROLL_B:
7681 		case BOTH_GETUP_FROLL_F:
7682 			if ( elapsedTime >= 250 && remainingTime >= 250 )
7683 			{//front
7684 				doKick = qtrue;
7685 				if ( ri->footRBolt != -1 )
7686 				{//actually trace to a bolt
7687 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7688 					VectorSubtract( kickEnd, ent->client->ps.origin, kickDir );
7689 					kickDir[2] = 0;//ah, flatten it, I guess...
7690 					VectorNormalize( kickDir );
7691 				}
7692 				else
7693 				{//guess
7694 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7695 				}
7696 			}
7697 			break;
7698 		case BOTH_A7_KICK_F_AIR:
7699 		case BOTH_A7_KICK_B_AIR:
7700 		case BOTH_A7_KICK_R_AIR:
7701 		case BOTH_A7_KICK_L_AIR:
7702 			if ( elapsedTime >= 100 && remainingTime >= 250 )
7703 			{//air
7704 				doKick = qtrue;
7705 				if ( ri->footRBolt != -1 )
7706 				{//actually trace to a bolt
7707 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7708 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7709 					kickDir[2] = 0;//ah, flatten it, I guess...
7710 					VectorNormalize( kickDir );
7711 				}
7712 				else
7713 				{//guess
7714 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7715 				}
7716 			}
7717 			break;
7718 		case BOTH_A7_KICK_F:
7719 			//FIXME: push forward?
7720 			if ( elapsedTime >= 250 && remainingTime >= 250 )
7721 			{//front
7722 				doKick = qtrue;
7723 				if ( ri->footRBolt != -1 )
7724 				{//actually trace to a bolt
7725 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7726 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7727 					kickDir[2] = 0;//ah, flatten it, I guess...
7728 					VectorNormalize( kickDir );
7729 				}
7730 				else
7731 				{//guess
7732 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7733 				}
7734 			}
7735 			break;
7736 		case BOTH_A7_KICK_B:
7737 			//FIXME: push back?
7738 			if ( elapsedTime >= 250 && remainingTime >= 250 )
7739 			{//back
7740 				doKick = qtrue;
7741 				if ( ri->footRBolt != -1 )
7742 				{//actually trace to a bolt
7743 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7744 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7745 					kickDir[2] = 0;//ah, flatten it, I guess...
7746 					VectorNormalize( kickDir );
7747 				}
7748 				else
7749 				{//guess
7750 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7751 					VectorScale( kickDir, -1, kickDir );
7752 				}
7753 			}
7754 			break;
7755 		case BOTH_A7_KICK_R:
7756 			//FIXME: push right?
7757 			if ( elapsedTime >= 250 && remainingTime >= 250 )
7758 			{//right
7759 				doKick = qtrue;
7760 				if ( ri->footRBolt != -1 )
7761 				{//actually trace to a bolt
7762 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7763 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7764 					kickDir[2] = 0;//ah, flatten it, I guess...
7765 					VectorNormalize( kickDir );
7766 				}
7767 				else
7768 				{//guess
7769 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7770 				}
7771 			}
7772 			break;
7773 		case BOTH_A7_KICK_L:
7774 			//FIXME: push left?
7775 			if ( elapsedTime >= 250 && remainingTime >= 250 )
7776 			{//left
7777 				doKick = qtrue;
7778 				if ( ri->footLBolt != -1 )
7779 				{//actually trace to a bolt
7780 					G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 );
7781 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7782 					kickDir[2] = 0;//ah, flatten it, I guess...
7783 					VectorNormalize( kickDir );
7784 				}
7785 				else
7786 				{//guess
7787 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7788 					VectorScale( kickDir, -1, kickDir );
7789 				}
7790 			}
7791 			break;
7792 		case BOTH_A7_KICK_S:
7793 			kickPush = flrand( 75.0f, 125.0f );
7794 			if ( ri->footRBolt != -1 )
7795 			{//actually trace to a bolt
7796 				if ( elapsedTime >= 550
7797 					&& elapsedTime <= 1050 )
7798 				{
7799 					doKick = qtrue;
7800 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7801 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7802 					kickDir[2] = 0;//ah, flatten it, I guess...
7803 					VectorNormalize( kickDir );
7804 					//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
7805 					VectorMA( kickEnd, 8.0f, kickDir, kickEnd );
7806 				}
7807 			}
7808 			else
7809 			{//guess
7810 				if ( elapsedTime >= 400 && elapsedTime < 500 )
7811 				{//front
7812 					doKick = qtrue;
7813 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7814 				}
7815 				else if ( elapsedTime >= 500 && elapsedTime < 600 )
7816 				{//front-right?
7817 					doKick = qtrue;
7818 					fwdAngs[YAW] += 45;
7819 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7820 				}
7821 				else if ( elapsedTime >= 600 && elapsedTime < 700 )
7822 				{//right
7823 					doKick = qtrue;
7824 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7825 				}
7826 				else if ( elapsedTime >= 700 && elapsedTime < 800 )
7827 				{//back-right?
7828 					doKick = qtrue;
7829 					fwdAngs[YAW] += 45;
7830 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7831 				}
7832 				else if ( elapsedTime >= 800 && elapsedTime < 900 )
7833 				{//back
7834 					doKick = qtrue;
7835 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7836 					VectorScale( kickDir, -1, kickDir );
7837 				}
7838 				else if ( elapsedTime >= 900 && elapsedTime < 1000 )
7839 				{//back-left?
7840 					doKick = qtrue;
7841 					fwdAngs[YAW] += 45;
7842 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7843 				}
7844 				else if ( elapsedTime >= 1000 && elapsedTime < 1100 )
7845 				{//left
7846 					doKick = qtrue;
7847 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7848 					VectorScale( kickDir, -1, kickDir );
7849 				}
7850 				else if ( elapsedTime >= 1100 && elapsedTime < 1200 )
7851 				{//front-left?
7852 					doKick = qtrue;
7853 					fwdAngs[YAW] += 45;
7854 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7855 					VectorScale( kickDir, -1, kickDir );
7856 				}
7857 			}
7858 			break;
7859 		case BOTH_A7_KICK_BF:
7860 			kickPush = flrand( 75.0f, 125.0f );
7861 			kickDist += 20.0f;
7862 			if ( elapsedTime < 1500 )
7863 			{//auto-aim!
7864 	//			overridAngles = PM_AdjustAnglesForBFKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles;
7865 				//FIXME: if we haven't done the back kick yet and there's no-one there to
7866 				//			kick anymore, go into some anim that returns us to our base stance
7867 			}
7868 			if ( ri->footRBolt != -1 )
7869 			{//actually trace to a bolt
7870 				if ( ( elapsedTime >= 750 && elapsedTime < 850 )
7871 					|| ( elapsedTime >= 1400 && elapsedTime < 1500 ) )
7872 				{//right, though either would do
7873 					doKick = qtrue;
7874 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7875 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7876 					kickDir[2] = 0;//ah, flatten it, I guess...
7877 					VectorNormalize( kickDir );
7878 					//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
7879 					VectorMA( kickEnd, 8, kickDir, kickEnd );
7880 				}
7881 			}
7882 			else
7883 			{//guess
7884 				if ( elapsedTime >= 250 && elapsedTime < 350 )
7885 				{//front
7886 					doKick = qtrue;
7887 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7888 				}
7889 				else if ( elapsedTime >= 350 && elapsedTime < 450 )
7890 				{//back
7891 					doKick = qtrue;
7892 					AngleVectors( fwdAngs, kickDir, NULL, NULL );
7893 					VectorScale( kickDir, -1, kickDir );
7894 				}
7895 			}
7896 			break;
7897 		case BOTH_A7_KICK_RL:
7898 			kickPush = flrand( 75.0f, 125.0f );
7899 			kickDist += 10.0f;
7900 
7901 			//ok, I'm tracing constantly on these things, they NEVER hit otherwise (in MP at least)
7902 
7903 			//FIXME: auto aim at enemies on the side of us?
7904 			//overridAngles = PM_AdjustAnglesForRLKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles;
7905 			//if ( elapsedTime >= 250 && elapsedTime < 350 )
7906 			if (level.framenum&1)
7907 			{//right
7908 				doKick = qtrue;
7909 				if ( ri->footRBolt != -1 )
7910 				{//actually trace to a bolt
7911 					G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 );
7912 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7913 					kickDir[2] = 0;//ah, flatten it, I guess...
7914 					VectorNormalize( kickDir );
7915 					//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
7916 					VectorMA( kickEnd, 8, kickDir, kickEnd );
7917 				}
7918 				else
7919 				{//guess
7920 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7921 				}
7922 			}
7923 			//else if ( elapsedTime >= 350 && elapsedTime < 450 )
7924 			else
7925 			{//left
7926 				doKick = qtrue;
7927 				if ( ri->footLBolt != -1 )
7928 				{//actually trace to a bolt
7929 					G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 );
7930 					VectorSubtract( kickEnd, ent->r.currentOrigin, kickDir );
7931 					kickDir[2] = 0;//ah, flatten it, I guess...
7932 					VectorNormalize( kickDir );
7933 					//NOTE: have to fudge this a little because it's not getting enough range with the anim as-is
7934 					VectorMA( kickEnd, 8, kickDir, kickEnd );
7935 				}
7936 				else
7937 				{//guess
7938 					AngleVectors( fwdAngs, NULL, kickDir, NULL );
7939 					VectorScale( kickDir, -1, kickDir );
7940 				}
7941 			}
7942 			break;
7943 		}
7944 	}
7945 
7946 	if ( doKick )
7947 	{
7948 //		G_KickTrace( ent, kickDir, kickDist, kickEnd, kickDamage, kickPush );
7949 		G_KickTrace( ent, kickDir, kickDist, NULL, kickDamage, kickPush );
7950 	}
7951 }
7952 
G_PrettyCloseIGuess(float a,float b,float tolerance)7953 static QINLINE qboolean G_PrettyCloseIGuess(float a, float b, float tolerance)
7954 {
7955     if ((a-b) < tolerance &&
7956 		(a-b) > -tolerance)
7957 	{
7958 		return qtrue;
7959 	}
7960 
7961 	return qfalse;
7962 }
7963 
G_GrabSomeMofos(gentity_t * self)7964 static void G_GrabSomeMofos(gentity_t *self)
7965 {
7966 	renderInfo_t *ri = &self->client->renderInfo;
7967 	mdxaBone_t boltMatrix;
7968 	vec3_t flatAng;
7969 	vec3_t pos;
7970 	vec3_t grabMins, grabMaxs;
7971 	trace_t trace;
7972 
7973 	if (!self->ghoul2 || ri->handRBolt == -1)
7974 	{ //no good
7975 		return;
7976 	}
7977 
7978     VectorSet(flatAng, 0.0f, self->client->ps.viewangles[1], 0.0f);
7979 	trap->G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, flatAng, self->client->ps.origin,
7980 		level.time, NULL, self->modelScale);
7981 	BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pos);
7982 
7983 	VectorSet(grabMins, -4.0f, -4.0f, -4.0f);
7984 	VectorSet(grabMaxs, 4.0f, 4.0f, 4.0f);
7985 
7986 	//trace from my origin to my hand, if we hit anyone then get 'em
7987 	trap->Trace( &trace, self->client->ps.origin, grabMins, grabMaxs, pos, self->s.number, MASK_SHOT, qfalse, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
7988 
7989 	if (trace.fraction != 1.0f &&
7990 		trace.entityNum < ENTITYNUM_WORLD)
7991 	{
7992 		gentity_t *grabbed = &g_entities[trace.entityNum];
7993 
7994 		if (grabbed->inuse && (grabbed->s.eType == ET_PLAYER || grabbed->s.eType == ET_NPC) &&
7995 			grabbed->client && grabbed->health > 0 &&
7996 			G_CanBeEnemy(self, grabbed) &&
7997 			G_PrettyCloseIGuess(grabbed->client->ps.origin[2], self->client->ps.origin[2], 4.0f) &&
7998 			(!BG_InGrappleMove(grabbed->client->ps.torsoAnim) || grabbed->client->ps.torsoAnim == BOTH_KYLE_GRAB) &&
7999 			(!BG_InGrappleMove(grabbed->client->ps.legsAnim) || grabbed->client->ps.legsAnim == BOTH_KYLE_GRAB))
8000 		{ //grabbed an active player/npc
8001 			int tortureAnim = -1;
8002 			int correspondingAnim = -1;
8003 
8004 			if (self->client->pers.cmd.forwardmove > 0)
8005 			{ //punch grab
8006 				tortureAnim = BOTH_KYLE_PA_1;
8007 				correspondingAnim = BOTH_PLAYER_PA_1;
8008 			}
8009 			else if (self->client->pers.cmd.forwardmove < 0)
8010 			{ //knee-throw
8011 				tortureAnim = BOTH_KYLE_PA_2;
8012 				correspondingAnim = BOTH_PLAYER_PA_2;
8013 			}
8014 
8015 			if (tortureAnim == -1 || correspondingAnim == -1)
8016 			{
8017 				if (self->client->ps.torsoTimer < 300 && !self->client->grappleState)
8018 				{ //you failed to grab anyone, play the "failed to grab" anim
8019 					G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
8020 					if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
8021 					{ //providing the anim set succeeded..
8022 						self->client->ps.weaponTime = self->client->ps.torsoTimer;
8023 					}
8024 				}
8025 				return;
8026 			}
8027 
8028 			self->client->grappleIndex = grabbed->s.number;
8029 			self->client->grappleState = 1;
8030 
8031 			grabbed->client->grappleIndex = self->s.number;
8032 			grabbed->client->grappleState = 20;
8033 
8034 			//time to crack some heads
8035 			G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, tortureAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
8036 			if (self->client->ps.torsoAnim == tortureAnim)
8037 			{ //providing the anim set succeeded..
8038 				self->client->ps.weaponTime = self->client->ps.torsoTimer;
8039 			}
8040 
8041 			G_SetAnim(grabbed, &grabbed->client->pers.cmd, SETANIM_BOTH, correspondingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
8042 			if (grabbed->client->ps.torsoAnim == correspondingAnim)
8043 			{ //providing the anim set succeeded..
8044 				if (grabbed->client->ps.weapon == WP_SABER)
8045 				{ //turn it off
8046 					if (!grabbed->client->ps.saberHolstered)
8047 					{
8048 						grabbed->client->ps.saberHolstered = 2;
8049 						if (grabbed->client->saber[0].soundOff)
8050 						{
8051 							G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[0].soundOff);
8052 						}
8053 						if (grabbed->client->saber[1].soundOff &&
8054 							grabbed->client->saber[1].model[0])
8055 						{
8056 							G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[1].soundOff);
8057 						}
8058 					}
8059 				}
8060 				if (grabbed->client->ps.torsoTimer < self->client->ps.torsoTimer)
8061 				{ //make sure they stay in the anim at least as long as the grabber
8062 					grabbed->client->ps.torsoTimer = self->client->ps.torsoTimer;
8063 				}
8064 				grabbed->client->ps.weaponTime = grabbed->client->ps.torsoTimer;
8065 			}
8066 		}
8067 	}
8068 
8069 	if (self->client->ps.torsoTimer < 300 && !self->client->grappleState)
8070 	{ //you failed to grab anyone, play the "failed to grab" anim
8071 		G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
8072 		if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
8073 		{ //providing the anim set succeeded..
8074 			self->client->ps.weaponTime = self->client->ps.torsoTimer;
8075 		}
8076 	}
8077 }
8078 
WP_SaberPositionUpdate(gentity_t * self,usercmd_t * ucmd)8079 void WP_SaberPositionUpdate( gentity_t *self, usercmd_t *ucmd )
8080 { //rww - keep the saber position as updated as possible on the server so that we can try to do realistic-looking contact stuff
8081   //Note that this function also does the majority of working in maintaining the server g2 client instance (updating angles/anims/etc)
8082 	gentity_t *mySaber = NULL;
8083 	mdxaBone_t	boltMatrix;
8084 	vec3_t properAngles, properOrigin;
8085 	vec3_t boltAngles, boltOrigin;
8086 	vec3_t end;
8087 	matrix3_t legAxis;
8088 	vec3_t addVel;
8089 	vec3_t rawAngles;
8090 	float fVSpeed = 0;
8091 	int returnAfterUpdate = 0;
8092 	float animSpeedScale = 1.0f;
8093 	int saberNum;
8094 	qboolean clientOverride;
8095 	gentity_t *vehEnt = NULL;
8096 	int rSaberNum = 0;
8097 	int rBladeNum = 0;
8098 
8099 #ifdef _DEBUG
8100 	if (g_disableServerG2.integer)
8101 	{
8102 		return;
8103 	}
8104 #endif
8105 
8106 	if (self && self->inuse && self->client)
8107 	{
8108 		if (self->client->saberCycleQueue)
8109 		{
8110 			self->client->ps.fd.saberDrawAnimLevel = self->client->saberCycleQueue;
8111 		}
8112 		else
8113 		{
8114 			self->client->ps.fd.saberDrawAnimLevel = self->client->ps.fd.saberAnimLevel;
8115 		}
8116 	}
8117 
8118 	if (self &&
8119 		self->inuse &&
8120 		self->client &&
8121 		self->client->saberCycleQueue &&
8122 		(self->client->ps.weaponTime <= 0 || self->health < 1))
8123 	{ //we cycled attack levels while we were busy, so update now that we aren't (even if that means we're dead)
8124 		self->client->ps.fd.saberAnimLevel = self->client->saberCycleQueue;
8125 		self->client->saberCycleQueue = 0;
8126 	}
8127 
8128 	if (!self ||
8129 		!self->inuse ||
8130 		!self->client ||
8131 		!self->ghoul2 ||
8132 		!g2SaberInstance)
8133 	{
8134 		return;
8135 	}
8136 
8137 	if (BG_KickingAnim(self->client->ps.legsAnim))
8138 	{ //do some kick traces and stuff if we're in the appropriate anim
8139 		G_KickSomeMofos(self);
8140 	}
8141 	else if (self->client->ps.torsoAnim == BOTH_KYLE_GRAB)
8142 	{ //try to grab someone
8143 		G_GrabSomeMofos(self);
8144 	}
8145 	else if (self->client->grappleState)
8146 	{
8147 		gentity_t *grappler = &g_entities[self->client->grappleIndex];
8148 
8149 		if (!grappler->inuse || !grappler->client || grappler->client->grappleIndex != self->s.number ||
8150 			!BG_InGrappleMove(grappler->client->ps.torsoAnim) || !BG_InGrappleMove(grappler->client->ps.legsAnim) ||
8151 			!BG_InGrappleMove(self->client->ps.torsoAnim) || !BG_InGrappleMove(self->client->ps.legsAnim) ||
8152 			!self->client->grappleState || !grappler->client->grappleState ||
8153 			grappler->health < 1 || self->health < 1 ||
8154 			!G_PrettyCloseIGuess(self->client->ps.origin[2], grappler->client->ps.origin[2], 4.0f))
8155 		{
8156 			self->client->grappleState = 0;
8157 			if ((BG_InGrappleMove(self->client->ps.torsoAnim) && self->client->ps.torsoTimer > 100) ||
8158 				(BG_InGrappleMove(self->client->ps.legsAnim) && self->client->ps.legsTimer > 100))
8159 			{ //if they're pretty far from finishing the anim then shove them into another anim
8160 				G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
8161 				if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
8162 				{ //providing the anim set succeeded..
8163 					self->client->ps.weaponTime = self->client->ps.torsoTimer;
8164 				}
8165 			}
8166 		}
8167 		else
8168 		{
8169 			vec3_t grapAng;
8170 
8171 			VectorSubtract(grappler->client->ps.origin, self->client->ps.origin, grapAng);
8172 
8173 			if (VectorLength(grapAng) > 64.0f)
8174 			{ //too far away, break it off
8175 				if ((BG_InGrappleMove(self->client->ps.torsoAnim) && self->client->ps.torsoTimer > 100) ||
8176 					(BG_InGrappleMove(self->client->ps.legsAnim) && self->client->ps.legsTimer > 100))
8177 				{
8178 					self->client->grappleState = 0;
8179 
8180 					G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
8181 					if (self->client->ps.torsoAnim == BOTH_KYLE_MISS)
8182 					{ //providing the anim set succeeded..
8183 						self->client->ps.weaponTime = self->client->ps.torsoTimer;
8184 					}
8185 				}
8186 			}
8187 			else
8188 			{
8189 				vectoangles(grapAng, grapAng);
8190 				SetClientViewAngle(self, grapAng);
8191 
8192 				if (self->client->grappleState >= 20)
8193 				{ //grapplee
8194 					//try to position myself at the correct distance from my grappler
8195 					float idealDist;
8196 					vec3_t gFwd, idealSpot;
8197 					trace_t trace;
8198 
8199 					if (grappler->client->ps.torsoAnim == BOTH_KYLE_PA_1)
8200 					{ //grab punch
8201 						idealDist = 46.0f;
8202 					}
8203 					else
8204 					{ //knee-throw
8205 						idealDist = 34.0f;
8206 					}
8207 
8208 					AngleVectors(grappler->client->ps.viewangles, gFwd, 0, 0);
8209 					VectorMA(grappler->client->ps.origin, idealDist, gFwd, idealSpot);
8210 
8211 					trap->Trace(&trace, self->client->ps.origin, self->r.mins, self->r.maxs, idealSpot, self->s.number, self->clipmask, qfalse, 0, 0);
8212 					if (!trace.startsolid && !trace.allsolid && trace.fraction == 1.0f)
8213 					{ //go there
8214 						G_SetOrigin(self, idealSpot);
8215 						VectorCopy(idealSpot, self->client->ps.origin);
8216 					}
8217 				}
8218 				else if (self->client->grappleState >= 1)
8219 				{ //grappler
8220 					if (grappler->client->ps.weapon == WP_SABER)
8221 					{ //make sure their saber is shut off
8222 						if (!grappler->client->ps.saberHolstered)
8223 						{
8224 							grappler->client->ps.saberHolstered = 2;
8225 							if (grappler->client->saber[0].soundOff)
8226 							{
8227 								G_Sound(grappler, CHAN_AUTO, grappler->client->saber[0].soundOff);
8228 							}
8229 							if (grappler->client->saber[1].soundOff &&
8230 								grappler->client->saber[1].model[0])
8231 							{
8232 								G_Sound(grappler, CHAN_AUTO, grappler->client->saber[1].soundOff);
8233 							}
8234 						}
8235 					}
8236 
8237 					//check for smashy events
8238 					if (self->client->ps.torsoAnim == BOTH_KYLE_PA_1)
8239 					{ //grab punch
8240                         if (self->client->grappleState == 1)
8241 						{ //smack
8242 							if (self->client->ps.torsoTimer < 3400)
8243 							{
8244 								int grapplerAnim = grappler->client->ps.torsoAnim;
8245 								int grapplerTime = grappler->client->ps.torsoTimer;
8246 
8247 								G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE);
8248 								//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
8249 
8250 								//it might try to put them into a pain anim or something, so override it back again
8251 								if (grappler->health > 0)
8252 								{
8253 									grappler->client->ps.torsoAnim = grapplerAnim;
8254 									grappler->client->ps.torsoTimer = grapplerTime;
8255 									grappler->client->ps.legsAnim = grapplerAnim;
8256 									grappler->client->ps.legsTimer = grapplerTime;
8257 									grappler->client->ps.weaponTime = grapplerTime;
8258 								}
8259 								self->client->grappleState++;
8260 							}
8261 						}
8262 						else if (self->client->grappleState == 2)
8263 						{ //smack!
8264 							if (self->client->ps.torsoTimer < 2550)
8265 							{
8266 								int grapplerAnim = grappler->client->ps.torsoAnim;
8267 								int grapplerTime = grappler->client->ps.torsoTimer;
8268 
8269 								G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE);
8270 								//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
8271 
8272 								//it might try to put them into a pain anim or something, so override it back again
8273 								if (grappler->health > 0)
8274 								{
8275 									grappler->client->ps.torsoAnim = grapplerAnim;
8276 									grappler->client->ps.torsoTimer = grapplerTime;
8277 									grappler->client->ps.legsAnim = grapplerAnim;
8278 									grappler->client->ps.legsTimer = grapplerTime;
8279 									grappler->client->ps.weaponTime = grapplerTime;
8280 								}
8281 								self->client->grappleState++;
8282 							}
8283 						}
8284 						else
8285 						{ //SMACK!
8286 							if (self->client->ps.torsoTimer < 1300)
8287 							{
8288 								vec3_t tossDir;
8289 
8290 								G_Damage(grappler, self, self, NULL, self->client->ps.origin, 30, 0, MOD_MELEE);
8291 								//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
8292 
8293 								self->client->grappleState = 0;
8294 
8295 								VectorSubtract(grappler->client->ps.origin, self->client->ps.origin, tossDir);
8296 								VectorNormalize(tossDir);
8297 								VectorScale(tossDir, 500.0f, tossDir);
8298 								tossDir[2] = 200.0f;
8299 
8300 								VectorAdd(grappler->client->ps.velocity, tossDir, grappler->client->ps.velocity);
8301 
8302 								if (grappler->health > 0)
8303 								{ //if still alive knock them down
8304 									grappler->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
8305 									grappler->client->ps.forceHandExtendTime = level.time + 1300;
8306 								}
8307 							}
8308 						}
8309 					}
8310 					else if (self->client->ps.torsoAnim == BOTH_KYLE_PA_2)
8311 					{ //knee throw
8312                         if (self->client->grappleState == 1)
8313 						{ //knee to the face
8314 							if (self->client->ps.torsoTimer < 3200)
8315 							{
8316 								int grapplerAnim = grappler->client->ps.torsoAnim;
8317 								int grapplerTime = grappler->client->ps.torsoTimer;
8318 
8319 								G_Damage(grappler, self, self, NULL, self->client->ps.origin, 20, 0, MOD_MELEE);
8320 								//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
8321 
8322 								//it might try to put them into a pain anim or something, so override it back again
8323 								if (grappler->health > 0)
8324 								{
8325 									grappler->client->ps.torsoAnim = grapplerAnim;
8326 									grappler->client->ps.torsoTimer = grapplerTime;
8327 									grappler->client->ps.legsAnim = grapplerAnim;
8328 									grappler->client->ps.legsTimer = grapplerTime;
8329 									grappler->client->ps.weaponTime = grapplerTime;
8330 								}
8331 								self->client->grappleState++;
8332 							}
8333 						}
8334 						else if (self->client->grappleState == 2)
8335 						{ //smashed on the ground
8336 							if (self->client->ps.torsoTimer < 2000)
8337 							{
8338 								//G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE);
8339 								//don't do damage on this one, it would look very freaky if they died
8340 								G_EntitySound( grappler, CHAN_VOICE, G_SoundIndex("*pain100.wav") );
8341 								//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
8342 								self->client->grappleState++;
8343 							}
8344 						}
8345 						else
8346 						{ //and another smash
8347 							if (self->client->ps.torsoTimer < 1000)
8348 							{
8349 								G_Damage(grappler, self, self, NULL, self->client->ps.origin, 30, 0, MOD_MELEE);
8350 								//G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) );
8351 
8352 								//it might try to put them into a pain anim or something, so override it back again
8353 								if (grappler->health > 0)
8354 								{
8355 									grappler->client->ps.torsoTimer = 1000;
8356 									//G_SetAnim(grappler, &grappler->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP3, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
8357 									grappler->client->grappleState = 0;
8358 								}
8359 								else
8360 								{ //override death anim
8361 									grappler->client->ps.torsoAnim = BOTH_DEADFLOP1;
8362 									grappler->client->ps.legsAnim = BOTH_DEADFLOP1;
8363 								}
8364 
8365 								self->client->grappleState = 0;
8366 							}
8367 						}
8368 					}
8369 					else
8370 					{ //?
8371 					}
8372 				}
8373 			}
8374 		}
8375 	}
8376 
8377 	//If this is a listen server (client+server running on same machine),
8378 	//then lets try to steal the skeleton/etc data off the client instance
8379 	//for this entity to save us processing time.
8380 	clientOverride = trap->G2API_OverrideServer(self->ghoul2);
8381 
8382 	saberNum = self->client->ps.saberEntityNum;
8383 
8384 	if (!saberNum)
8385 	{
8386 		saberNum = self->client->saberStoredIndex;
8387 	}
8388 
8389 	if (!saberNum)
8390 	{
8391 		returnAfterUpdate = 1;
8392 		goto nextStep;
8393 	}
8394 
8395 	mySaber = &g_entities[saberNum];
8396 
8397 	if (self->health < 1)
8398 	{ //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..
8399 		//I guess it's good to keep the position updated even when contents are 0
8400 		if (mySaber && ((mySaber->r.contents & CONTENTS_LIGHTSABER) || mySaber->r.contents == 0) && !self->client->ps.saberInFlight)
8401 		{ //Since we haven't got a bolt position, place it on top of the player origin.
8402 			VectorCopy(self->client->ps.origin, mySaber->r.currentOrigin);
8403 		}
8404 
8405 		//I don't want to return now actually, I want to keep g2 instances for corpses up to
8406 		//date because I'm doing better corpse hit detection/dismem (particularly for the
8407 		//npc's)
8408 		//return;
8409 	}
8410 
8411 	if ( BG_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
8412 	{
8413 		self->client->ps.weaponstate = WEAPON_FIRING;
8414 	}
8415 	if (self->client->ps.weapon != WP_SABER ||
8416 		self->client->ps.weaponstate == WEAPON_RAISING ||
8417 		self->client->ps.weaponstate == WEAPON_DROPPING ||
8418 		self->health < 1)
8419 	{
8420 		if (!self->client->ps.saberInFlight)
8421 		{
8422 			returnAfterUpdate = 1;
8423 		}
8424 	}
8425 
8426 	if (self->client->ps.saberThrowDelay < level.time)
8427 	{
8428 		if ( (self->client->saber[0].saberFlags&SFL_NOT_THROWABLE) )
8429 		{//cant throw it normally!
8430 			if ( (self->client->saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE) )
8431 			{//but can throw it if only have 1 blade on
8432 				if ( self->client->saber[0].numBlades > 1
8433 					&& self->client->ps.saberHolstered == 1 )
8434 				{//have multiple blades and only one blade on
8435 					self->client->ps.saberCanThrow = qtrue;//qfalse;
8436 					//huh? want to be able to throw then right?
8437 				}
8438 				else
8439 				{//multiple blades on, can't throw
8440 					self->client->ps.saberCanThrow = qfalse;
8441 				}
8442 			}
8443 			else
8444 			{//never can throw it
8445 				self->client->ps.saberCanThrow = qfalse;
8446 			}
8447 		}
8448 		else
8449 		{//can throw it!
8450 			self->client->ps.saberCanThrow = qtrue;
8451 		}
8452 	}
8453 nextStep:
8454 	if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE))
8455 	{
8456 		animSpeedScale = 2;
8457 	}
8458 
8459 	VectorCopy(self->client->ps.origin, properOrigin);
8460 
8461 	//try to predict the origin based on velocity so it's more like what the client is seeing
8462 	VectorCopy(self->client->ps.velocity, addVel);
8463 	VectorNormalize(addVel);
8464 
8465 	if (self->client->ps.velocity[0] < 0)
8466 	{
8467 		fVSpeed += (-self->client->ps.velocity[0]);
8468 	}
8469 	else
8470 	{
8471 		fVSpeed += self->client->ps.velocity[0];
8472 	}
8473 	if (self->client->ps.velocity[1] < 0)
8474 	{
8475 		fVSpeed += (-self->client->ps.velocity[1]);
8476 	}
8477 	else
8478 	{
8479 		fVSpeed += self->client->ps.velocity[1];
8480 	}
8481 	if (self->client->ps.velocity[2] < 0)
8482 	{
8483 		fVSpeed += (-self->client->ps.velocity[2]);
8484 	}
8485 	else
8486 	{
8487 		fVSpeed += self->client->ps.velocity[2];
8488 	}
8489 
8490 	//fVSpeed *= 0.08;
8491 	fVSpeed *= 1.6f/sv_fps.value;
8492 
8493 	//Cap it off at reasonable values so the saber box doesn't go flying ahead of us or
8494 	//something if we get a big speed boost from something.
8495 	if (fVSpeed > 70)
8496 	{
8497 		fVSpeed = 70;
8498 	}
8499 	if (fVSpeed < -70)
8500 	{
8501 		fVSpeed = -70;
8502 	}
8503 
8504 	properOrigin[0] += addVel[0]*fVSpeed;
8505 	properOrigin[1] += addVel[1]*fVSpeed;
8506 	properOrigin[2] += addVel[2]*fVSpeed;
8507 
8508 	properAngles[0] = 0;
8509 	if (self->s.number < MAX_CLIENTS && self->client->ps.m_iVehicleNum)
8510 	{
8511 		vehEnt = &g_entities[self->client->ps.m_iVehicleNum];
8512 		if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle)
8513 		{
8514 			properAngles[1] = vehEnt->m_pVehicle->m_vOrientation[YAW];
8515 		}
8516 		else
8517 		{
8518 			properAngles[1] = self->client->ps.viewangles[YAW];
8519 			vehEnt = NULL;
8520 		}
8521 	}
8522 	else
8523 	{
8524 		properAngles[1] = self->client->ps.viewangles[YAW];
8525 	}
8526 	properAngles[2] = 0;
8527 
8528 	AnglesToAxis( properAngles, legAxis );
8529 
8530 	UpdateClientRenderinfo(self, properOrigin, properAngles);
8531 
8532 	if (!clientOverride)
8533 	{ //if we get the client instance we don't need to do this
8534 		G_G2PlayerAngles( self, legAxis, properAngles );
8535 	}
8536 
8537 	if (vehEnt)
8538 	{
8539 		properAngles[1] = vehEnt->m_pVehicle->m_vOrientation[YAW];
8540 	}
8541 
8542 	if (returnAfterUpdate && saberNum)
8543 	{ //We don't even need to do GetBoltMatrix if we're only in here to keep the g2 server instance in sync
8544 		//but keep our saber entity in sync too, just copy it over our origin.
8545 
8546 		//I guess it's good to keep the position updated even when contents are 0
8547 		if (mySaber && ((mySaber->r.contents & CONTENTS_LIGHTSABER) || mySaber->r.contents == 0) && !self->client->ps.saberInFlight)
8548 		{ //Since we haven't got a bolt position, place it on top of the player origin.
8549 			VectorCopy(self->client->ps.origin, mySaber->r.currentOrigin);
8550 		}
8551 
8552 		goto finalUpdate;
8553 	}
8554 
8555 	if (returnAfterUpdate)
8556 	{
8557 		goto finalUpdate;
8558 	}
8559 
8560 	//We'll get data for blade 0 first no matter what it is and stick them into
8561 	//the constant ("_Always") values. Later we will handle going through each blade.
8562 	trap->G2API_GetBoltMatrix(self->ghoul2, 1, 0, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
8563 	BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrigin);
8564 	BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, boltAngles);
8565 
8566 	//immediately store these values so we don't have to recalculate this again
8567 	if (self->client->lastSaberStorageTime && (level.time - self->client->lastSaberStorageTime) < 200)
8568 	{ //alright
8569 		VectorCopy(self->client->lastSaberBase_Always, self->client->olderSaberBase);
8570 		self->client->olderIsValid = qtrue;
8571 	}
8572 	else
8573 	{
8574 		self->client->olderIsValid = qfalse;
8575 	}
8576 
8577 	VectorCopy(boltOrigin, self->client->lastSaberBase_Always);
8578 	VectorCopy(boltAngles, self->client->lastSaberDir_Always);
8579 	self->client->lastSaberStorageTime = level.time;
8580 
8581 	VectorCopy(boltAngles, rawAngles);
8582 
8583 	VectorMA( boltOrigin, self->client->saber[0].blade[0].lengthMax, boltAngles, end );
8584 
8585 	if (self->client->ps.saberEntityNum)
8586 	{
8587 		//I guess it's good to keep the position updated even when contents are 0
8588 		if (mySaber && ((mySaber->r.contents & CONTENTS_LIGHTSABER) || mySaber->r.contents == 0) && !self->client->ps.saberInFlight)
8589 		{ //place it roughly in the middle of the saber..
8590 			VectorMA( boltOrigin, self->client->saber[0].blade[0].lengthMax, boltAngles, mySaber->r.currentOrigin );
8591 		}
8592 	}
8593 
8594 	boltAngles[YAW] = self->client->ps.viewangles[YAW];
8595 
8596 /*	{
8597 		static int lastDTime = 0;
8598 		if (lastDTime < level.time)
8599 		{
8600 			G_TestLine(boltOrigin, end, 0x0000ff, 200);
8601 			lastDTime = level.time + 200;
8602 		}
8603 	}
8604 */
8605 	if (self->client->ps.saberInFlight)
8606 	{ //do the thrown-saber stuff
8607 		gentity_t *saberent = &g_entities[saberNum];
8608 
8609 		if (saberent)
8610 		{
8611 			if (!self->client->ps.saberEntityState && self->client->ps.saberEntityNum)
8612 			{
8613 				vec3_t startorg, startang, dir;
8614 
8615 				VectorCopy(boltOrigin, saberent->r.currentOrigin);
8616 
8617 				VectorCopy(boltOrigin, startorg);
8618 				VectorCopy(boltAngles, startang);
8619 
8620 				//startang[0] = 90;
8621 				//Instead of this we'll sort of fake it and slowly tilt it down on the client via
8622 				//a perframe method (which doesn't actually affect where or how the saber hits)
8623 
8624 				saberent->r.svFlags &= ~(SVF_NOCLIENT);
8625 				VectorCopy(startorg, saberent->s.pos.trBase);
8626 				VectorCopy(startang, saberent->s.apos.trBase);
8627 
8628 				VectorCopy(startorg, saberent->s.origin);
8629 				VectorCopy(startang, saberent->s.angles);
8630 
8631 				saberent->s.saberInFlight = qtrue;
8632 
8633 				saberent->s.apos.trType = TR_LINEAR;
8634 				saberent->s.apos.trDelta[0] = 0;
8635 				saberent->s.apos.trDelta[1] = 800;
8636 				saberent->s.apos.trDelta[2] = 0;
8637 
8638 				saberent->s.pos.trType = TR_LINEAR;
8639 				saberent->s.eType = ET_GENERAL;
8640 				saberent->s.eFlags = 0;
8641 
8642 				WP_SaberAddG2Model( saberent, self->client->saber[0].model, self->client->saber[0].skin );
8643 
8644 				saberent->s.modelGhoul2 = 127;
8645 
8646 				saberent->parent = self;
8647 
8648 				self->client->ps.saberEntityState = 1;
8649 
8650 				//Projectile stuff:
8651 				AngleVectors(self->client->ps.viewangles, dir, NULL, NULL);
8652 
8653 				saberent->nextthink = level.time + FRAMETIME;
8654 				saberent->think = saberFirstThrown;
8655 
8656 				saberent->damage = SABER_THROWN_HIT_DAMAGE;
8657 				saberent->methodOfDeath = MOD_SABER;
8658 				saberent->splashMethodOfDeath = MOD_SABER;
8659 				saberent->s.solid = 2;
8660 				saberent->r.contents = CONTENTS_LIGHTSABER;
8661 
8662 				saberent->genericValue5 = 0;
8663 
8664 				VectorSet( saberent->r.mins, SABERMINS_X, SABERMINS_Y, SABERMINS_Z );
8665 				VectorSet( saberent->r.maxs, SABERMAXS_X, SABERMAXS_Y, SABERMAXS_Z );
8666 
8667 				saberent->s.genericenemyindex = self->s.number+1024;
8668 
8669 				saberent->touch = thrownSaberTouch;
8670 
8671 				saberent->s.weapon = WP_SABER;
8672 
8673 				VectorScale(dir, 400, saberent->s.pos.trDelta );
8674 				saberent->s.pos.trTime = level.time;
8675 
8676 				if ( self->client->saber[0].spinSound )
8677 				{
8678 					saberent->s.loopSound = self->client->saber[0].spinSound;
8679 				}
8680 				else
8681 				{
8682 					saberent->s.loopSound = saberSpinSound;
8683 				}
8684 				saberent->s.loopIsSoundset = qfalse;
8685 
8686 				self->client->ps.saberDidThrowTime = level.time;
8687 
8688 				self->client->dangerTime = level.time;
8689 				self->client->ps.eFlags &= ~EF_INVULNERABLE;
8690 				self->client->invulnerableTimer = 0;
8691 
8692 				trap->LinkEntity((sharedEntity_t *)saberent);
8693 			}
8694 			else if (self->client->ps.saberEntityNum) //only do this stuff if your saber is active and has not been knocked out of the air.
8695 			{
8696 				VectorCopy(boltOrigin, saberent->pos1);
8697 				trap->LinkEntity((sharedEntity_t *)saberent);
8698 
8699 				if (saberent->genericValue5 == PROPER_THROWN_VALUE)
8700 				{ //return to the owner now, this is a bad state to be in for here..
8701 					saberent->genericValue5 = 0;
8702 					saberent->think = SaberUpdateSelf;
8703 					saberent->nextthink = level.time;
8704 					WP_SaberRemoveG2Model( saberent );
8705 
8706 					self->client->ps.saberInFlight = qfalse;
8707 					self->client->ps.saberEntityState = 0;
8708 					self->client->ps.saberThrowDelay = level.time + 500;
8709 					self->client->ps.saberCanThrow = qfalse;
8710 				}
8711 			}
8712 		}
8713 	}
8714 
8715 	/*
8716 	if (self->client->ps.saberInFlight)
8717 	{ //if saber is thrown then only do the standard stuff for the left hand saber
8718 		rSaberNum = 1;
8719 	}
8720 	*/
8721 
8722 	if (!BG_SabersOff(&self->client->ps))
8723 	{
8724 		gentity_t *saberent = &g_entities[saberNum];
8725 
8726 		if (!self->client->ps.saberInFlight && saberent)
8727 		{
8728 			saberent->r.svFlags |= (SVF_NOCLIENT);
8729 			saberent->r.contents = CONTENTS_LIGHTSABER;
8730 			SetSaberBoxSize(saberent);
8731 			saberent->s.loopSound = 0;
8732 			saberent->s.loopIsSoundset = qfalse;
8733 		}
8734 
8735 		if (self->client->ps.saberLockTime > level.time && self->client->ps.saberEntityNum)
8736 		{
8737 			if (self->client->ps.saberIdleWound < level.time)
8738 			{
8739 				gentity_t *te;
8740 				vec3_t dir;
8741 				te = G_TempEntity( g_entities[saberNum].r.currentOrigin, EV_SABER_BLOCK );
8742 				VectorSet( dir, 0, 1, 0 );
8743 				VectorCopy(g_entities[saberNum].r.currentOrigin, te->s.origin);
8744 				VectorCopy(dir, te->s.angles);
8745 				te->s.eventParm = 1;
8746 				te->s.weapon = 0;//saberNum
8747 				te->s.legsAnim = 0;//bladeNum
8748 
8749 				self->client->ps.saberIdleWound = level.time + Q_irand(400, 600);
8750 			}
8751 
8752 			while (rSaberNum < MAX_SABERS)
8753 			{
8754 				rBladeNum = 0;
8755 				while (rBladeNum < self->client->saber[rSaberNum].numBlades)
8756 				{ //Don't bother updating the bolt for each blade for this, it's just a very rough fallback method for during saberlocks
8757 					VectorCopy(boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].trail.base);
8758 					VectorCopy(end, self->client->saber[rSaberNum].blade[rBladeNum].trail.tip);
8759 					self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime = level.time;
8760 
8761 					rBladeNum++;
8762 				}
8763 
8764 				rSaberNum++;
8765 			}
8766 			self->client->hasCurrentPosition = qtrue;
8767 
8768 			self->client->ps.saberBlocked = BLOCKED_NONE;
8769 
8770 			goto finalUpdate;
8771 		}
8772 
8773 		//reset it in case we used it for cycling before
8774 		rSaberNum = rBladeNum = 0;
8775 
8776 		if (self->client->ps.saberInFlight)
8777 		{ //if saber is thrown then only do the standard stuff for the left hand saber
8778 			if (!self->client->ps.saberEntityNum)
8779 			{ //however, if saber is not in flight but rather knocked away, our left saber is off, and thus we may do nothing.
8780 				rSaberNum = 1;//was 2?
8781 			}
8782 			else
8783 			{//thrown saber still in flight, so do damage
8784 				rSaberNum = 0;//was 1?
8785 			}
8786 		}
8787 
8788 		WP_SaberClearDamage();
8789 		saberDoClashEffect = qfalse;
8790 
8791 		//Now cycle through each saber and each blade on the saber and do damage traces.
8792 		while (rSaberNum < MAX_SABERS)
8793 		{
8794 			if (!self->client->saber[rSaberNum].model[0])
8795 			{
8796 				rSaberNum++;
8797 				continue;
8798 			}
8799 
8800 			/*
8801 			if (rSaberNum == 0 && (self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)))
8802 			{ //don't do saber 0 is the right arm is broken
8803 				rSaberNum++;
8804 				continue;
8805 			}
8806 			*/
8807 			//for now I'm keeping a broken right arm swingable, it will just look and act damaged
8808 			//but still be useable
8809 
8810 			if (rSaberNum == 1 && (self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
8811 			{ //don't to saber 1 if the left arm is broken
8812 				break;
8813 			}
8814 			if (rSaberNum > 0
8815 				&& self->client->saber[1].model[0]
8816 				&& self->client->ps.saberHolstered == 1 )
8817 			{ //don't to saber 2 if it's off
8818 				break;
8819 			}
8820 			rBladeNum = 0;
8821 			while (rBladeNum < self->client->saber[rSaberNum].numBlades)
8822 			{
8823 				//update muzzle data for the blade
8824 				VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePointOld);
8825 				VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDirOld);
8826 
8827 				if ( rBladeNum > 0 //more than one blade
8828 					&& (!self->client->saber[1].model[0])//not using dual blades
8829 					&& self->client->saber[rSaberNum].numBlades > 1//using a multi-bladed saber
8830 					&& self->client->ps.saberHolstered == 1 )//
8831 				{ //don't to extra blades if they're off
8832 					break;
8833 				}
8834 				//get the new data
8835 				//then update the bolt pos/dir. rBladeNum corresponds to the bolt index because blade bolts are added in order.
8836 				if ( rSaberNum == 0 && self->client->ps.saberInFlight )
8837 				{
8838 					if ( !self->client->ps.saberEntityNum )
8839 					{//dropped it... shouldn't get here, but...
8840 						//assert(0);
8841 						//FIXME: It's getting here a lot actually....
8842 						rSaberNum++;
8843 						rBladeNum = 0;
8844 						continue;
8845 					}
8846 					else
8847 					{
8848 						gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum];
8849 						vec3_t saberOrg, saberAngles;
8850 						if ( !saberEnt
8851 							|| !saberEnt->inuse
8852 							|| !saberEnt->ghoul2 )
8853 						{//wtf?
8854 							rSaberNum++;
8855 							rBladeNum = 0;
8856 							continue;
8857 						}
8858 						if ( saberent->s.saberInFlight )
8859 						{//spinning
8860 							BG_EvaluateTrajectory( &saberEnt->s.pos, level.time+50, saberOrg );
8861 							BG_EvaluateTrajectory( &saberEnt->s.apos, level.time+50, saberAngles );
8862 						}
8863 						else
8864 						{//coming right back
8865 							vec3_t saberDir;
8866 							BG_EvaluateTrajectory( &saberEnt->s.pos, level.time, saberOrg );
8867 							VectorSubtract( self->r.currentOrigin, saberOrg, saberDir );
8868 							vectoangles( saberDir, saberAngles );
8869 						}
8870 						trap->G2API_GetBoltMatrix(saberEnt->ghoul2, 0, rBladeNum, &boltMatrix, saberAngles, saberOrg, level.time, NULL, self->modelScale);
8871 						BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint);
8872 						BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir);
8873 						VectorCopy( self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, boltOrigin );
8874 						VectorMA( boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].lengthMax, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, end );
8875 					}
8876 
8877 				}
8878 				else
8879 				{
8880 					trap->G2API_GetBoltMatrix(self->ghoul2, rSaberNum+1, rBladeNum, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
8881 					BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint);
8882 					BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir);
8883 					VectorCopy( self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, boltOrigin );
8884 					VectorMA( boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].lengthMax, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, end );
8885 				}
8886 
8887 				self->client->saber[rSaberNum].blade[rBladeNum].storageTime = level.time;
8888 
8889 				if (self->client->hasCurrentPosition && d_saberInterpolate.integer)
8890 				{
8891 					if (self->client->ps.weaponTime <= 0)
8892 					{ //rww - 07/17/02 - don't bother doing the extra stuff unless actually attacking. This is in attempt to save CPU.
8893 						CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse);
8894 					}
8895 					else if (d_saberInterpolate.integer == 1)
8896 					{
8897 						int trMask = CONTENTS_LIGHTSABER|CONTENTS_BODY;
8898 						int sN = 0;
8899 						qboolean gotHit = qfalse;
8900 						qboolean clientUnlinked[MAX_CLIENTS];
8901 						qboolean skipSaberTrace = qfalse;
8902 
8903 						if (!g_saberTraceSaberFirst.integer)
8904 						{
8905 							skipSaberTrace = qtrue;
8906 						}
8907 						else if (g_saberTraceSaberFirst.integer >= 2 &&
8908 							level.gametype != GT_DUEL &&
8909 							level.gametype != GT_POWERDUEL &&
8910 							!self->client->ps.duelInProgress)
8911 						{ //if value is >= 2, and not in a duel, skip
8912 							skipSaberTrace = qtrue;
8913 						}
8914 
8915 						if (skipSaberTrace)
8916 						{ //skip the saber-contents-only trace and get right to the full trace
8917 							trMask = (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT);
8918 						}
8919 						else
8920 						{
8921 							while (sN < MAX_CLIENTS)
8922 							{
8923 								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))
8924 								{ //Take this mask off before the saber trace, because we want to hit the saber first
8925 									g_entities[sN].r.contents &= ~CONTENTS_BODY;
8926 									clientUnlinked[sN] = qtrue;
8927 								}
8928 								else
8929 								{
8930 									clientUnlinked[sN] = qfalse;
8931 								}
8932 								sN++;
8933 							}
8934 						}
8935 
8936 						while (!gotHit)
8937 						{
8938 							if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, trMask, qfalse))
8939 							{
8940 								if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qtrue, trMask, qfalse))
8941 								{
8942 									vec3_t oldSaberStart;
8943 									vec3_t oldSaberEnd;
8944 									vec3_t saberAngleNow;
8945 									vec3_t saberAngleBefore;
8946 									vec3_t saberMidDir;
8947 									vec3_t saberMidAngle;
8948 									vec3_t saberMidPoint;
8949 									vec3_t saberMidEnd;
8950 									vec3_t saberSubBase;
8951 									float deltaX, deltaY, deltaZ;
8952 
8953 									if ( (level.time-self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime) > 100 )
8954 									{//no valid last pos, use current
8955 										VectorCopy(boltOrigin, oldSaberStart);
8956 										VectorCopy(end, oldSaberEnd);
8957 									}
8958 									else
8959 									{//trace from last pos
8960 										VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart);
8961 										VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd);
8962 									}
8963 
8964 									VectorSubtract(oldSaberEnd, oldSaberStart, saberAngleBefore);
8965 									vectoangles(saberAngleBefore, saberAngleBefore);
8966 
8967 									VectorSubtract(end, boltOrigin, saberAngleNow);
8968 									vectoangles(saberAngleNow, saberAngleNow);
8969 
8970 									deltaX = AngleDelta(saberAngleBefore[0], saberAngleNow[0]);
8971 									deltaY = AngleDelta(saberAngleBefore[1], saberAngleNow[1]);
8972 									deltaZ = AngleDelta(saberAngleBefore[2], saberAngleNow[2]);
8973 
8974 									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)) )
8975 									{ //don't go beyond here if we aren't attacking/transitioning or the angle is too large.
8976 									//and don't bother if the angle is the same
8977 										saberMidAngle[0] = saberAngleBefore[0] + (deltaX/2);
8978 										saberMidAngle[1] = saberAngleBefore[1] + (deltaY/2);
8979 										saberMidAngle[2] = saberAngleBefore[2] + (deltaZ/2);
8980 
8981 										//Now that I have the angle, I'll just say the base for it is the difference between the two start
8982 										//points (even though that's quite possibly completely false)
8983 										VectorSubtract(boltOrigin, oldSaberStart, saberSubBase);
8984 										saberMidPoint[0] = boltOrigin[0] + (saberSubBase[0]*0.5);
8985 										saberMidPoint[1] = boltOrigin[1] + (saberSubBase[1]*0.5);
8986 										saberMidPoint[2] = boltOrigin[2] + (saberSubBase[2]*0.5);
8987 
8988 										AngleVectors(saberMidAngle, saberMidDir, 0, 0);
8989 										saberMidEnd[0] = saberMidPoint[0] + saberMidDir[0]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax;
8990 										saberMidEnd[1] = saberMidPoint[1] + saberMidDir[1]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax;
8991 										saberMidEnd[2] = saberMidPoint[2] + saberMidDir[2]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax;
8992 
8993 										//I'll just trace straight out and not even trace between positions to save speed.
8994 										if (CheckSaberDamage(self, rSaberNum, rBladeNum, saberMidPoint, saberMidEnd, qfalse, trMask, qfalse))
8995 										{
8996 											gotHit = qtrue;
8997 										}
8998 									}
8999 								}
9000 								else
9001 								{
9002 									gotHit = qtrue;
9003 								}
9004 							}
9005 							else
9006 							{
9007 								gotHit = qtrue;
9008 							}
9009 
9010 							if (g_saberTraceSaberFirst.integer)
9011 							{
9012 								sN = 0;
9013 								while (sN < MAX_CLIENTS)
9014 								{
9015 									if (clientUnlinked[sN])
9016 									{ //Make clients clip properly again.
9017 										if (g_entities[sN].inuse && g_entities[sN].health > 0)
9018 										{
9019 											g_entities[sN].r.contents |= CONTENTS_BODY;
9020 										}
9021 									}
9022 									sN++;
9023 								}
9024 							}
9025 
9026 							if (!gotHit)
9027 							{
9028 								if (trMask != (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT))
9029 								{
9030 									trMask = (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT);
9031 								}
9032 								else
9033 								{
9034 									gotHit = qtrue; //break out of the loop
9035 								}
9036 							}
9037 						}
9038 					}
9039 					else if (d_saberInterpolate.integer) //anything but 0 or 1, use the old plain method.
9040 					{
9041 						if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse))
9042 						{
9043 							CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qtrue, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse);
9044 						}
9045 					}
9046 				}
9047 				else if ( d_saberSPStyleDamage.integer )
9048 				{
9049 					G_SPSaberDamageTraceLerped( self, rSaberNum, rBladeNum, boltOrigin, end, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT) );
9050 				}
9051 				else
9052 				{
9053 					CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse);
9054 				}
9055 
9056 				VectorCopy(boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].trail.base);
9057 				VectorCopy(end, self->client->saber[rSaberNum].blade[rBladeNum].trail.tip);
9058 				self->client->saber[rSaberNum].blade[rBladeNum].trail.lastTime = level.time;
9059 				//VectorCopy(boltOrigin, self->client->lastSaberBase);
9060 				//VectorCopy(end, self->client->lastSaberTip);
9061 				self->client->hasCurrentPosition = qtrue;
9062 
9063 				//do hit effects
9064 				WP_SaberDoHit( self, rSaberNum, rBladeNum );
9065 				WP_SaberDoClash( self, rSaberNum, rBladeNum );
9066 
9067 				rBladeNum++;
9068 			}
9069 
9070 			rSaberNum++;
9071 		}
9072 
9073 		WP_SaberApplyDamage( self );
9074 		//NOTE: doing one call like this after the 2 loops above is a bit cheaper, tempentity-wise... but won't use the correct saber and blade numbers...
9075 		//now actually go through and apply all the damage we did
9076 		//WP_SaberDoHit( self, 0, 0 );
9077 		//WP_SaberDoClash( self, 0, 0 );
9078 
9079 		if (mySaber && mySaber->inuse)
9080 		{
9081 			trap->LinkEntity((sharedEntity_t *)mySaber);
9082 		}
9083 
9084 		if (!self->client->ps.saberInFlight)
9085 		{
9086 			self->client->ps.saberEntityState = 0;
9087 		}
9088 	}
9089 finalUpdate:
9090 	if (clientOverride)
9091 	{ //if we get the client instance we don't even need to bother setting anims and stuff
9092 		return;
9093 	}
9094 
9095 	G_UpdateClientAnims(self, animSpeedScale);
9096 }
9097 
WP_MissileBlockForBlock(int saberBlock)9098 int WP_MissileBlockForBlock( int saberBlock )
9099 {
9100 	switch( saberBlock )
9101 	{
9102 	case BLOCKED_UPPER_RIGHT:
9103 		return BLOCKED_UPPER_RIGHT_PROJ;
9104 		break;
9105 	case BLOCKED_UPPER_LEFT:
9106 		return BLOCKED_UPPER_LEFT_PROJ;
9107 		break;
9108 	case BLOCKED_LOWER_RIGHT:
9109 		return BLOCKED_LOWER_RIGHT_PROJ;
9110 		break;
9111 	case BLOCKED_LOWER_LEFT:
9112 		return BLOCKED_LOWER_LEFT_PROJ;
9113 		break;
9114 	case BLOCKED_TOP:
9115 		return BLOCKED_TOP_PROJ;
9116 		break;
9117 	}
9118 	return saberBlock;
9119 }
9120 
WP_SaberBlockNonRandom(gentity_t * self,vec3_t hitloc,qboolean missileBlock)9121 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock )
9122 {
9123 	vec3_t diff, fwdangles={0,0,0}, right;
9124 	vec3_t clEye;
9125 	float rightdot;
9126 	float zdiff;
9127 
9128 	VectorCopy(self->client->ps.origin, clEye);
9129 	clEye[2] += self->client->ps.viewheight;
9130 
9131 	VectorSubtract( hitloc, clEye, diff );
9132 	diff[2] = 0;
9133 	VectorNormalize( diff );
9134 
9135 	fwdangles[1] = self->client->ps.viewangles[1];
9136 	// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
9137 	AngleVectors( fwdangles, NULL, right, NULL );
9138 
9139 	rightdot = DotProduct(right, diff);
9140 	zdiff = hitloc[2] - clEye[2];
9141 
9142 	if ( zdiff > 0 )
9143 	{
9144 		if ( rightdot > 0.3 )
9145 		{
9146 			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
9147 		}
9148 		else if ( rightdot < -0.3 )
9149 		{
9150 			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
9151 		}
9152 		else
9153 		{
9154 			self->client->ps.saberBlocked = BLOCKED_TOP;
9155 		}
9156 	}
9157 	else if ( zdiff > -20 )//20 )
9158 	{
9159 		if ( zdiff < -10 )//30 )
9160 		{//hmm, pretty low, but not low enough to use the low block, so we need to duck
9161 
9162 		}
9163 		if ( rightdot > 0.1 )
9164 		{
9165 			self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
9166 		}
9167 		else if ( rightdot < -0.1 )
9168 		{
9169 			self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
9170 		}
9171 		else
9172 		{
9173 			self->client->ps.saberBlocked = BLOCKED_TOP;
9174 		}
9175 	}
9176 	else
9177 	{
9178 		if ( rightdot >= 0 )
9179 		{
9180 			self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
9181 		}
9182 		else
9183 		{
9184 			self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
9185 		}
9186 	}
9187 
9188 	if ( missileBlock )
9189 	{
9190 		self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
9191 	}
9192 }
9193 
WP_SaberBlock(gentity_t * playerent,vec3_t hitloc,qboolean missileBlock)9194 void WP_SaberBlock( gentity_t *playerent, vec3_t hitloc, qboolean missileBlock )
9195 {
9196 	vec3_t diff, fwdangles={0,0,0}, right;
9197 	float rightdot;
9198 	float zdiff;
9199 
9200 	VectorSubtract(hitloc, playerent->client->ps.origin, diff);
9201 	VectorNormalize(diff);
9202 
9203 	fwdangles[1] = playerent->client->ps.viewangles[1];
9204 	// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
9205 	AngleVectors( fwdangles, NULL, right, NULL );
9206 
9207 	rightdot = DotProduct(right, diff) + RandFloat(-0.2f,0.2f);
9208 	zdiff = hitloc[2] - playerent->client->ps.origin[2] + Q_irand(-8,8);
9209 
9210 	// Figure out what quadrant the block was in.
9211 	if (zdiff > 24)
9212 	{	// Attack from above
9213 		if (Q_irand(0,1))
9214 		{
9215 			playerent->client->ps.saberBlocked = BLOCKED_TOP;
9216 		}
9217 		else
9218 		{
9219 			playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
9220 		}
9221 	}
9222 	else if (zdiff > 13)
9223 	{	// The upper half has three viable blocks...
9224 		if (rightdot > 0.25)
9225 		{	// In the right quadrant...
9226 			if (Q_irand(0,1))
9227 			{
9228 				playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
9229 			}
9230 			else
9231 			{
9232 				playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
9233 			}
9234 		}
9235 		else
9236 		{
9237 			switch(Q_irand(0,3))
9238 			{
9239 			case 0:
9240 				playerent->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
9241 				break;
9242 			case 1:
9243 			case 2:
9244 				playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
9245 				break;
9246 			case 3:
9247 				playerent->client->ps.saberBlocked = BLOCKED_TOP;
9248 				break;
9249 			}
9250 		}
9251 	}
9252 	else
9253 	{	// The lower half is a bit iffy as far as block coverage.  Pick one of the "low" ones at random.
9254 		if (Q_irand(0,1))
9255 		{
9256 			playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
9257 		}
9258 		else
9259 		{
9260 			playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
9261 		}
9262 	}
9263 
9264 	if ( missileBlock )
9265 	{
9266 		playerent->client->ps.saberBlocked = WP_MissileBlockForBlock( playerent->client->ps.saberBlocked );
9267 	}
9268 }
9269 
WP_SaberCanBlock(gentity_t * self,vec3_t point,int dflags,int mod,qboolean projectile,int attackStr)9270 int WP_SaberCanBlock(gentity_t *self, vec3_t point, int dflags, int mod, qboolean projectile, int attackStr)
9271 {
9272 	qboolean thrownSaber = qfalse;
9273 	float blockFactor = 0;
9274 
9275 	if (!self || !self->client || !point)
9276 	{
9277 		return 0;
9278 	}
9279 
9280 	if (attackStr == 999)
9281 	{
9282 		attackStr = 0;
9283 		thrownSaber = qtrue;
9284 	}
9285 
9286 	if (BG_SaberInAttack(self->client->ps.saberMove))
9287 	{
9288 		return 0;
9289 	}
9290 
9291 	if (PM_InSaberAnim(self->client->ps.torsoAnim) && !self->client->ps.saberBlocked &&
9292 		self->client->ps.saberMove != LS_READY && self->client->ps.saberMove != LS_NONE)
9293 	{
9294 		if ( self->client->ps.saberMove < LS_PARRY_UP || self->client->ps.saberMove > LS_REFLECT_LL )
9295 		{
9296 			return 0;
9297 		}
9298 	}
9299 
9300 	if (PM_SaberInBrokenParry(self->client->ps.saberMove))
9301 	{
9302 		return 0;
9303 	}
9304 
9305 	if (!self->client->ps.saberEntityNum)
9306 	{ //saber is knocked away
9307 		return 0;
9308 	}
9309 
9310 	if (BG_SabersOff( &self->client->ps ))
9311 	{
9312 		return 0;
9313 	}
9314 
9315 	if (self->client->ps.weapon != WP_SABER)
9316 	{
9317 		return 0;
9318 	}
9319 
9320 	if (self->client->ps.weaponstate == WEAPON_RAISING)
9321 	{
9322 		return 0;
9323 	}
9324 
9325 	if (self->client->ps.saberInFlight)
9326 	{
9327 		return 0;
9328 	}
9329 
9330 	if ((self->client->pers.cmd.buttons & BUTTON_ATTACK)/* &&
9331 		(projectile || attackStr == FORCE_LEVEL_3)*/)
9332 	{ //don't block when the player is trying to slash, if it's a projectile or he's doing a very strong attack
9333 		return 0;
9334 	}
9335 
9336 	//Removed this for now, the new broken parry stuff should handle it. This is how
9337 	//blocks were decided before the 1.03 patch (as you can see, it was STUPID.. for the most part)
9338 	/*
9339 	if (attackStr == FORCE_LEVEL_3)
9340 	{
9341 		if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
9342 		{
9343 			if (Q_irand(1, 10) < 3)
9344 			{
9345 				return 0;
9346 			}
9347 		}
9348 		else
9349 		{
9350 			return 0;
9351 		}
9352 	}
9353 
9354 	if (attackStr == FORCE_LEVEL_2 && Q_irand(1, 10) < 3)
9355 	{
9356 		if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
9357 		{
9358 			//do nothing for now
9359 		}
9360 		else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_2)
9361 		{
9362 			if (Q_irand(1, 10) < 5)
9363 			{
9364 				return 0;
9365 			}
9366 		}
9367 		else
9368 		{
9369 			return 0;
9370 		}
9371 	}
9372 
9373 	if (attackStr == FORCE_LEVEL_1 && !self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] &&
9374 		Q_irand(1, 40) < 3)
9375 	{ //if I have no defense level at all then I might be unable to block a level 1 attack (but very rarely)
9376 		return 0;
9377 	}
9378 	*/
9379 
9380 	if (SaberAttacking(self))
9381 	{ //attacking, can't block now
9382 		return 0;
9383 	}
9384 
9385 	if (self->client->ps.saberMove != LS_READY &&
9386 		!self->client->ps.saberBlocking)
9387 	{
9388 		return 0;
9389 	}
9390 
9391 	if (self->client->ps.saberBlockTime >= level.time)
9392 	{
9393 		return 0;
9394 	}
9395 
9396 	if (self->client->ps.forceHandExtend != HANDEXTEND_NONE)
9397 	{
9398 		return 0;
9399 	}
9400 
9401 	if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == FORCE_LEVEL_3)
9402 	{
9403 		if (d_saberGhoul2Collision.integer)
9404 		{
9405 			blockFactor = 0.3f;
9406 		}
9407 		else
9408 		{
9409 			blockFactor = 0.05f;
9410 		}
9411 	}
9412 	else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == FORCE_LEVEL_2)
9413 	{
9414 		blockFactor = 0.6f;
9415 	}
9416 	else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == FORCE_LEVEL_1)
9417 	{
9418 		blockFactor = 0.9f;
9419 	}
9420 	else
9421 	{ //for now we just don't get to autoblock with no def
9422 		return 0;
9423 	}
9424 
9425 	if (thrownSaber)
9426 	{
9427 		blockFactor -= 0.25f;
9428 	}
9429 
9430 	if (attackStr)
9431 	{ //blocking a saber, not a projectile.
9432 		blockFactor -= 0.25f;
9433 	}
9434 
9435 	if (!InFront( point, self->client->ps.origin, self->client->ps.viewangles, blockFactor )) //orig 0.2f
9436 	{
9437 		return 0;
9438 	}
9439 
9440 	if (projectile)
9441 	{
9442 		WP_SaberBlockNonRandom(self, point, projectile);
9443 	}
9444 	return 1;
9445 }
9446 
HasSetSaberOnly(void)9447 qboolean HasSetSaberOnly(void)
9448 {
9449 	int i = 0;
9450 	int wDisable = 0;
9451 
9452 	if (level.gametype == GT_JEDIMASTER)
9453 	{ //set to 0
9454 		return qfalse;
9455 	}
9456 
9457 	if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
9458 	{
9459 		wDisable = g_duelWeaponDisable.integer;
9460 	}
9461 	else
9462 	{
9463 		wDisable = g_weaponDisable.integer;
9464 	}
9465 
9466 	while (i < WP_NUM_WEAPONS)
9467 	{
9468 		if (!(wDisable & (1 << i)) &&
9469 			i != WP_SABER && i != WP_NONE)
9470 		{
9471 			return qfalse;
9472 		}
9473 
9474 		i++;
9475 	}
9476 
9477 	return qtrue;
9478 }
9479