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