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 "anims.h"
25 #include "b_local.h"
26 #include "bg_local.h"
27 #include "g_functions.h"
28 #include "wp_saber.h"
29 #include "g_vehicles.h"
30 #include "../qcommon/tri_coll_test.h"
31 #include "../cgame/cg_local.h"
32
33 #define JK2_RAGDOLL_GRIPNOHEALTH
34
35 #define MAX_SABER_VICTIMS 16
36 static int victimEntityNum[MAX_SABER_VICTIMS];
37 static float totalDmg[MAX_SABER_VICTIMS];
38 static vec3_t dmgDir[MAX_SABER_VICTIMS];
39 static vec3_t dmgNormal[MAX_SABER_VICTIMS];
40 static vec3_t dmgBladeVec[MAX_SABER_VICTIMS];
41 static vec3_t dmgSpot[MAX_SABER_VICTIMS];
42 static float dmgFraction[MAX_SABER_VICTIMS];
43 static int hitLoc[MAX_SABER_VICTIMS];
44 static qboolean hitDismember[MAX_SABER_VICTIMS];
45 static int hitDismemberLoc[MAX_SABER_VICTIMS];
46 static vec3_t saberHitLocation, saberHitNormal={0,0,1.0};
47 static float saberHitFraction;
48 static float sabersCrossed;
49 static int saberHitEntity;
50 static int numVictims = 0;
51
52 extern cvar_t *g_sex;
53 extern cvar_t *g_timescale;
54 extern cvar_t *g_dismemberment;
55 extern cvar_t *g_debugSaberLock;
56 extern cvar_t *g_saberLockRandomNess;
57 extern cvar_t *d_slowmodeath;
58 extern cvar_t *g_cheats;
59 extern cvar_t *g_debugMelee;
60 extern cvar_t *g_saberRestrictForce;
61 extern cvar_t *g_saberPickuppableDroppedSabers;
62 extern cvar_t *debug_subdivision;
63
64
65 extern qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum );
66 extern qboolean WP_SaberBladeDoTransitionDamage( saberInfo_t *saber, int bladeNum );
67 extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
68 extern qboolean G_ClearViewEntity( gentity_t *ent );
69 extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
70 extern qboolean G_ControlledByPlayer( gentity_t *self );
71 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
72 extern void CG_ChangeWeapon( int num );
73 extern void CG_SaberDoWeaponHitMarks( gclient_t *client, gentity_t *saberEnt, gentity_t *hitEnt, int saberNum, int bladeNum, vec3_t hitPos, vec3_t hitDir, vec3_t uaxis, vec3_t splashBackDir, float sizeTimeScale );
74 extern void G_AngerAlert( gentity_t *self );
75 extern void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward );
76 extern int G_CheckLedgeDive( gentity_t *self, float checkDist, const vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp );
77 extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
78 extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs );
79 extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone );
80 extern void WP_FireDreadnoughtBeam( gentity_t *ent );
81 extern void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE );
82 extern evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f );
83 extern void Jedi_RageStop( gentity_t *self );
84 extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
85 extern void NPC_SetPainEvent( gentity_t *self );
86 extern qboolean PM_SwimmingAnim( int anim );
87 extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
88 extern qboolean PM_SpinningSaberAnim( int anim );
89 extern qboolean PM_SaberInSpecialAttack( int anim );
90 extern qboolean PM_SaberInAttack( int move );
91 extern qboolean PM_SaberInAttackPure( int move );
92 extern qboolean PM_SaberInTransition( int move );
93 extern qboolean PM_SaberInStart( int move );
94 extern qboolean PM_SaberInTransitionAny( int move );
95 extern qboolean PM_SaberInReturn( int move );
96 extern qboolean PM_SaberInBounce( int move );
97 extern qboolean PM_SaberInParry( int move );
98 extern qboolean PM_SaberInKnockaway( int move );
99 extern qboolean PM_SaberInBrokenParry( int move );
100 extern qboolean PM_SpinningSaberAnim( int anim );
101 extern saberMoveName_t PM_SaberBounceForAttack( int move );
102 extern saberMoveName_t PM_BrokenParryForAttack( int move );
103 extern saberMoveName_t PM_KnockawayForParry( int move );
104 extern qboolean PM_FlippingAnim( int anim );
105 extern qboolean PM_RollingAnim( int anim );
106 extern qboolean PM_CrouchAnim( int anim );
107 extern qboolean PM_SaberInIdle( int move );
108 extern qboolean PM_SaberInReflect( int move );
109 extern qboolean PM_InSpecialJump( int anim );
110 extern qboolean PM_InKnockDown( playerState_t *ps );
111 extern qboolean PM_ForceUsingSaberAnim( int anim );
112 extern qboolean PM_SuperBreakLoseAnim( int anim );
113 extern qboolean PM_SuperBreakWinAnim( int anim );
114 extern qboolean PM_SaberLockBreakAnim( int anim );
115 extern qboolean PM_InOnGroundAnim ( playerState_t *ps );
116 extern qboolean PM_KnockDownAnim( int anim );
117 extern qboolean PM_SaberInKata( saberMoveName_t saberMove );
118 extern qboolean PM_StabDownAnim( int anim );
119 extern int PM_PowerLevelForSaberAnim( playerState_t *ps, int saberNum = 0 );
120 extern void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir );
121 extern qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir );
122 extern qboolean PM_SaberCanInterruptMove( int move, int anim );
123 extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType );
124 extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc );
125 extern void Jedi_PlayDeflectSound( gentity_t *self );
126 extern void Jedi_PlayBlockedPushSound( gentity_t *self );
127 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
128 extern void Jedi_Ambush( gentity_t *self );
129 extern qboolean Jedi_SaberBusy( gentity_t *self );
130 extern qboolean Jedi_CultistDestroyer( gentity_t *self );
131 extern qboolean Boba_Flying( gentity_t *self );
132 extern void JET_FlyStart( gentity_t *self );
133 extern void Boba_DoFlameThrower( gentity_t *self );
134 extern void Boba_StopFlameThrower( gentity_t *self );
135
136 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
137 extern int SaberDroid_PowerLevelForSaberAnim( gentity_t *self );
138 extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy );
139 extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 );
140 extern int PM_AnimLength( int index, animNumber_t anim );
141 extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
142 extern void G_KnockOffVehicle( gentity_t *pRider, gentity_t *self, qboolean bPull );
143 extern qboolean PM_LockedAnim( int anim );
144 extern qboolean Rosh_BeingHealed( gentity_t *self );
145 extern qboolean G_OkayToLean( playerState_t *ps, usercmd_t *cmd, qboolean interruptOkay );
146
147 int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent);
148 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
149 void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower );
150 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
151 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd );
152
153 void WP_SaberDrop( gentity_t *self, gentity_t *saber );
154 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
155 void WP_SaberReturn( gentity_t *self, gentity_t *saber );
156 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
157 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
158 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
159 void WP_DeactivateSaber( gentity_t *self, qboolean clearLength = qfalse );
160 qboolean FP_ForceDrainGrippableEnt( gentity_t *victim );
161
162 extern cvar_t *g_saberAutoBlocking;
163 extern cvar_t *g_saberRealisticCombat;
164 extern cvar_t *g_saberDamageCapping;
165 extern cvar_t *g_saberNewControlScheme;
166 extern int g_crosshairEntNum;
167
168 qboolean g_saberNoEffects = qfalse;
169 qboolean g_noClashFlare = qfalse;
170 int g_saberFlashTime = 0;
171 vec3_t g_saberFlashPos = {0,0,0};
172
173 int forcePowerDarkLight[NUM_FORCE_POWERS] = //0 == neutral
174 { //nothing should be usable at rank 0..
175 FORCE_LIGHTSIDE,//FP_HEAL,//instant
176 0,//FP_LEVITATION,//hold/duration
177 0,//FP_SPEED,//duration
178 0,//FP_PUSH,//hold/duration
179 0,//FP_PULL,//hold/duration
180 FORCE_LIGHTSIDE,//FP_TELEPATHY,//instant
181 FORCE_DARKSIDE,//FP_GRIP,//hold/duration
182 FORCE_DARKSIDE,//FP_LIGHTNING,//hold/duration
183 0,//FP_SABERATTACK,
184 0,//FP_SABERDEFEND,
185 0,//FP_SABERTHROW,
186 //new Jedi Academy powers
187 FORCE_DARKSIDE,//FP_RAGE,//duration
188 FORCE_LIGHTSIDE,//FP_PROTECT,//duration
189 FORCE_LIGHTSIDE,//FP_ABSORB,//duration
190 FORCE_DARKSIDE,//FP_DRAIN,//hold/duration
191 0,//FP_SEE,//duration
192 //NUM_FORCE_POWERS
193 };
194
195 int forcePowerNeeded[NUM_FORCE_POWERS] =
196 {
197 0,//FP_HEAL,//instant
198 10,//FP_LEVITATION,//hold/duration
199 50,//FP_SPEED,//duration
200 15,//FP_PUSH,//hold/duration
201 15,//FP_PULL,//hold/duration
202 20,//FP_TELEPATHY,//instant
203 1,//FP_GRIP,//hold/duration - FIXME: 30?
204 1,//FP_LIGHTNING,//hold/duration
205 20,//FP_SABERTHROW,
206 1,//FP_SABER_DEFENSE,
207 0,//FP_SABER_OFFENSE,
208 //new Jedi Academy powers
209 50,//FP_RAGE,//duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards.
210 30,//FP_PROTECT,//duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions)
211 30,//FP_ABSORB,//duration - protect against dark force powers (grip, lightning, drain)
212 1,//FP_DRAIN,//hold/duration - drain force power for health
213 20//FP_SEE,//duration - detect/see hidden enemies
214 //NUM_FORCE_POWERS
215 };
216
217 float forceJumpStrength[NUM_FORCE_POWER_LEVELS] =
218 {
219 JUMP_VELOCITY,//normal jump
220 420,
221 590,
222 840
223 };
224
225 float forceJumpHeight[NUM_FORCE_POWER_LEVELS] =
226 {
227 32,//normal jump (+stepheight+crouchdiff = 66)
228 96,//(+stepheight+crouchdiff = 130)
229 192,//(+stepheight+crouchdiff = 226)
230 384//(+stepheight+crouchdiff = 418)
231 };
232
233 float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] =
234 {
235 66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74)
236 130,//(96+stepheight(18)+crouchdiff(24) = 138)
237 226,//(192+stepheight(18)+crouchdiff(24) = 234)
238 418//(384+stepheight(18)+crouchdiff(24) = 426)
239 };
240
241 float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] =
242 {
243 0,//none
244 384,//256,
245 448,//384,
246 512
247 };
248
249 float forcePushCone[NUM_FORCE_POWER_LEVELS] =
250 {
251 1.0f,//none
252 1.0f,
253 0.8f,
254 0.6f
255 };
256
257 float forcePullCone[NUM_FORCE_POWER_LEVELS] =
258 {
259 1.0f,//none
260 1.0f,
261 1.0f,
262 0.8f
263 };
264
265 float forceSpeedValue[NUM_FORCE_POWER_LEVELS] =
266 {
267 1.0f,//none
268 0.75f,
269 0.5f,
270 0.25f
271 };
272
273 float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] =
274 {
275 0.0f,//none
276 30.0f,
277 45.0f,
278 60.0f
279 };
280
281 float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] =
282 {
283 0.0f,//none
284 20.0f,
285 30.0f,
286 40.0f
287 };
288
289 int forceGripDamage[NUM_FORCE_POWER_LEVELS] =
290 {
291 0,//none
292 0,
293 6,
294 9
295 };
296
297 int mindTrickTime[NUM_FORCE_POWER_LEVELS] =
298 {
299 0,//none
300 10000,//5000,
301 15000,//10000,
302 30000//15000
303 };
304
305 //NOTE: keep in synch with table below!!!
306 int saberThrowDist[NUM_FORCE_POWER_LEVELS] =
307 {
308 0,//none
309 256,
310 400,
311 400
312 };
313
314 //NOTE: keep in synch with table above!!!
315 int saberThrowDistSquared[NUM_FORCE_POWER_LEVELS] =
316 {
317 0,//none
318 65536,
319 160000,
320 160000
321 };
322
323 int parryDebounce[NUM_FORCE_POWER_LEVELS] =
324 {
325 500,//if don't even have defense, can't use defense!
326 300,
327 150,
328 50
329 };
330
331 float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS] =
332 {
333 0.0f,//if don't even have offense, can't use offense!
334 0.75f,
335 1.0f,
336 2.0f
337 };
338
339 stringID_table_t SaberStyleTable[] =
340 {
341 { "NULL",SS_NONE },
342 ENUM2STRING(SS_FAST),
343 { "fast",SS_FAST },
344 ENUM2STRING(SS_MEDIUM),
345 { "medium",SS_MEDIUM },
346 ENUM2STRING(SS_STRONG),
347 { "strong",SS_STRONG },
348 ENUM2STRING(SS_DESANN),
349 { "desann",SS_DESANN },
350 ENUM2STRING(SS_TAVION),
351 { "tavion",SS_TAVION },
352 ENUM2STRING(SS_DUAL),
353 { "dual",SS_DUAL },
354 ENUM2STRING(SS_STAFF),
355 { "staff",SS_STAFF },
356 { "", 0 },
357 };
358
359 //SABER INITIALIZATION======================================================================
360
G_CreateG2AttachedWeaponModel(gentity_t * ent,const char * psWeaponModel,int boltNum,int weaponNum)361 void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel, int boltNum, int weaponNum )
362 {
363 if (!psWeaponModel)
364 {
365 assert (psWeaponModel);
366 return;
367 }
368 if ( ent->playerModel == -1 )
369 {
370 return;
371 }
372 if ( boltNum == -1 )
373 {
374 return;
375 }
376
377 if ( ent && ent->client && ent->client->NPC_class == CLASS_GALAKMECH )
378 {//hack for galakmech, no weaponmodel
379 ent->weaponModel[0] = ent->weaponModel[1] = -1;
380 return;
381 }
382 if ( weaponNum < 0 || weaponNum >= MAX_INHAND_WEAPONS )
383 {
384 return;
385 }
386 char weaponModel[64];
387
388 strcpy (weaponModel, psWeaponModel);
389 if (char *spot = strstr(weaponModel, ".md3") ) {
390 *spot = 0;
391 spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
392 if (!spot&&!strstr(weaponModel, "noweap"))
393 {
394 strcat (weaponModel, "_w");
395 }
396 strcat (weaponModel, ".glm"); //and change to ghoul2
397 }
398
399 // give us a saber model
400 int wModelIndex = G_ModelIndex( weaponModel );
401 if ( wModelIndex )
402 {
403 ent->weaponModel[weaponNum] = gi.G2API_InitGhoul2Model(ent->ghoul2, weaponModel, wModelIndex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
404 if ( ent->weaponModel[weaponNum] != -1 )
405 {
406 // attach it to the hand
407 gi.G2API_AttachG2Model(&ent->ghoul2[ent->weaponModel[weaponNum]], &ent->ghoul2[ent->playerModel],
408 boltNum, ent->playerModel);
409 // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
410 gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel[weaponNum]], "*flash");
411 //gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel[weaponNum]], 0 );
412 }
413 }
414
415 }
416
WP_SaberAddG2SaberModels(gentity_t * ent,int specificSaberNum)417 void WP_SaberAddG2SaberModels( gentity_t *ent, int specificSaberNum )
418 {
419 int saberNum = 0, maxSaber = 1;
420 if ( specificSaberNum != -1 && specificSaberNum <= maxSaber )
421 {
422 saberNum = maxSaber = specificSaberNum;
423 }
424 for ( ; saberNum <= maxSaber; saberNum++ )
425 {
426 if ( ent->weaponModel[saberNum] > 0 )
427 {//we already have a weapon model in this slot
428 //remove it
429 gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], -1, 0 );
430 gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[saberNum] );
431 ent->weaponModel[saberNum] = -1;
432 }
433 if ( saberNum > 0 )
434 {//second saber
435 if ( !ent->client->ps.dualSabers
436 || G_IsRidingVehicle( ent ) )
437 {//only have one saber or riding a vehicle and can only use one saber
438 return;
439 }
440 }
441 else if ( saberNum == 0 )
442 {//first saber
443 if ( ent->client->ps.saberInFlight )
444 {//it's still out there somewhere, don't add it
445 //FIXME: call it back?
446 continue;
447 }
448 }
449 int handBolt = ((saberNum == 0) ? ent->handRBolt : ent->handLBolt);
450 if ( (ent->client->ps.saber[saberNum].saberFlags&SFL_BOLT_TO_WRIST) )
451 {//special case, bolt to forearm
452 if ( saberNum == 0 )
453 {
454 handBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*r_hand_cap_r_arm" );
455 }
456 else
457 {
458 handBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*l_hand_cap_l_arm" );
459 }
460 }
461 G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[saberNum].model, handBolt, saberNum );
462
463 if ( ent->client->ps.saber[saberNum].skin != NULL )
464 {//if this saber has a customSkin, use it
465 // lets see if it's out there
466 int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[saberNum].skin );
467 if ( saberSkin )
468 {
469 // put it in the config strings
470 // and set the ghoul2 model to use it
471 gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], G_SkinIndex( ent->client->ps.saber[saberNum].skin ), saberSkin );
472 }
473 }
474 }
475 }
476
477 //----------------------------------------------------------
G_Throw(gentity_t * targ,const vec3_t newDir,float push)478 void G_Throw( gentity_t *targ, const vec3_t newDir, float push )
479 //----------------------------------------------------------
480 {
481 vec3_t kvel;
482 float mass;
483
484 if ( targ
485 && targ->client
486 && ( targ->client->NPC_class == CLASS_ATST
487 || targ->client->NPC_class == CLASS_RANCOR
488 || targ->client->NPC_class == CLASS_SAND_CREATURE ) )
489 {//much to large to *ever* throw
490 return;
491 }
492
493 if ( targ->physicsBounce > 0 ) //overide the mass
494 {
495 mass = targ->physicsBounce;
496 }
497 else
498 {
499 mass = 200;
500 }
501
502 if ( g_gravity->value > 0 )
503 {
504 VectorScale( newDir, g_knockback->value * (float)push / mass * 0.8, kvel );
505 if ( !targ->client || targ->client->ps.groundEntityNum != ENTITYNUM_NONE )
506 {//give them some z lift to get them off the ground
507 kvel[2] = newDir[2] * g_knockback->value * (float)push / mass * 1.5;
508 }
509 }
510 else
511 {
512 VectorScale( newDir, g_knockback->value * (float)push / mass, kvel );
513 }
514
515 if ( targ->client )
516 {
517 VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
518 }
519 else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
520 {
521 VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
522 VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
523 targ->s.pos.trTime = level.time;
524 }
525
526 // set the timer so that the other client can't cancel
527 // out the movement immediately
528 if ( targ->client && !targ->client->ps.pm_time )
529 {
530 int t;
531
532 t = push * 2;
533
534 if ( t < 50 )
535 {
536 t = 50;
537 }
538 if ( t > 200 )
539 {
540 t = 200;
541 }
542 targ->client->ps.pm_time = t;
543 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
544 }
545 }
546
WP_SetSaberModel(gclient_t * client,class_t npcClass)547 int WP_SetSaberModel( gclient_t *client, class_t npcClass )
548 {//FIXME: read from NPCs.cfg
549 if ( client )
550 {
551 switch ( npcClass )
552 {
553 case CLASS_DESANN://Desann
554 client->ps.saber[0].model = "models/weapons2/saber_desann/saber_w.glm";
555 break;
556 case CLASS_LUKE://Luke
557 client->ps.saber[0].model = "models/weapons2/saber_luke/saber_w.glm";
558 break;
559 case CLASS_PLAYER://Kyle NPC and player
560 case CLASS_KYLE://Kyle NPC and player
561 client->ps.saber[0].model = "models/weapons2/saber/saber_w.glm";
562 break;
563 default://reborn and tavion and everyone else
564 client->ps.saber[0].model = "models/weapons2/saber_reborn/saber_w.glm";
565 break;
566 }
567 return ( G_ModelIndex( client->ps.saber[0].model ) );
568 }
569 else
570 {
571 switch ( npcClass )
572 {
573 case CLASS_DESANN://Desann
574 return ( G_ModelIndex( "models/weapons2/saber_desann/saber_w.glm" ) );
575 break;
576 case CLASS_LUKE://Luke
577 return ( G_ModelIndex( "models/weapons2/saber_luke/saber_w.glm" ) );
578 break;
579 case CLASS_PLAYER://Kyle NPC and player
580 case CLASS_KYLE://Kyle NPC and player
581 return ( G_ModelIndex( "models/weapons2/saber/saber_w.glm" ) );
582 break;
583 default://reborn and tavion and everyone else
584 return ( G_ModelIndex( "models/weapons2/saber_reborn/saber_w.glm" ) );
585 break;
586 }
587 }
588 }
589
WP_SetSaberEntModelSkin(gentity_t * ent,gentity_t * saberent)590 void WP_SetSaberEntModelSkin( gentity_t *ent, gentity_t *saberent )
591 {
592 int saberModel = 0;
593 qboolean newModel = qfalse;
594 //FIXME: get saberModel from NPCs.cfg
595 if ( !ent->client->ps.saber[0].model )
596 {
597 saberModel = WP_SetSaberModel( ent->client, ent->client->NPC_class );
598 }
599 else
600 {
601 //got saberModel from NPCs.cfg
602 saberModel = G_ModelIndex( ent->client->ps.saber[0].model );
603 }
604 if ( saberModel && saberent->s.modelindex != saberModel )
605 {
606 if ( saberent->playerModel >= 0 )
607 {//remove the old one, if there is one
608 gi.G2API_RemoveGhoul2Model( saberent->ghoul2, saberent->playerModel );
609 }
610 //add the new one
611 saberent->playerModel = gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[0].model, saberModel, NULL_HANDLE, NULL_HANDLE, 0, 0);
612 saberent->s.modelindex = saberModel;
613 newModel = qtrue;
614 }
615 //set skin, too
616 if ( ent->client->ps.saber[0].skin == NULL )
617 {
618 gi.G2API_SetSkin( &saberent->ghoul2[0], -1, 0 );
619 }
620 else
621 {//if this saber has a customSkin, use it
622 // lets see if it's out there
623 int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[0].skin );
624 if ( saberSkin && (newModel || saberent->s.modelindex2 != saberSkin) )
625 {
626 // put it in the config strings
627 // and set the ghoul2 model to use it
628 gi.G2API_SetSkin( &saberent->ghoul2[0], G_SkinIndex( ent->client->ps.saber[0].skin ), saberSkin );
629 saberent->s.modelindex2 = saberSkin;
630 }
631 }
632 }
633
WP_SaberFallSound(gentity_t * owner,gentity_t * saber)634 void WP_SaberFallSound( gentity_t *owner, gentity_t *saber )
635 {
636 if ( !saber )
637 {
638 return;
639 }
640 if ( owner && owner->client )
641 {//have an owner, use their data (assume saberNum is 0 because only the 0 saber can be thrown)
642 if ( owner->client->ps.saber[0].fallSound[0] )
643 {//have an override
644 G_Sound( saber, owner->client->ps.saber[0].fallSound[Q_irand( 0, 2 )] );
645 }
646 else if ( owner->client->ps.saber[0].type == SABER_SITH_SWORD )
647 {//is a sith sword
648 G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) );
649 }
650 else
651 {//normal saber
652 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
653 }
654 }
655 else if ( saber->NPC_type && saber->NPC_type[0] )
656 {//have a saber name to look up
657 saberInfo_t saberInfo;
658 if ( WP_SaberParseParms( saber->NPC_type, &saberInfo ) )
659 {//found it
660 if ( saberInfo.fallSound[0] )
661 {//have an override sound
662 G_Sound( saber, saberInfo.fallSound[Q_irand( 0, 2 )] );
663 }
664 else if ( saberInfo.type == SABER_SITH_SWORD )
665 {//is a sith sword
666 G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) );
667 }
668 else
669 {//normal saber
670 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
671 }
672 }
673 else
674 {//can't find it
675 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
676 }
677 }
678 else
679 {//no saber name specified
680 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
681 }
682 }
683
WP_SaberSwingSound(gentity_t * ent,int saberNum,swingType_t swingType)684 void WP_SaberSwingSound( gentity_t *ent, int saberNum, swingType_t swingType )
685 {
686 int index = 1;
687 if ( !ent || !ent->client )
688 {
689 return;
690 }
691 if ( swingType == SWING_FAST )
692 {
693 index = Q_irand( 1, 3 );
694 }
695 else if ( swingType == SWING_MEDIUM )
696 {
697 index = Q_irand( 4, 6 );
698 }
699 else if ( swingType == SWING_STRONG )
700 {
701 index = Q_irand( 7, 9 );
702 }
703
704 if ( ent->client->ps.saber[saberNum].swingSound[0] )
705 {
706 G_SoundIndexOnEnt( ent, CHAN_WEAPON, ent->client->ps.saber[saberNum].swingSound[Q_irand( 0, 2 )] );
707 }
708 else if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
709 {
710 G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/sword/swing%d.wav", Q_irand( 1, 4 ) ) );
711 }
712 else
713 {
714 G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/saber/saberhup%d.wav", index ) );
715 }
716 }
717
WP_SaberHitSound(gentity_t * ent,int saberNum,int bladeNum)718 void WP_SaberHitSound( gentity_t *ent, int saberNum, int bladeNum )
719 {
720 int index = 1;
721 if ( !ent || !ent->client )
722 {
723 return;
724 }
725 index = Q_irand( 1, 3 );
726
727 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
728 && ent->client->ps.saber[saberNum].hitSound[0] )
729 {
730 G_Sound( ent, ent->client->ps.saber[saberNum].hitSound[Q_irand( 0, 2 )] );
731 }
732 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
733 && ent->client->ps.saber[saberNum].hit2Sound[0] )
734 {
735 G_Sound( ent, ent->client->ps.saber[saberNum].hit2Sound[Q_irand( 0, 2 )] );
736 }
737 else if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD )
738 {
739 G_Sound( ent, G_SoundIndex( va( "sound/weapons/sword/stab%d.wav", Q_irand( 1, 4 ) ) ) );
740 }
741 else
742 {
743 G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", index ) ) );
744 }
745 }
746
WP_SaberBlockSound(gentity_t * ent,gentity_t * hitEnt,int saberNum,int bladeNum)747 void WP_SaberBlockSound( gentity_t *ent, gentity_t *hitEnt, int saberNum, int bladeNum )
748 {
749 int index = 1;
750 if ( !ent || !ent->client )
751 {
752 return;
753 }
754 index = Q_irand( 1, 9 );
755
756 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
757 && ent->client->ps.saber[saberNum].blockSound[0] )
758 {
759 G_Sound( ent, ent->client->ps.saber[saberNum].blockSound[Q_irand( 0, 2 )] );
760 }
761 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
762 && ent->client->ps.saber[saberNum].block2Sound[0] )
763 {
764 G_Sound( ent, ent->client->ps.saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
765 }
766 else
767 {
768 G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
769 }
770 }
771
772
WP_SaberBounceOnWallSound(gentity_t * ent,int saberNum,int bladeNum)773 void WP_SaberBounceOnWallSound( gentity_t *ent, int saberNum, int bladeNum )
774 {
775 int index = 1;
776 if ( !ent || !ent->client )
777 {
778 return;
779 }
780 index = Q_irand( 1, 9 );
781
782 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
783 && ent->client->ps.saber[saberNum].bounceSound[0] )
784 {
785 G_Sound( ent, ent->client->ps.saber[saberNum].bounceSound[Q_irand( 0, 2 )] );
786 }
787 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
788 && ent->client->ps.saber[saberNum].bounce2Sound[0] )
789 {
790 G_Sound( ent, ent->client->ps.saber[saberNum].bounce2Sound[Q_irand( 0, 2 )] );
791 }
792 else if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
793 && ent->client->ps.saber[saberNum].blockSound[0] )
794 {
795 G_Sound( ent, ent->client->ps.saber[saberNum].blockSound[Q_irand( 0, 2 )] );
796 }
797 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
798 && ent->client->ps.saber[saberNum].block2Sound[0] )
799 {
800 G_Sound( ent, ent->client->ps.saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
801 }
802 else
803 {
804 G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) );
805 }
806 }
807
WP_SaberBounceSound(gentity_t * ent,gentity_t * hitEnt,gentity_t * playOnEnt,int saberNum,int bladeNum,qboolean doForce)808 void WP_SaberBounceSound( gentity_t *ent, gentity_t *hitEnt, gentity_t *playOnEnt, int saberNum, int bladeNum, qboolean doForce )
809 {
810 int index = 1;
811 if ( !ent || !ent->client )
812 {
813 return;
814 }
815 index = Q_irand( 1, 3 );
816
817 if ( !playOnEnt )
818 {
819 playOnEnt = ent;
820 }
821 //NOTE: we don't allow overriding of the saberbounce sound, but since it's just a variant on the saberblock sound, we use that as the override
822 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
823 && ent->client->ps.saber[saberNum].blockSound[0] )
824 {
825 G_Sound( playOnEnt, ent->client->ps.saber[saberNum].blockSound[Q_irand( 0, 2 )] );
826 }
827 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
828 && ent->client->ps.saber[saberNum].block2Sound[0] )
829 {
830 G_Sound( playOnEnt, ent->client->ps.saber[saberNum].block2Sound[Q_irand( 0, 2 )] );
831 }
832 else
833 {
834 G_Sound( playOnEnt, G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", index ) ) );
835 }
836 }
837
WP_SaberInitBladeData(gentity_t * ent)838 int WP_SaberInitBladeData( gentity_t *ent )
839 {
840 if ( !ent->client )
841 {
842 return 0;
843 }
844 if ( 1 )
845 {
846 VectorClear( ent->client->renderInfo.muzzlePoint );
847 VectorClear( ent->client->renderInfo.muzzlePointOld );
848 //VectorClear( ent->client->renderInfo.muzzlePointNext );
849 VectorClear( ent->client->renderInfo.muzzleDir );
850 VectorClear( ent->client->renderInfo.muzzleDirOld );
851 //VectorClear( ent->client->renderInfo.muzzleDirNext );
852 for ( int saberNum = 0; saberNum < MAX_SABERS; saberNum++ )
853 {
854 for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ )
855 {
856 VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint );
857 VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
858 VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir );
859 VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
860 ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length = 0;
861 if ( !ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax )
862 {
863 if ( ent->client->NPC_class == CLASS_DESANN )
864 {//longer saber
865 ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 48;
866 }
867 else if ( ent->client->NPC_class == CLASS_REBORN )
868 {//shorter saber
869 ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 32;
870 }
871 else
872 {//standard saber length
873 ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 40;
874 }
875 }
876 }
877 }
878 ent->client->ps.saberLockEnemy = ENTITYNUM_NONE;
879 ent->client->ps.saberLockTime = 0;
880 if ( ent->s.number )
881 {
882 if ( !ent->client->ps.saberAnimLevel )
883 {
884 if ( ent->client->NPC_class == CLASS_DESANN )
885 {
886 ent->client->ps.saberAnimLevel = SS_DESANN;
887 }
888 else if ( ent->client->NPC_class == CLASS_TAVION )
889 {
890 ent->client->ps.saberAnimLevel = SS_TAVION;
891 }
892 else if ( ent->client->NPC_class == CLASS_ALORA )
893 {
894 ent->client->ps.saberAnimLevel = SS_DUAL;
895 }
896 //FIXME: CLASS_CULTIST instead of this Q_stricmpn?
897 else if ( !Q_stricmpn( "cultist", ent->NPC_type, 7 ) )
898 {//should already be set in the .npc file
899 ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
900 }
901 else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CIVILIAN || ent->NPC->rank == RANK_LT_JG) )
902 {//grunt and fencer always uses quick attacks
903 ent->client->ps.saberAnimLevel = SS_FAST;
904 }
905 else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CREWMAN || ent->NPC->rank == RANK_ENSIGN) )
906 {//acrobat & force-users always use medium attacks
907 ent->client->ps.saberAnimLevel = SS_MEDIUM;
908 }
909 else if ( ent->client->playerTeam == TEAM_ENEMY && ent->client->NPC_class == CLASS_SHADOWTROOPER )
910 {//shadowtroopers
911 ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
912 }
913 else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && ent->NPC->rank == RANK_LT )
914 {//boss always starts with strong attacks
915 ent->client->ps.saberAnimLevel = SS_STRONG;
916 }
917 else if ( ent->client->NPC_class == CLASS_PLAYER )
918 {
919 ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
920 }
921 else
922 {//?
923 ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG );
924 }
925 }
926 }
927 else
928 {
929 if ( !ent->client->ps.saberAnimLevel )
930 {//initialize, but don't reset
931 if (ent->s.number < MAX_CLIENTS)
932 {
933 if (!ent->client->ps.saberStylesKnown)
934 {
935 ent->client->ps.saberStylesKnown = (1<<SS_MEDIUM);
936 }
937
938
939 if (ent->client->ps.saberStylesKnown & (1<<SS_FAST))
940 {
941 ent->client->ps.saberAnimLevel = SS_FAST;
942 }
943 else if (ent->client->ps.saberStylesKnown & (1<<SS_STRONG))
944 {
945 ent->client->ps.saberAnimLevel = SS_STRONG;
946 }
947 else
948 {
949 ent->client->ps.saberAnimLevel = SS_MEDIUM;
950 }
951
952 }
953 else
954 {
955 ent->client->ps.saberAnimLevel = SS_MEDIUM;
956 }
957 }
958
959 cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel;
960 if ( ent->client->sess.missionStats.weaponUsed[WP_SABER] <= 0 )
961 {//let missionStats know that we actually do have the saber, even if we never use it
962 ent->client->sess.missionStats.weaponUsed[WP_SABER] = 1;
963 }
964 }
965 ent->client->ps.saberAttackChainCount = 0;
966
967 if ( ent->client->ps.saberEntityNum <= 0 || ent->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
968 {//FIXME: if you do have a saber already, be sure to re-set the model if it's changed (say, via a script).
969 gentity_t *saberent = G_Spawn();
970 ent->client->ps.saberEntityNum = saberent->s.number;
971 saberent->classname = "lightsaber";
972
973 saberent->s.eType = ET_GENERAL;
974 saberent->svFlags = SVF_USE_CURRENT_ORIGIN;
975 saberent->s.weapon = WP_SABER;
976 saberent->owner = ent;
977 saberent->s.otherEntityNum = ent->s.number;
978 //clear the enemy
979 saberent->enemy = NULL;
980
981 saberent->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
982 saberent->contents = CONTENTS_LIGHTSABER;//|CONTENTS_SHOTCLIP;
983
984 VectorSet( saberent->mins, -3.0f, -3.0f, -3.0f );
985 VectorSet( saberent->maxs, 3.0f, 3.0f, 3.0f );
986 saberent->mass = 10;//necc?
987
988 saberent->s.eFlags |= EF_NODRAW;
989 saberent->svFlags |= SVF_NOCLIENT;
990 /*
991 Ghoul2 Insert Start
992 */
993 saberent->playerModel = -1;
994 WP_SetSaberEntModelSkin( ent, saberent );
995
996 // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
997 gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
998 //gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
999 if ( ent->client->ps.dualSabers )
1000 {
1001 //int saber2 =
1002 G_ModelIndex( ent->client->ps.saber[1].model );
1003 //gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[1].model, saber2 );
1004 // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
1005 //gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
1006 //gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
1007 }
1008
1009 /*
1010 Ghoul2 Insert End
1011 */
1012
1013 ent->client->ps.saberInFlight = qfalse;
1014 ent->client->ps.saberEntityDist = 0;
1015 ent->client->ps.saberEntityState = SES_LEAVING;
1016
1017 ent->client->ps.saberMove = ent->client->ps.saberMoveNext = LS_NONE;
1018
1019 //FIXME: need a think function to create alerts when turned on or is on, etc.
1020 }
1021 else
1022 {//already have one, might just be changing sabers, register the model and skin and use them if different from what we're using now.
1023 WP_SetSaberEntModelSkin( ent, &g_entities[ent->client->ps.saberEntityNum] );
1024 }
1025 }
1026 else
1027 {
1028 ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
1029 ent->client->ps.saberInFlight = qfalse;
1030 ent->client->ps.saberEntityDist = 0;
1031 ent->client->ps.saberEntityState = SES_LEAVING;
1032 }
1033
1034 if ( ent->client->ps.dualSabers )
1035 {
1036 return 2;
1037 }
1038
1039 return 1;
1040 }
1041
WP_SaberUpdateOldBladeData(gentity_t * ent)1042 void WP_SaberUpdateOldBladeData( gentity_t *ent )
1043 {
1044 if ( ent->client )
1045 {
1046 qboolean didEvent = qfalse;
1047 for ( int saberNum = 0; saberNum < 2; saberNum++ )
1048 {
1049 for ( int bladeNum = 0; bladeNum < ent->client->ps.saber[saberNum].numBlades; bladeNum++ )
1050 {
1051 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
1052 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
1053 if ( !didEvent )
1054 {
1055 if ( ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld <= 0 && ent->client->ps.saber[saberNum].blade[bladeNum].length > 0 )
1056 {//just turned on
1057 //do sound event
1058 vec3_t saberOrg;
1059 VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, saberOrg );
1060 if ( (!ent->client->ps.saberInFlight && ent->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground
1061 || g_entities[ent->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground
1062 {//a ground alert
1063 AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS, qfalse, qtrue );
1064 }
1065 else
1066 {//an in-air alert
1067 AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS );
1068 }
1069 didEvent = qtrue;
1070 }
1071 }
1072 ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length;
1073 }
1074 }
1075 VectorCopy( ent->client->renderInfo.muzzlePoint, ent->client->renderInfo.muzzlePointOld );
1076 VectorCopy( ent->client->renderInfo.muzzleDir, ent->client->renderInfo.muzzleDirOld );
1077 }
1078 }
1079
1080
1081
1082 //SABER DAMAGE==============================================================================
1083 //SABER DAMAGE==============================================================================
1084 //SABER DAMAGE==============================================================================
1085 //SABER DAMAGE==============================================================================
1086 //SABER DAMAGE==============================================================================
1087 //SABER DAMAGE==============================================================================
WPDEBUG_SaberColor(saber_colors_t saberColor)1088 int WPDEBUG_SaberColor( saber_colors_t saberColor )
1089 {
1090 switch( (int)(saberColor) )
1091 {
1092 case SABER_RED:
1093 return 0x000000ff;
1094 break;
1095 case SABER_ORANGE:
1096 return 0x000088ff;
1097 break;
1098 case SABER_YELLOW:
1099 return 0x0000ffff;
1100 break;
1101 case SABER_GREEN:
1102 return 0x0000ff00;
1103 break;
1104 case SABER_BLUE:
1105 return 0x00ff0000;
1106 break;
1107 case SABER_PURPLE:
1108 return 0x00ff00ff;
1109 break;
1110 default:
1111 return 0x00ffffff;//white
1112 break;
1113 }
1114 }
1115
WP_GetSaberDeflectionAngle(gentity_t * attacker,gentity_t * defender)1116 qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender )
1117 {
1118 vec3_t temp, att_SaberBase, att_StartPos, saberMidNext, att_HitDir, att_HitPos, def_BladeDir;
1119 float att_SaberHitLength, hitDot;
1120
1121 if ( !attacker || !attacker->client || attacker->client->ps.saberInFlight || attacker->client->ps.SaberLength() <= 0 )
1122 {
1123 return qfalse;
1124 }
1125 if ( !defender || !defender->client || defender->client->ps.saberInFlight || defender->client->ps.SaberLength() <= 0 )
1126 {
1127 return qfalse;
1128 }
1129 if ( PM_SuperBreakLoseAnim( attacker->client->ps.torsoAnim )
1130 || PM_SuperBreakWinAnim( attacker->client->ps.torsoAnim ) )
1131 {
1132 return qfalse;
1133 }
1134 attacker->client->ps.saberBounceMove = LS_NONE;
1135
1136 //get the attacker's saber base pos at time of impact
1137 VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp );
1138 VectorMA( attacker->client->renderInfo.muzzlePointOld, saberHitFraction, temp, att_SaberBase );
1139
1140 //get the position along the length of the blade where the hit occured
1141 att_SaberHitLength = Distance( saberHitLocation, att_SaberBase )/attacker->client->ps.SaberLength();
1142
1143 //now get the start of that midpoint in the swing and the actual impact point in the swing (shouldn't the latter just be saberHitLocation?)
1144 VectorMA( attacker->client->renderInfo.muzzlePointOld, att_SaberHitLength, attacker->client->renderInfo.muzzleDirOld, att_StartPos );
1145 VectorMA( attacker->client->renderInfo.muzzlePoint, att_SaberHitLength, attacker->client->renderInfo.muzzleDir, saberMidNext );
1146 VectorSubtract( saberMidNext, att_StartPos, att_HitDir );
1147 VectorMA( att_StartPos, saberHitFraction, att_HitDir, att_HitPos );
1148 VectorNormalize( att_HitDir );
1149
1150 //get the defender's saber dir at time of impact
1151 VectorSubtract( defender->client->renderInfo.muzzleDirOld, defender->client->renderInfo.muzzleDir, temp );
1152 VectorMA( defender->client->renderInfo.muzzleDirOld, saberHitFraction, temp, def_BladeDir );
1153
1154 //now compare
1155 hitDot = DotProduct( att_HitDir, def_BladeDir );
1156 if ( hitDot < 0.25f && hitDot > -0.25f )
1157 {//hit pretty much perpendicular, pop straight back
1158 attacker->client->ps.saberBounceMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
1159 return qfalse;
1160 }
1161 else
1162 {//a deflection
1163 vec3_t att_Right, att_Up, att_DeflectionDir;
1164 float swingRDot, swingUDot;
1165
1166 //get the direction of the deflection
1167 VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
1168 //get our bounce straight back direction
1169 VectorScale( att_HitDir, -1.0f, temp );
1170 //add the bounce back and deflection
1171 VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
1172 //normalize the result to determine what direction our saber should bounce back toward
1173 VectorNormalize( att_DeflectionDir );
1174
1175 //need to know the direction of the deflectoin relative to the attacker's facing
1176 VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
1177 AngleVectors( temp, NULL, att_Right, att_Up );
1178 swingRDot = DotProduct( att_Right, att_DeflectionDir );
1179 swingUDot = DotProduct( att_Up, att_DeflectionDir );
1180
1181 if ( swingRDot > 0.25f )
1182 {//deflect to right
1183 if ( swingUDot > 0.25f )
1184 {//deflect to top
1185 attacker->client->ps.saberBounceMove = LS_D1_TR;
1186 }
1187 else if ( swingUDot < -0.25f )
1188 {//deflect to bottom
1189 attacker->client->ps.saberBounceMove = LS_D1_BR;
1190 }
1191 else
1192 {//deflect horizontally
1193 attacker->client->ps.saberBounceMove = LS_D1__R;
1194 }
1195 }
1196 else if ( swingRDot < -0.25f )
1197 {//deflect to left
1198 if ( swingUDot > 0.25f )
1199 {//deflect to top
1200 attacker->client->ps.saberBounceMove = LS_D1_TL;
1201 }
1202 else if ( swingUDot < -0.25f )
1203 {//deflect to bottom
1204 attacker->client->ps.saberBounceMove = LS_D1_BL;
1205 }
1206 else
1207 {//deflect horizontally
1208 attacker->client->ps.saberBounceMove = LS_D1__L;
1209 }
1210 }
1211 else
1212 {//deflect in middle
1213 if ( swingUDot > 0.25f )
1214 {//deflect to top
1215 attacker->client->ps.saberBounceMove = LS_D1_T_;
1216 }
1217 else if ( swingUDot < -0.25f )
1218 {//deflect to bottom
1219 attacker->client->ps.saberBounceMove = LS_D1_B_;
1220 }
1221 else
1222 {//deflect horizontally? Well, no such thing as straight back in my face, so use top
1223 if ( swingRDot > 0 )
1224 {
1225 attacker->client->ps.saberBounceMove = LS_D1_TR;
1226 }
1227 else if ( swingRDot < 0 )
1228 {
1229 attacker->client->ps.saberBounceMove = LS_D1_TL;
1230 }
1231 else
1232 {
1233 attacker->client->ps.saberBounceMove = LS_D1_T_;
1234 }
1235 }
1236 }
1237 #ifndef FINAL_BUILD
1238 if ( d_saberCombat->integer )
1239 {
1240 gi.Printf( S_COLOR_BLUE"%s deflected from %s to %s\n", attacker->targetname, saberMoveData[attacker->client->ps.saberMove].name, saberMoveData[attacker->client->ps.saberBounceMove].name );
1241 }
1242 #endif
1243 return qtrue;
1244 }
1245 }
1246
1247
WP_SaberClearDamageForEntNum(gentity_t * attacker,int entityNum,int saberNum,int bladeNum)1248 void WP_SaberClearDamageForEntNum( gentity_t *attacker, int entityNum, int saberNum, int bladeNum )
1249 {
1250 #ifndef FINAL_BUILD
1251 if ( d_saberCombat->integer )
1252 {
1253 if ( entityNum )
1254 {
1255 Com_Printf( "clearing damage for entnum %d\n", entityNum );
1256 }
1257 }
1258 #endif// FINAL_BUILD
1259 if ( g_saberRealisticCombat->integer > 1 )
1260 {
1261 return;
1262 }
1263
1264 //FIXME: if hit their saber in WP_SaberDamageForTrace, need to still do knockback on them...
1265 float knockBackScale = 0.0f;
1266 if ( attacker && attacker->client )
1267 {
1268 if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
1269 && attacker->client->ps.saber[saberNum].knockbackScale > 0.0f )
1270 {
1271 knockBackScale = attacker->client->ps.saber[saberNum].knockbackScale;
1272 }
1273 else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
1274 && attacker->client->ps.saber[saberNum].knockbackScale2 > 0.0f )
1275 {
1276 knockBackScale = attacker->client->ps.saber[saberNum].knockbackScale2;
1277 }
1278 }
1279
1280 for ( int i = 0; i < numVictims; i++ )
1281 {
1282 if ( victimEntityNum[i] == entityNum )
1283 {
1284 //hold on a sec, let's still do any accumulated knockback
1285 if ( knockBackScale )
1286 {
1287 gentity_t *victim = &g_entities[victimEntityNum[i]];
1288 if ( victim && victim->client )
1289 {
1290 vec3_t center, dirToCenter;
1291 float knockDownThreshHold, knockback = knockBackScale * totalDmg[i] * 0.5f;
1292
1293 VectorAdd( victim->absmin, victim->absmax, center );
1294 VectorScale( center, 0.5, center );
1295 VectorSubtract( victim->currentOrigin, saberHitLocation, dirToCenter );
1296 VectorNormalize( dirToCenter );
1297 G_Throw( victim, dirToCenter, knockback );
1298 if ( victim->client->ps.groundEntityNum != ENTITYNUM_NONE
1299 && dirToCenter[2] <= 0 )
1300 {//hit downward on someone who is standing on firm ground, so more likely to knock them down
1301 knockDownThreshHold = Q_irand( 25, 50 );
1302 }
1303 else
1304 {
1305 knockDownThreshHold = Q_irand( 75, 125 );
1306 }
1307
1308 if ( knockback > knockDownThreshHold )
1309 {
1310 G_Knockdown( victim, attacker, dirToCenter, 350, qtrue );
1311 }
1312 }
1313 }
1314 //now clear everything
1315 totalDmg[i] = 0;//no damage
1316 hitLoc[i] = HL_NONE;
1317 hitDismemberLoc[i] = HL_NONE;
1318 hitDismember[i] = qfalse;
1319 victimEntityNum[i] = ENTITYNUM_NONE;//like we never hit him
1320 }
1321 }
1322 }
1323
1324 extern float damageModifier[];
1325 extern float hitLocHealthPercentage[];
WP_SaberApplyDamage(gentity_t * ent,float baseDamage,int baseDFlags,qboolean brokenParry,int saberNum,int bladeNum,qboolean thrownSaber)1326 qboolean WP_SaberApplyDamage( gentity_t *ent, float baseDamage, int baseDFlags,
1327 qboolean brokenParry, int saberNum, int bladeNum, qboolean thrownSaber )
1328 {
1329 qboolean didDamage = qfalse;
1330 gentity_t *victim;
1331 int dFlags = baseDFlags;
1332 float maxDmg;
1333 saberType_t saberType = ent->client->ps.saber[saberNum].type;
1334
1335 if ( !numVictims )
1336 {
1337 return qfalse;
1338 }
1339 for ( int i = 0; i < numVictims; i++ )
1340 {
1341 dFlags = baseDFlags|DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_HIT_LOC;
1342 if ( victimEntityNum[i] != ENTITYNUM_NONE && &g_entities[victimEntityNum[i]] != NULL )
1343 { // Don't bother with this damage if the fraction is higher than the saber's fraction
1344 if ( dmgFraction[i] < saberHitFraction || brokenParry )
1345 {
1346 victim = &g_entities[victimEntityNum[i]];
1347 if ( !victim )
1348 {
1349 continue;
1350 }
1351
1352 if ( victim->e_DieFunc == dieF_maglock_die )
1353 {//*sigh*, special check for maglocks
1354 vec3_t testFrom;
1355 if ( ent->client->ps.saberInFlight )
1356 {
1357 VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, testFrom );
1358 }
1359 else
1360 {
1361 VectorCopy( ent->currentOrigin, testFrom );
1362 }
1363 testFrom[2] = victim->currentOrigin[2];
1364 trace_t testTrace;
1365 gi.trace( &testTrace, testFrom, vec3_origin, vec3_origin, victim->currentOrigin, ent->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1366 if ( testTrace.entityNum != victim->s.number )
1367 {//can only damage maglocks if have a clear trace to the thing's origin
1368 continue;
1369 }
1370 }
1371 if ( totalDmg[i] > 0 )
1372 {//actually want to do *some* damage here
1373 if ( victim->client
1374 && victim->client->NPC_class==CLASS_WAMPA
1375 && victim->activator == ent )
1376 {
1377 }
1378 else if ( PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
1379 || PM_StabDownAnim( ent->client->ps.torsoAnim ) )
1380 {//never cap the superbreak wins
1381 }
1382 else
1383 {
1384 if ( victim->client
1385 && (victim->s.weapon == WP_SABER || (victim->client->NPC_class==CLASS_REBORN) || (victim->client->NPC_class==CLASS_WAMPA))
1386 && !g_saberRealisticCombat->integer )
1387 {//dmg vs other saber fighters is modded by hitloc and capped
1388 totalDmg[i] *= damageModifier[hitLoc[i]];
1389 if ( hitLoc[i] == HL_NONE )
1390 {
1391 maxDmg = 33*baseDamage;
1392 }
1393 else
1394 {
1395 maxDmg = 50*hitLocHealthPercentage[hitLoc[i]]*baseDamage;//*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f;
1396 }
1397 if ( maxDmg < totalDmg[i] )
1398 {
1399 totalDmg[i] = maxDmg;
1400 }
1401 //dFlags |= DAMAGE_NO_HIT_LOC;
1402 }
1403 //clamp the dmg
1404 if ( victim->s.weapon != WP_SABER )
1405 {//clamp the dmg between 25 and maxhealth
1406 /*
1407 if ( totalDmg[i] > victim->max_health )
1408 {
1409 totalDmg[i] = victim->max_health;
1410 }
1411 else */if ( totalDmg[i] < 25 )
1412 {
1413 totalDmg[i] = 25;
1414 }
1415 if ( totalDmg[i] > 100 )//+(50*g_spskill->integer) )
1416 {//clamp using same adjustment as in NPC_Begin
1417 totalDmg[i] = 100;//+(50*g_spskill->integer);
1418 }
1419 }
1420 else
1421 {//clamp the dmg between 5 and 100
1422 if ( !victim->s.number && totalDmg[i] > 50 )
1423 {//never do more than half full health damage to player
1424 //prevents one-hit kills
1425 totalDmg[i] = 50;
1426 }
1427 else if ( totalDmg[i] > 100 )
1428 {
1429 totalDmg[i] = 100;
1430 }
1431 else
1432 {
1433 if ( totalDmg[i] < 5 )
1434 {
1435 totalDmg[i] = 5;
1436 }
1437 }
1438 }
1439 }
1440
1441 if ( totalDmg[i] > 0 )
1442 {
1443 gentity_t *inflictor = ent;
1444 didDamage = qtrue;
1445 qboolean vicWasDismembered = qtrue;
1446 qboolean vicWasAlive = (qboolean)(victim->health>0);
1447
1448 if ( baseDamage <= 0.1f )
1449 {//just get their attention?
1450 dFlags |= DAMAGE_NO_DAMAGE;
1451 }
1452
1453 if( victim->client )
1454 {
1455 if ( victim->client->ps.pm_time > 0 && victim->client->ps.pm_flags & PMF_TIME_KNOCKBACK && victim->client->ps.velocity[2] > 0 )
1456 {//already being knocked around
1457 dFlags |= DAMAGE_NO_KNOCKBACK;
1458 }
1459 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1460 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_DISMEMBERMENT) )
1461 {//no dismemberment! (blunt/stabbing weapon?)
1462 }
1463 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1464 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_DISMEMBERMENT2) )
1465 {//no dismemberment! (blunt/stabbing weapon?)
1466 }
1467 else
1468 {
1469 if ( debug_subdivision->integer || g_saberRealisticCombat->integer )
1470 {
1471 dFlags |= DAMAGE_DISMEMBER;
1472 if ( hitDismember[i] )
1473 {
1474 victim->client->dismembered = false;
1475 }
1476 }
1477 else if ( hitDismember[i] )
1478 {
1479 dFlags |= DAMAGE_DISMEMBER;
1480 }
1481 if ( !victim->client->dismembered )
1482 {
1483 vicWasDismembered = qfalse;
1484 }
1485 }
1486 if ( baseDamage <= 1.0f )
1487 {//very mild damage
1488 if ( victim->s.number == 0 || victim->client->ps.weapon == WP_SABER || victim->client->NPC_class == CLASS_GALAKMECH )
1489 {//if it's the player or a saber-user, don't kill them with this blow
1490 dFlags |= DAMAGE_NO_KILL;
1491 }
1492 }
1493 }
1494 else
1495 {
1496 if ( victim->takedamage )
1497 {//some other breakable thing
1498 //create a flash here
1499 if ( !g_noClashFlare )
1500 {
1501 g_saberFlashTime = level.time-50;
1502 VectorCopy( dmgSpot[i], g_saberFlashPos );
1503 }
1504 }
1505 }
1506 if ( !PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
1507 && !PM_StabDownAnim( ent->client->ps.torsoAnim )
1508 && !g_saberRealisticCombat->integer
1509 && g_saberDamageCapping->integer )
1510 {//never cap the superbreak wins
1511 if ( victim->client
1512 && victim->s.number >= MAX_CLIENTS )
1513 {
1514 if ( victim->client->NPC_class == CLASS_SHADOWTROOPER
1515 || ( victim->NPC && (victim->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) )
1516 {//hit a boss character
1517 int maxDmg = ((3-g_spskill->integer)*5)+10;
1518 if ( totalDmg[i] > maxDmg )
1519 {
1520 totalDmg[i] = maxDmg;
1521 }
1522 }
1523 else if ( victim->client->ps.weapon == WP_SABER
1524 || victim->client->NPC_class == CLASS_REBORN
1525 || victim->client->NPC_class == CLASS_JEDI )
1526 {//hit a non-boss saber-user
1527 int maxDmg = ((3-g_spskill->integer)*15)+30;
1528 if ( totalDmg[i] > maxDmg )
1529 {
1530 totalDmg[i] = maxDmg;
1531 }
1532 }
1533 }
1534 if ( victim->s.number < MAX_CLIENTS
1535 && ent->NPC )
1536 {
1537 if ( (ent->NPC->aiFlags&NPCAI_BOSS_CHARACTER)
1538 || (ent->NPC->aiFlags&NPCAI_SUBBOSS_CHARACTER)
1539 || ent->client->NPC_class == CLASS_SHADOWTROOPER )
1540 {//player hit by a boss character
1541 int maxDmg = ((g_spskill->integer+1)*4)+3;
1542 if ( totalDmg[i] > maxDmg )
1543 {
1544 totalDmg[i] = maxDmg;
1545 }
1546 }
1547 else if ( g_spskill->integer < 3 ) //was < 2
1548 {//player hit by any enemy //on easy or medium?
1549 int maxDmg = ((g_spskill->integer+1)*10)+20;
1550 if ( totalDmg[i] > maxDmg )
1551 {
1552 totalDmg[i] = maxDmg;
1553 }
1554 }
1555 }
1556 }
1557 //victim->hitLoc = hitLoc[i];
1558
1559 dFlags |= DAMAGE_NO_KNOCKBACK;//okay, let's try no knockback whatsoever...
1560 dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
1561 if ( g_saberRealisticCombat->integer )
1562 {
1563 dFlags |= DAMAGE_NO_KNOCKBACK;
1564 dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
1565 dFlags &= ~DAMAGE_NO_KILL;
1566 }
1567 if ( ent->client && !ent->s.number )
1568 {
1569 switch( hitLoc[i] )
1570 {
1571 case HL_FOOT_RT:
1572 case HL_FOOT_LT:
1573 case HL_LEG_RT:
1574 case HL_LEG_LT:
1575 ent->client->sess.missionStats.legAttacksCnt++;
1576 break;
1577 case HL_WAIST:
1578 case HL_BACK_RT:
1579 case HL_BACK_LT:
1580 case HL_BACK:
1581 case HL_CHEST_RT:
1582 case HL_CHEST_LT:
1583 case HL_CHEST:
1584 ent->client->sess.missionStats.torsoAttacksCnt++;
1585 break;
1586 case HL_ARM_RT:
1587 case HL_ARM_LT:
1588 case HL_HAND_RT:
1589 case HL_HAND_LT:
1590 ent->client->sess.missionStats.armAttacksCnt++;
1591 break;
1592 default:
1593 ent->client->sess.missionStats.otherAttacksCnt++;
1594 break;
1595 }
1596 }
1597
1598 if ( saberType == SABER_SITH_SWORD )
1599 {//do knockback
1600 dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK);
1601 }
1602 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1603 && ent->client->ps.saber[saberNum].knockbackScale > 0.0f )
1604 {
1605 dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK);
1606 if ( saberNum < 1 )
1607 {
1608 dFlags |= DAMAGE_SABER_KNOCKBACK1;
1609 }
1610 else
1611 {
1612 dFlags |= DAMAGE_SABER_KNOCKBACK2;
1613 }
1614 }
1615 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1616 && ent->client->ps.saber[saberNum].knockbackScale2 > 0.0f )
1617 {
1618 dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK);
1619 if ( saberNum < 1 )
1620 {
1621 dFlags |= DAMAGE_SABER_KNOCKBACK1_B2;
1622 }
1623 else
1624 {
1625 dFlags |= DAMAGE_SABER_KNOCKBACK2_B2;
1626 }
1627 }
1628 if ( thrownSaber )
1629 {
1630 inflictor = &g_entities[ent->client->ps.saberEntityNum];
1631 }
1632 int damage = 0;
1633 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1634 && ent->client->ps.saber[saberNum].damageScale != 1.0f )
1635 {
1636 damage = ceil(totalDmg[i]*ent->client->ps.saber[saberNum].damageScale);
1637 }
1638 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
1639 && ent->client->ps.saber[saberNum].damageScale2 != 1.0f )
1640 {
1641 damage = ceil(totalDmg[i]*ent->client->ps.saber[saberNum].damageScale2);
1642 }
1643 else
1644 {
1645 damage = ceil(totalDmg[i]);
1646 }
1647 G_Damage( victim, inflictor, ent, dmgDir[i], dmgSpot[i], damage, dFlags, MOD_SABER, hitDismemberLoc[i] );
1648 if ( damage > 0 && cg.time )
1649 {
1650 float sizeTimeScale = 1.0f;
1651 if ( (vicWasAlive
1652 && victim->health <= 0 )
1653 || (!vicWasDismembered
1654 && victim->client->dismembered
1655 && hitDismemberLoc[i] != HL_NONE
1656 && hitDismember[i]) )
1657 {
1658 sizeTimeScale = 3.0f;
1659 }
1660 //FIXME: if not hitting the first model on the enemy, don't do this!
1661 CG_SaberDoWeaponHitMarks( ent->client,
1662 (ent->client->ps.saberInFlight?&g_entities[ent->client->ps.saberEntityNum]:NULL),
1663 victim,
1664 saberNum,
1665 bladeNum,
1666 dmgSpot[i],
1667 dmgDir[i],
1668 dmgBladeVec[i],
1669 dmgNormal[i],
1670 sizeTimeScale );
1671 }
1672 #ifndef FINAL_BUILD
1673 if ( d_saberCombat->integer )
1674 {
1675 if ( (dFlags&DAMAGE_NO_DAMAGE) )
1676 {
1677 gi.Printf( S_COLOR_RED"damage: fake, hitLoc %d\n", hitLoc[i] );
1678 }
1679 else
1680 {
1681 gi.Printf( S_COLOR_RED"damage: %4.2f, hitLoc %d\n", totalDmg[i], hitLoc[i] );
1682 }
1683 }
1684 #endif
1685 //do the effect
1686 //vec3_t splashBackDir;
1687 //VectorScale( dmgNormal[i], -1, splashBackDir );
1688 //G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], splashBackDir );
1689 if ( ent->s.number == 0 )
1690 {
1691 AddSoundEvent( victim->owner, dmgSpot[i], 256, AEL_DISCOVERED );
1692 AddSightEvent( victim->owner, dmgSpot[i], 512, AEL_DISCOVERED, 50 );
1693 }
1694 if ( ent->client )
1695 {
1696 if ( ent->enemy && ent->enemy == victim )
1697 {//just so Jedi knows that he hit his enemy
1698 ent->client->ps.saberEventFlags |= SEF_HITENEMY;
1699 }
1700 else
1701 {
1702 ent->client->ps.saberEventFlags |= SEF_HITOBJECT;
1703 }
1704 }
1705 }
1706 }
1707 }
1708 }
1709 }
1710 return didDamage;
1711 }
1712
WP_SaberDamageAdd(float trDmg,int trVictimEntityNum,vec3_t trDmgDir,vec3_t trDmgBladeVec,vec3_t trDmgNormal,vec3_t trDmgSpot,float dmg,float fraction,int trHitLoc,qboolean trDismember,int trDismemberLoc)1713 void WP_SaberDamageAdd( float trDmg, int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgBladeVec, vec3_t trDmgNormal, vec3_t trDmgSpot, float dmg, float fraction, int trHitLoc, qboolean trDismember, int trDismemberLoc )
1714 {
1715 int curVictim = 0;
1716 int i;
1717
1718 if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
1719 {
1720 return;
1721 }
1722 if ( trDmg * dmg < 10.0f )
1723 {//too piddly an amount of damage to really count?
1724 //FIXME: but already did the effect, didn't we... sigh...
1725 //return;
1726 }
1727 if ( trDmg )
1728 {//did some damage to something
1729 for ( i = 0; i < numVictims; i++ )
1730 {
1731 if ( victimEntityNum[i] == trVictimEntityNum )
1732 {//already hit this guy before
1733 curVictim = i;
1734 break;
1735 }
1736 }
1737 if ( i == numVictims )
1738 {//haven't hit his guy before
1739 if ( numVictims + 1 >= MAX_SABER_VICTIMS )
1740 {//can't add another victim at this time
1741 return;
1742 }
1743 //add a new victim to the list
1744 curVictim = numVictims;
1745 victimEntityNum[numVictims++] = trVictimEntityNum;
1746 }
1747
1748 float addDmg = trDmg*dmg;
1749 if ( trHitLoc!=HL_NONE && (hitLoc[curVictim]==HL_NONE||hitLocHealthPercentage[trHitLoc]>hitLocHealthPercentage[hitLoc[curVictim]]) )
1750 {//this hitLoc is more critical than the previous one this frame
1751 hitLoc[curVictim] = trHitLoc;
1752 }
1753
1754 totalDmg[curVictim] += addDmg;
1755 if ( !VectorLengthSquared( dmgDir[curVictim] ) )
1756 {
1757 VectorCopy( trDmgDir, dmgDir[curVictim] );
1758 }
1759 if ( !VectorLengthSquared( dmgBladeVec[curVictim] ) )
1760 {
1761 VectorCopy( trDmgBladeVec, dmgBladeVec[curVictim] );
1762 }
1763 if ( !VectorLengthSquared( dmgNormal[curVictim] ) )
1764 {
1765 VectorCopy( trDmgNormal, dmgNormal[curVictim] );
1766 }
1767 if ( !VectorLengthSquared( dmgSpot[curVictim] ) )
1768 {
1769 VectorCopy( trDmgSpot, dmgSpot[curVictim] );
1770 }
1771
1772 // Make sure we keep track of the fraction. Why?
1773 // Well, if the saber hits something that stops it, the damage isn't done past that point.
1774 dmgFraction[curVictim] = fraction;
1775 if ( (trDismemberLoc != HL_NONE && hitDismemberLoc[curVictim] == HL_NONE)
1776 || (!hitDismember[curVictim] && trDismember) )
1777 {//either this is the first dismember loc we got or we got a loc before, but it wasn't a dismember loc, so take the new one
1778 hitDismemberLoc[curVictim] = trDismemberLoc;
1779 }
1780 if ( trDismember )
1781 {//we scored a dismemberment hit...
1782 hitDismember[curVictim] = trDismember;
1783 }
1784 }
1785 }
1786
1787 /*
1788 WP_SabersIntersect
1789
1790 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
1791
1792 FIXME: subdivide the arc into a consistant increment
1793 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)?
1794 */
1795 extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2);
WP_SabersIntersect(gentity_t * ent1,int ent1SaberNum,int ent1BladeNum,gentity_t * ent2,qboolean checkDir)1796 qboolean WP_SabersIntersect( gentity_t *ent1, int ent1SaberNum, int ent1BladeNum, gentity_t *ent2, qboolean checkDir )
1797 {
1798 vec3_t saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
1799 vec3_t saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
1800 int ent2SaberNum = 0, ent2BladeNum = 0;
1801 vec3_t dir;
1802
1803 /*
1804 #ifndef FINAL_BUILD
1805 if ( d_saberCombat->integer )
1806 {
1807 gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1808 }
1809 #endif
1810 */
1811
1812 if ( !ent1 || !ent2 )
1813 {
1814 return qfalse;
1815 }
1816 if ( !ent1->client || !ent2->client )
1817 {
1818 return qfalse;
1819 }
1820 if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
1821 {
1822 return qfalse;
1823 }
1824
1825 for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ )
1826 {
1827 for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->ps.saber[ent2SaberNum].numBlades; ent2BladeNum++ )
1828 {
1829 if ( ent2->client->ps.saber[ent2SaberNum].type != SABER_NONE
1830 && ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length > 0 )
1831 {//valid saber and this blade is on
1832 //if ( ent1->client->ps.saberInFlight )
1833 {
1834 VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 );
1835 VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 );
1836
1837 VectorSubtract( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir );
1838 VectorNormalize( dir );
1839 VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
1840
1841 VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 );
1842 VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 );
1843
1844 VectorSubtract( saberTipNext1, saberTip1, dir );
1845 VectorNormalize( dir );
1846 VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
1847 }
1848 /*
1849 else
1850 {
1851 VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 );
1852 VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 );
1853 VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 );
1854 VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 );
1855 }
1856 */
1857
1858 //if ( ent2->client->ps.saberInFlight )
1859 {
1860 VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 );
1861 VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 );
1862
1863 VectorSubtract( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir );
1864 VectorNormalize( dir );
1865 VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
1866
1867 VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 );
1868 VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 );
1869
1870 VectorSubtract( saberTipNext2, saberTip2, dir );
1871 VectorNormalize( dir );
1872 VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
1873 }
1874 /*
1875 else
1876 {
1877 VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 );
1878 VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 );
1879 VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 );
1880 VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirNext, saberTipNext2 );
1881 }
1882 */
1883 if ( checkDir )
1884 {//check the direction of the two swings to make sure the sabers are swinging towards each other
1885 vec3_t saberDir1, saberDir2;
1886
1887 VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
1888 VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
1889 VectorNormalize( saberDir1 );
1890 VectorNormalize( saberDir2 );
1891 if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
1892 {//sabers moving in same dir, probably didn't actually hit
1893 continue;
1894 }
1895 //now check orientation of sabers, make sure they're not parallel or close to it
1896 float dot = DotProduct( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir );
1897 if ( dot > 0.9f || dot < -0.9f )
1898 {//too parallel to really block effectively?
1899 continue;
1900 }
1901 }
1902
1903 if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1904 {
1905 return qtrue;
1906 }
1907 if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
1908 {
1909 return qtrue;
1910 }
1911 if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1912 {
1913 return qtrue;
1914 }
1915 if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
1916 {
1917 return qtrue;
1918 }
1919 }
1920 }
1921 }
1922 return qfalse;
1923 }
1924
WP_SabersDistance(gentity_t * ent1,gentity_t * ent2)1925 float WP_SabersDistance( gentity_t *ent1, gentity_t *ent2 )
1926 {
1927 vec3_t saberBaseNext1, saberTipNext1, saberPoint1;
1928 vec3_t saberBaseNext2, saberTipNext2, saberPoint2;
1929
1930 /*
1931 #ifndef FINAL_BUILD
1932 if ( d_saberCombat->integer )
1933 {
1934 gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1935 }
1936 #endif
1937 */
1938
1939 if ( !ent1 || !ent2 )
1940 {
1941 return qfalse;
1942 }
1943 if ( !ent1->client || !ent2->client )
1944 {
1945 return qfalse;
1946 }
1947 if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
1948 {
1949 return qfalse;
1950 }
1951
1952 //FIXME: UGH, how do we make this work for multiply-bladed sabers?
1953
1954 //if ( ent1->client->ps.saberInFlight )
1955 {
1956 VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext1 );
1957 VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDir, saberTipNext1 );
1958 }
1959 /*
1960 else
1961 {
1962 VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext1 );
1963 VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext1 );
1964 }
1965 */
1966
1967 //if ( ent2->client->ps.saberInFlight )
1968 {
1969 VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext2 );
1970 VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDir, saberTipNext2 );
1971 }
1972 /*
1973 else
1974 {
1975 VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext2 );
1976 VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext2 );
1977 }
1978 */
1979
1980 float sabersDist = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
1981
1982 //okay, this is a super hack, but makes saber collisions look better from the player point of view
1983 /*
1984 if ( sabersDist < 16.0f )
1985 {
1986 vec3_t saberDistDir, saberMidPoint, camLookDir;
1987
1988 VectorSubtract( saberPoint2, saberPoint1, saberDistDir );
1989 VectorMA( saberPoint1, 0.5f, saberDistDir, saberMidPoint );
1990 VectorSubtract( saberMidPoint, cg.refdef.vieworg, camLookDir );
1991 VectorNormalize( saberDistDir );
1992 VectorNormalize( camLookDir );
1993 float dot = fabs(DotProduct( camLookDir, saberDistDir ));
1994 sabersDist -= 8.0f*dot;
1995 if ( sabersDist < 0.0f )
1996 {
1997 sabersDist = 0.0f;
1998 }
1999 }
2000 */
2001
2002 #ifndef FINAL_BUILD
2003 if ( d_saberCombat->integer > 2 )
2004 {
2005 G_DebugLine( saberPoint1, saberPoint2, FRAMETIME, 0x00ffffff, qtrue );
2006 }
2007 #endif
2008 return sabersDist;
2009 }
2010
WP_SabersIntersection(gentity_t * ent1,gentity_t * ent2,vec3_t intersect)2011 qboolean WP_SabersIntersection( gentity_t *ent1, gentity_t *ent2, vec3_t intersect )
2012 {
2013 vec3_t saberBaseNext1, saberTipNext1, saberPoint1;
2014 vec3_t saberBaseNext2, saberTipNext2, saberPoint2;
2015 int saberNum1, saberNum2, bladeNum1, bladeNum2;
2016 float lineSegLength, bestLineSegLength = Q3_INFINITE;
2017
2018 if ( !ent1 || !ent2 )
2019 {
2020 return qfalse;
2021 }
2022 if ( !ent1->client || !ent2->client )
2023 {
2024 return qfalse;
2025 }
2026 if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 )
2027 {
2028 return qfalse;
2029 }
2030
2031 //UGH, had to make this work for multiply-bladed sabers
2032 for ( saberNum1 = 0; saberNum1 < MAX_SABERS; saberNum1++ )
2033 {
2034 for ( bladeNum1 = 0; bladeNum1 < ent1->client->ps.saber[saberNum1].numBlades; bladeNum1++ )
2035 {
2036 if ( ent1->client->ps.saber[saberNum1].type != SABER_NONE
2037 && ent1->client->ps.saber[saberNum1].blade[bladeNum1].length > 0 )
2038 {//valid saber and this blade is on
2039 for ( saberNum2 = 0; saberNum2 < MAX_SABERS; saberNum2++ )
2040 {
2041 for ( bladeNum2 = 0; bladeNum2 < ent2->client->ps.saber[saberNum2].numBlades; bladeNum2++ )
2042 {
2043 if ( ent2->client->ps.saber[saberNum2].type != SABER_NONE
2044 && ent2->client->ps.saber[saberNum2].blade[bladeNum2].length > 0 )
2045 {//valid saber and this blade is on
2046 VectorCopy( ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzlePoint, saberBaseNext1 );
2047 VectorMA( saberBaseNext1, ent1->client->ps.saber[saberNum1].blade[bladeNum1].length, ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzleDir, saberTipNext1 );
2048
2049 VectorCopy( ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzlePoint, saberBaseNext2 );
2050 VectorMA( saberBaseNext2, ent2->client->ps.saber[saberNum2].blade[bladeNum2].length, ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzleDir, saberTipNext2 );
2051
2052 lineSegLength = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
2053 if ( lineSegLength < bestLineSegLength )
2054 {
2055 bestLineSegLength = lineSegLength;
2056 VectorAdd( saberPoint1, saberPoint2, intersect );
2057 VectorScale( intersect, 0.5, intersect );
2058 }
2059 }
2060 }
2061 }
2062 }
2063 }
2064 }
2065 return qtrue;
2066 }
2067
2068 const char *hit_blood_sparks = "sparks/blood_sparks2"; // could have changed this effect directly, but this is just safer in case anyone anywhere else is using the old one for something?
2069 const char *hit_sparks = "saber/saber_cut";
2070
WP_SaberDamageEffects(trace_t * tr,const vec3_t start,float length,float dmg,vec3_t dmgDir,vec3_t bladeVec,int enemyTeam,saberType_t saberType,saberInfo_t * saber,int bladeNum)2071 qboolean WP_SaberDamageEffects( trace_t *tr, const vec3_t start, float length, float dmg, vec3_t dmgDir, vec3_t bladeVec, int enemyTeam, saberType_t saberType, saberInfo_t *saber, int bladeNum )
2072 {
2073
2074 int hitEntNum[MAX_G2_COLLISIONS];
2075 for ( int hen = 0; hen < MAX_G2_COLLISIONS; hen++ )
2076 {
2077 hitEntNum[hen] = ENTITYNUM_NONE;
2078 }
2079 //NOTE: = {0} does NOT work on anything but bytes?
2080 float hitEntDmgAdd[MAX_G2_COLLISIONS] = {0};
2081 float hitEntDmgSub[MAX_G2_COLLISIONS] = {0};
2082 vec3_t hitEntPoint[MAX_G2_COLLISIONS];
2083 vec3_t hitEntNormal[MAX_G2_COLLISIONS];
2084 vec3_t bladeDir;
2085 float hitEntStartFrac[MAX_G2_COLLISIONS] = {0};
2086 int trHitLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0
2087 int trDismemberLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0
2088 qboolean trDismember[MAX_G2_COLLISIONS] = {qfalse};//same as 0
2089 int i,z;
2090 int numHitEnts = 0;
2091 float distFromStart,doDmg;
2092 int hitEffect = 0;
2093 const char *trSurfName;
2094 gentity_t *hitEnt;
2095
2096 VectorNormalize2( bladeVec, bladeDir );
2097
2098 for (z=0; z < MAX_G2_COLLISIONS; z++)
2099 {
2100 if ( tr->G2CollisionMap[z].mEntityNum == -1 )
2101 {//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
2102 continue;//break;//
2103 }
2104
2105 CCollisionRecord &coll = tr->G2CollisionMap[z];
2106 //distFromStart = Distance( start, coll.mCollisionPosition );
2107 distFromStart = coll.mDistance;
2108
2109 /*
2110 //FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*...
2111 if ( length && saberHitFraction < 1.0f && (distFromStart/length) < 1.0f && (distFromStart/length) > saberHitFraction )
2112 {//a saber was hit before this point, don't count it
2113 #ifndef FINAL_BUILD
2114 if ( d_saberCombat->integer )
2115 {
2116 gi.Printf( S_COLOR_MAGENTA"rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f\n", (distFromStart/length), saberHitFraction );
2117 }
2118 #endif
2119 continue;
2120 }
2121 */
2122
2123 for ( i = 0; i < numHitEnts; i++ )
2124 {
2125 if ( hitEntNum[i] == coll.mEntityNum )
2126 {//we hit this ent before
2127 //we'll want to add this dist
2128 hitEntDmgAdd[i] = distFromStart;
2129 break;
2130 }
2131 }
2132 if ( i == numHitEnts )
2133 {//first time we hit this ent
2134 if ( numHitEnts == MAX_G2_COLLISIONS )
2135 {//hit too many damn ents!
2136 continue;
2137 }
2138 hitEntNum[numHitEnts] = coll.mEntityNum;
2139 if ( !coll.mFlags )
2140 {//hmm, we came out first, so we must have started inside
2141 //we'll want to subtract this dist
2142 hitEntDmgAdd[numHitEnts] = distFromStart;
2143 }
2144 else
2145 {//we're entering the model
2146 //we'll want to subtract this dist
2147 hitEntDmgSub[numHitEnts] = distFromStart;
2148 }
2149 //keep track of how far in the damage was done
2150 hitEntStartFrac[numHitEnts] = hitEntDmgSub[numHitEnts]/length;
2151 //remember the entrance point
2152 VectorCopy( coll.mCollisionPosition, hitEntPoint[numHitEnts] );
2153 //remember the normal of the face we hit
2154 VectorCopy( coll.mCollisionNormal, hitEntNormal[numHitEnts] );
2155 VectorNormalize( hitEntNormal[numHitEnts] );
2156
2157 //do the effect
2158
2159 //FIXME: check material rather than team?
2160 hitEnt = &g_entities[hitEntNum[numHitEnts]];
2161 if ( hitEnt
2162 && hitEnt->client
2163 && coll.mModelIndex > 0 )
2164 {//hit a submodel on the enemy, not their actual body!
2165 if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2166 && saber->hitOtherEffect )
2167 {
2168 hitEffect = saber->hitOtherEffect;
2169 }
2170 else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2171 && saber->hitOtherEffect2 )
2172 {
2173 hitEffect = saber->hitOtherEffect2;
2174 }
2175 else
2176 {
2177 hitEffect = G_EffectIndex( hit_sparks );
2178 }
2179 }
2180 else
2181 {
2182 if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2183 && saber->hitPersonEffect )
2184 {
2185 hitEffect = saber->hitPersonEffect;
2186 }
2187 else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2188 && saber->hitPersonEffect2 )
2189 {
2190 hitEffect = saber->hitPersonEffect2;
2191 }
2192 else
2193 {
2194 hitEffect = G_EffectIndex( hit_blood_sparks );
2195 }
2196 }
2197 if ( hitEnt != NULL )
2198 {
2199 if ( hitEnt->client )
2200 {
2201 class_t npc_class = hitEnt->client->NPC_class;
2202 if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
2203 npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
2204 npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
2205 npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
2206 {
2207 if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2208 && saber->hitOtherEffect )
2209 {
2210 hitEffect = saber->hitOtherEffect;
2211 }
2212 else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2213 && saber->hitOtherEffect2 )
2214 {
2215 hitEffect = saber->hitOtherEffect2;
2216 }
2217 else
2218 {
2219 hitEffect = G_EffectIndex( hit_sparks );
2220 }
2221 }
2222 }
2223 else
2224 {
2225 // So sue me, this is the easiest way to check to see if this is the turbo laser from t2_wedge,
2226 // in which case I don't want the saber effects goin off on it.
2227 if ( (hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY)
2228 && hitEnt->takedamage == qfalse
2229 && Q_stricmp( hitEnt->classname, "misc_turret" ) == 0 )
2230 {
2231 continue;
2232 }
2233 else
2234 {
2235 if ( dmg )
2236 {//only do these effects if actually trying to damage the thing...
2237 if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush
2238 && ( (hitEnt->spawnflags&1)//INVINCIBLE
2239 ||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only
2240 )
2241 {//no hit effect (besides regular client-side one)
2242 hitEffect = 0;
2243 }
2244 else
2245 {
2246 if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2247 && saber->hitOtherEffect )
2248 {
2249 hitEffect = saber->hitOtherEffect;
2250 }
2251 else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2252 && saber->hitOtherEffect2 )
2253 {
2254 hitEffect = saber->hitOtherEffect2;
2255 }
2256 else
2257 {
2258 hitEffect = G_EffectIndex( hit_sparks );
2259 }
2260 }
2261 }
2262 }
2263 }
2264 }
2265
2266 //FIXME: play less if damage is less?
2267 if ( !g_saberNoEffects )
2268 {
2269 if ( hitEffect != 0 )
2270 {
2271 //FIXME: when you have multiple blades hitting someone for many sequential server frames, this can get a bit chuggy!
2272 G_PlayEffect( hitEffect, coll.mCollisionPosition, coll.mCollisionNormal );
2273 }
2274 }
2275
2276 //Get the hit location based on surface name
2277 if ( (hitLoc[numHitEnts] == HL_NONE && trHitLoc[numHitEnts] == HL_NONE)
2278 || (hitDismemberLoc[numHitEnts] == HL_NONE && trDismemberLoc[numHitEnts] == HL_NONE)
2279 || (!hitDismember[numHitEnts] && !trDismember[numHitEnts]) )
2280 {//no hit loc set for this ent this damage cycle yet
2281 //FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName?
2282 //FIXED: if hit multiple ents in this collision record, these trSurfName, trDismember and trDismemberLoc will get stomped/confused over the multiple ents I hit
2283 trSurfName = gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex );
2284 trDismember[numHitEnts] = G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], trSurfName, &trHitLoc[numHitEnts], coll.mCollisionPosition, dmgDir, bladeDir, MOD_SABER, saberType );
2285 if ( trDismember[numHitEnts] )
2286 {
2287 trDismemberLoc[numHitEnts] = trHitLoc[numHitEnts];
2288 }
2289 /*
2290 if ( trDismember[numHitEnts] )
2291 {
2292 Com_Printf( S_COLOR_RED"Okay to dismember %s on ent %d\n", hitLocName[trDismemberLoc[numHitEnts]], hitEntNum[numHitEnts] );
2293 }
2294 else
2295 {
2296 Com_Printf( "Hit (no dismember) %s on ent %d\n", hitLocName[trHitLoc[numHitEnts]], hitEntNum[numHitEnts] );
2297 }
2298 */
2299 }
2300 numHitEnts++;
2301 }
2302 }
2303
2304 //now go through all the ents we hit and do the damage
2305 for ( i = 0; i < numHitEnts; i++ )
2306 {
2307 doDmg = dmg;
2308 if ( hitEntNum[i] != ENTITYNUM_NONE )
2309 {
2310 if ( doDmg < 10 )
2311 {//base damage is less than 10
2312 if ( hitEntNum[i] != 0 )
2313 {//not the player
2314 hitEnt = &g_entities[hitEntNum[i]];
2315 if ( !hitEnt->client || (hitEnt->client->ps.weapon!=WP_SABER&&hitEnt->client->NPC_class!=CLASS_GALAKMECH&&hitEnt->client->playerTeam==enemyTeam) )
2316 {//did *not* hit a jedi and did *not* hit the player
2317 //make sure the base damage is high against non-jedi, feels better
2318 doDmg = 10;
2319 }
2320 }
2321 }
2322 if ( !hitEntDmgAdd[i] && !hitEntDmgSub[i] )
2323 {//spent entire time in model
2324 //NOTE: will we even get a collision then?
2325 doDmg *= length;
2326 }
2327 else if ( hitEntDmgAdd[i] && hitEntDmgSub[i] )
2328 {//we did enter and exit
2329 doDmg *= hitEntDmgAdd[i] - hitEntDmgSub[i];
2330 }
2331 else if ( !hitEntDmgAdd[i] )
2332 {//we didn't exit, just entered
2333 doDmg *= length - hitEntDmgSub[i];
2334 }
2335 else if ( !hitEntDmgSub[i] )
2336 {//we didn't enter, only exited
2337 doDmg *= hitEntDmgAdd[i];
2338 }
2339 if ( doDmg > 0 )
2340 {
2341 WP_SaberDamageAdd( 1.0, hitEntNum[i], dmgDir, bladeVec, hitEntNormal[i], hitEntPoint[i], ceil(doDmg), hitEntStartFrac[i], trHitLoc[i], trDismember[i], trDismemberLoc[i] );
2342 }
2343 }
2344 }
2345 return (qboolean)(numHitEnts>0);
2346 }
2347
WP_SaberBlockEffect(gentity_t * attacker,int saberNum,int bladeNum,vec3_t position,vec3_t normal,qboolean cutNotBlock)2348 void WP_SaberBlockEffect( gentity_t *attacker, int saberNum, int bladeNum, vec3_t position, vec3_t normal, qboolean cutNotBlock )
2349 {
2350 saberInfo_t *saber = NULL;
2351
2352 if ( attacker && attacker->client )
2353 {
2354 saber = &attacker->client->ps.saber[saberNum];
2355 }
2356
2357 if ( saber
2358 && !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2359 && saber->blockEffect )
2360 {
2361 if ( normal )
2362 {
2363 G_PlayEffect( saber->blockEffect, position, normal );
2364 }
2365 else
2366 {
2367 G_PlayEffect( saber->blockEffect, position );
2368 }
2369 }
2370 else if ( saber
2371 && WP_SaberBladeUseSecondBladeStyle( saber, bladeNum )
2372 && saber->blockEffect2 )
2373 {
2374 if ( normal )
2375 {
2376 G_PlayEffect( saber->blockEffect2, position, normal );
2377 }
2378 else
2379 {
2380 G_PlayEffect( saber->blockEffect2, position );
2381 }
2382 }
2383 else if ( cutNotBlock )
2384 {
2385 if ( normal )
2386 {
2387 G_PlayEffect( "saber/saber_cut", position, normal );
2388 }
2389 else
2390 {
2391 G_PlayEffect( "saber/saber_cut", position );
2392 }
2393 }
2394 else
2395 {
2396
2397 if ( normal )
2398 {
2399 G_PlayEffect( "saber/saber_block", position, normal );
2400 }
2401 else
2402 {
2403 G_PlayEffect( "saber/saber_block", position );
2404 }
2405 }
2406 }
2407
WP_SaberKnockaway(gentity_t * attacker,trace_t * tr)2408 void WP_SaberKnockaway( gentity_t *attacker, trace_t *tr )
2409 {
2410 WP_SaberDrop( attacker, &g_entities[attacker->client->ps.saberEntityNum] );
2411 WP_SaberBlockSound( attacker, NULL, 0, 0 );
2412 //G_Sound( &g_entities[attacker->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
2413 WP_SaberBlockEffect( attacker, 0, 0, tr->endpos, NULL, qfalse );
2414 saberHitFraction = tr->fraction;
2415 #ifndef FINAL_BUILD
2416 if ( d_saberCombat->integer )
2417 {
2418 gi.Printf( S_COLOR_MAGENTA"WP_SaberKnockaway: saberHitFraction %4.2f\n", saberHitFraction );
2419 }
2420 #endif
2421 VectorCopy( tr->endpos, saberHitLocation );
2422 saberHitEntity = tr->entityNum;
2423 if ( !g_noClashFlare )
2424 {
2425 g_saberFlashTime = level.time-50;
2426 VectorCopy( saberHitLocation, g_saberFlashPos );
2427 }
2428
2429 //FIXME: make hitEnt play an attack anim or some other special anim when this happens
2430 //gentity_t *hitEnt = &g_entities[tr->entityNum];
2431 //NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2432 }
2433
G_InCinematicSaberAnim(gentity_t * self)2434 qboolean G_InCinematicSaberAnim( gentity_t *self )
2435 {
2436 if ( self->NPC
2437 && self->NPC->behaviorState == BS_CINEMATIC
2438 && (self->client->ps.torsoAnim == BOTH_CIN_16 ||self->client->ps.torsoAnim == BOTH_CIN_17) )
2439 {
2440 return qtrue;
2441 }
2442 return qfalse;
2443 }
2444
2445 #define SABER_COLLISION_DIST 6//was 2//was 4//was 8//was 16
2446 extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
WP_SaberDamageForTrace(int ignore,vec3_t start,vec3_t end,float dmg,vec3_t bladeDir,qboolean noGhoul,int attackStrength,saberType_t saberType,qboolean extrapolate,int saberNum,int bladeNum)2447 qboolean WP_SaberDamageForTrace( int ignore, vec3_t start, vec3_t end, float dmg,
2448 vec3_t bladeDir, qboolean noGhoul, int attackStrength,
2449 saberType_t saberType, qboolean extrapolate,
2450 int saberNum, int bladeNum )
2451 {
2452 trace_t tr;
2453 vec3_t dir;
2454 int mask = (MASK_SHOT|CONTENTS_LIGHTSABER);
2455 gentity_t *attacker = &g_entities[ignore];
2456
2457 vec3_t end2;
2458 VectorCopy( end, end2 );
2459 if ( extrapolate )
2460 {
2461 //NOTE: since we can no longer use the predicted point, extrapolate the trace some.
2462 // this may allow saber hits that aren't actually hits, but it doesn't look too bad
2463 vec3_t diff;
2464 VectorSubtract( end, start, diff );
2465 VectorNormalize( diff );
2466 VectorMA( end2, SABER_EXTRAPOLATE_DIST, diff, end2 );
2467 }
2468
2469 if ( !noGhoul )
2470 {
2471 float useRadiusForDamage = 0;
2472
2473 if ( attacker
2474 && attacker->client )
2475 {//see if we're not drawing the blade, if so, do a trace based on radius of blade (because the radius is being used to simulate a larger/smaller piece of a solid weapon)...
2476 if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2477 && (attacker->client->ps.saber[saberNum].saberFlags2&SFL2_NO_BLADE) )
2478 {//not drawing blade
2479 useRadiusForDamage = attacker->client->ps.saber[saberNum].blade[bladeNum].radius;
2480 }
2481 else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2482 && (attacker->client->ps.saber[saberNum].saberFlags2&SFL2_NO_BLADE2) )
2483 {//not drawing blade
2484 useRadiusForDamage = attacker->client->ps.saber[saberNum].blade[bladeNum].radius;
2485 }
2486 }
2487 if ( !useRadiusForDamage )
2488 {//do normal check for larger-size saber traces
2489 if ( !attacker->s.number
2490 || (attacker->client
2491 && (attacker->client->playerTeam==TEAM_PLAYER
2492 || attacker->client->NPC_class==CLASS_SHADOWTROOPER
2493 || attacker->client->NPC_class==CLASS_ALORA
2494 || (attacker->NPC && (attacker->NPC->aiFlags&NPCAI_BOSS_CHARACTER))
2495 )
2496 )
2497 )//&&attackStrength==FORCE_LEVEL_3)
2498 {
2499 useRadiusForDamage = 2;
2500 }
2501 }
2502
2503 if ( useRadiusForDamage > 0 )//&&attackStrength==FORCE_LEVEL_3)
2504 {//player,. player allies, shadowtroopers, tavion and desann use larger traces
2505 vec3_t traceMins = {-useRadiusForDamage,-useRadiusForDamage,-useRadiusForDamage}, traceMaxs = {useRadiusForDamage,useRadiusForDamage,useRadiusForDamage};
2506 gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
2507 }
2508 /*
2509 else if ( !attacker->s.number )
2510 {
2511 vec3_t traceMins = {-1,-1,-1}, traceMaxs = {1,1,1};
2512 gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
2513 }
2514 */
2515 else
2516 {//reborn use smaller traces
2517 gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
2518 }
2519 }
2520 else
2521 {
2522 gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_NOCOLLIDE, 10 );
2523 }
2524
2525
2526 #ifndef FINAL_BUILD
2527 if ( d_saberCombat->integer > 1 )
2528 {
2529 if ( attacker != NULL && attacker->client != NULL )
2530 {
2531 G_DebugLine(start, end2, FRAMETIME, WPDEBUG_SaberColor( attacker->client->ps.saber[0].blade[0].color ), qtrue);
2532 }
2533 }
2534 #endif
2535
2536 if ( tr.entityNum == ENTITYNUM_NONE )
2537 {
2538 return qfalse;
2539 }
2540
2541 if ( tr.entityNum == ENTITYNUM_WORLD )
2542 {
2543 if ( attacker && attacker->client && (attacker->client->ps.saber[saberNum].saberFlags&SFL_BOUNCE_ON_WALLS) )
2544 {
2545 VectorCopy( tr.endpos, saberHitLocation );
2546 VectorCopy( tr.plane.normal, saberHitNormal );
2547 }
2548 return qtrue;
2549 }
2550
2551 if ( &g_entities[tr.entityNum] )
2552 {
2553 gentity_t *hitEnt = &g_entities[tr.entityNum];
2554 gentity_t *owner = g_entities[tr.entityNum].owner;
2555 if ( hitEnt->contents & CONTENTS_LIGHTSABER )
2556 {
2557 if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
2558 {//thrown saber hit something
2559 if ( owner
2560 && owner->s.number
2561 && owner->client
2562 && owner->NPC
2563 && owner->health > 0 )
2564 {
2565 if ( owner->client->NPC_class == CLASS_ALORA )
2566 {//alora takes less damage
2567 dmg *= 0.25f;
2568 }
2569 else if ( owner->client->NPC_class == CLASS_TAVION
2570 /*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 ))
2571 || (Q_irand( -5, owner->NPC->rank ) > RANK_CIVILIAN && !Q_irand( 0, g_spskill->integer*3 ))*/ )
2572 {//Tavion can toss a blocked thrown saber aside
2573 WP_SaberKnockaway( attacker, &tr );
2574 Jedi_PlayDeflectSound( owner );
2575 return qfalse;
2576 }
2577 }
2578 }
2579 //FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow?
2580 qboolean sabersIntersect = WP_SabersIntersect( attacker, saberNum, bladeNum, owner, qfalse );//qtrue );
2581 float sabersDist;
2582 if ( attacker && attacker->client && attacker->client->ps.saberInFlight
2583 && owner && owner->s.number == 0 && (g_saberAutoBlocking->integer||attacker->client->ps.saberBlockingTime>level.time) )//NPC flying saber hit player's saber bounding box
2584 {//players have g_saberAutoBlocking, do the more generous check against flying sabers
2585 //FIXME: instead of hitting the player's saber bounding box
2586 //and picking an anim afterwards, have him use AI similar
2587 //to the AI the jedi use for picking a saber melee block...?
2588 sabersDist = 0;
2589 }
2590 else
2591 {//sabers must actually collide with the attacking saber
2592 sabersDist = WP_SabersDistance( attacker, owner );
2593 if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
2594 {
2595 sabersDist /= 2.0f;
2596 if ( sabersDist <= 16.0f )
2597 {
2598 sabersIntersect = qtrue;
2599 }
2600 }
2601 #ifndef FINAL_BUILD
2602 if ( d_saberCombat->integer > 1 )
2603 {
2604 gi.Printf( "sabersDist: %4.2f\n", sabersDist );
2605 }
2606 #endif//FINAL_BUILD
2607 }
2608 if ( sabersCrossed == -1 || sabersCrossed > sabersDist )
2609 {
2610 sabersCrossed = sabersDist;
2611 }
2612 float collisionDist;
2613 if ( g_saberRealisticCombat->integer )
2614 {
2615 collisionDist = SABER_COLLISION_DIST;
2616 }
2617 else
2618 {
2619 collisionDist = SABER_COLLISION_DIST+6+g_spskill->integer*4;
2620 }
2621 {
2622 if ( G_InCinematicSaberAnim( owner )
2623 && G_InCinematicSaberAnim( attacker ) )
2624 {
2625 sabersIntersect = qtrue;
2626 }
2627 }
2628 if ( owner && owner->client && (attacker != NULL)
2629 && (sabersDist > collisionDist )//|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f ))
2630 && !sabersIntersect )//was qtrue, but missed too much?
2631 {//swing came from behind and/or was not stopped by a lightsaber
2632 //re-try the trace without checking for lightsabers
2633 gi.trace ( &tr, start, NULL, NULL, end2, ignore, mask&~CONTENTS_LIGHTSABER, G2_NOCOLLIDE, 10 );
2634 if ( tr.entityNum == ENTITYNUM_WORLD )
2635 {
2636 return qtrue;
2637 }
2638 if ( tr.entityNum == ENTITYNUM_NONE || &g_entities[tr.entityNum] == NULL )
2639 {//didn't hit the owner
2640 /*
2641 if ( attacker
2642 && attacker->client
2643 && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
2644 && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
2645 {
2646 if ( owner->NPC
2647 && !owner->client->ps.saberInFlight
2648 && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
2649 && !Jedi_SaberBusy( owner ) )
2650 {//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
2651 if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
2652 {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
2653 //FIXME: also take into account the owner's FP_DEFENSE?
2654 if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
2655 {//lower-rank Jedi aren't as good blockers
2656 vec3_t attDir;
2657 VectorSubtract( end2, start, attDir );
2658 VectorNormalize( attDir );
2659 Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
2660 }
2661 }
2662 }
2663 }
2664 */
2665 return qfalse; // Exit, but we didn't hit the wall.
2666 }
2667 #ifndef FINAL_BUILD
2668 if ( d_saberCombat->integer > 1 )
2669 {
2670 if ( !attacker->s.number )
2671 {
2672 gi.Printf( S_COLOR_MAGENTA"%d saber hit owner through saber %4.2f, dist = %4.2f\n", level.time, saberHitFraction, sabersDist );
2673 }
2674 }
2675 #endif//FINAL_BUILD
2676 hitEnt = &g_entities[tr.entityNum];
2677 owner = g_entities[tr.entityNum].owner;
2678 }
2679 else
2680 {//hit a lightsaber
2681 if ( (tr.fraction < saberHitFraction || tr.startsolid)
2682 && sabersDist < (8.0f+g_spskill->value)*4.0f// 50.0f//16.0f
2683 && (sabersIntersect || sabersDist < (4.0f+g_spskill->value)*2.0f) )//32.0f) )
2684 { // This saber hit closer than the last one.
2685 if ( (tr.allsolid || tr.startsolid) && owner && owner->client )
2686 {//tr.fraction will be 0, unreliable... so calculate actual
2687 float dist = Distance( start, end2 );
2688 if ( dist )
2689 {
2690 float hitFrac = WP_SabersDistance( attacker, owner )/dist;
2691 if ( hitFrac > 1.0f )
2692 {//umm... minimum distance between sabers was longer than trace...?
2693 hitFrac = 1.0f;
2694 }
2695 if ( hitFrac < saberHitFraction )
2696 {
2697 saberHitFraction = hitFrac;
2698 }
2699 }
2700 else
2701 {
2702 saberHitFraction = 0.0f;
2703 }
2704 #ifndef FINAL_BUILD
2705 if ( d_saberCombat->integer > 1 )
2706 {
2707 if ( !attacker->s.number )
2708 {
2709 gi.Printf( S_COLOR_GREEN"%d saber hit saber dist %4.2f allsolid %4.2f\n", level.time, sabersDist, saberHitFraction );
2710 }
2711 }
2712 #endif//FINAL_BUILD
2713 }
2714 else
2715 {
2716 #ifndef FINAL_BUILD
2717 if ( d_saberCombat->integer > 1 )
2718 {
2719 if ( !attacker->s.number )
2720 {
2721 gi.Printf( S_COLOR_BLUE"%d saber hit saber dist %4.2f, frac %4.2f\n", level.time, sabersDist, saberHitFraction );
2722 }
2723 saberHitFraction = tr.fraction;
2724 }
2725 #endif//FINAL_BUILD
2726 }
2727 #ifndef FINAL_BUILD
2728 if ( d_saberCombat->integer )
2729 {
2730 gi.Printf( S_COLOR_MAGENTA"hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d\n", saberHitFraction, tr.allsolid, tr.startsolid );
2731 }
2732 #endif//FINAL_BUILD
2733 VectorCopy(tr.endpos, saberHitLocation);
2734 saberHitEntity = tr.entityNum;
2735 }
2736 /*
2737 if ( owner
2738 && owner->client
2739 && attacker
2740 && attacker->client
2741 && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
2742 && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
2743 {
2744 if ( owner->NPC
2745 && !owner->client->ps.saberInFlight
2746 && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
2747 && !Jedi_SaberBusy( owner ) )
2748 {//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber
2749 if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
2750 {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
2751 //FIXME: also take into account the owner's FP_DEFENSE?
2752 if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
2753 {//lower-rank Jedi aren't as good blockers
2754 vec3_t attDir;
2755 VectorSubtract( end2, start, attDir );
2756 VectorNormalize( attDir );
2757 Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
2758 }
2759 }
2760 }
2761 }
2762 */
2763 //FIXME: check to see if we broke the saber
2764 // go through the impacted surfaces and call WP_BreakSaber
2765 // PROBLEM: saberEnt doesn't actually have a saber g2 model
2766 // and/or isn't in same location as saber model attached
2767 // to the client. We'd have to fake it somehow...
2768 return qfalse; // Exit, but we didn't hit the wall.
2769 }
2770 }
2771 else
2772 {
2773 #ifndef FINAL_BUILD
2774 if ( d_saberCombat->integer > 1 )
2775 {
2776 if ( !attacker->s.number )
2777 {
2778 gi.Printf( S_COLOR_RED"%d saber hit owner directly %4.2f\n", level.time, saberHitFraction );
2779 }
2780 }
2781 #endif//FINAL_BUILD
2782 }
2783
2784 if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
2785 {//thrown saber hit something
2786 if ( ( hitEnt && hitEnt->client && hitEnt->health > 0 && ( hitEnt->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",hitEnt->NPC_type) || hitEnt->client->NPC_class == CLASS_LUKE || hitEnt->client->NPC_class == CLASS_BOBAFETT || (hitEnt->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) ||
2787 ( owner && owner->client && owner->health > 0 && ( owner->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",owner->NPC_type) || owner->client->NPC_class == CLASS_LUKE || (owner->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) )
2788 {//Luke and Desann slap thrown sabers aside
2789 //FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin?
2790 WP_SaberKnockaway( attacker, &tr );
2791 if ( hitEnt->client )
2792 {
2793 Jedi_PlayDeflectSound( hitEnt );
2794 }
2795 else
2796 {
2797 Jedi_PlayDeflectSound( owner );
2798 }
2799 return qfalse; // Exit, but we didn't hit the wall.
2800 }
2801 }
2802
2803 if ( hitEnt->takedamage )
2804 {
2805 //no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) )
2806 {
2807 vec3_t bladeVec={0};
2808 if ( attacker && attacker->client )
2809 {
2810 VectorScale( bladeDir, attacker->client->ps.saber[saberNum].blade[bladeNum].length, bladeVec );
2811 }
2812 //multiply the damage by the total distance of the swipe
2813 VectorSubtract( end2, start, dir );
2814 float len = VectorNormalize( dir );//VectorLength( dir );
2815 if ( noGhoul || !hitEnt->ghoul2.size() )
2816 {//we weren't doing a ghoul trace
2817 int hitEffect = 0;
2818 if ( dmg >= 1.0 && hitEnt->bmodel )
2819 {
2820 dmg = 1.0;
2821 }
2822 if ( len > 1 )
2823 {
2824 dmg *= len;
2825 }
2826 #ifndef FINAL_BUILD
2827 if ( d_saberCombat->integer > 1 )
2828 {
2829 if ( !(hitEnt->contents & CONTENTS_LIGHTSABER) )
2830 {
2831 gi.Printf( S_COLOR_GREEN"Hit ent, but no ghoul collisions\n" );
2832 }
2833 }
2834 #endif
2835 float trFrac, dmgFrac;
2836 if ( tr.allsolid )
2837 {//totally inside them
2838 trFrac = 1.0;
2839 dmgFrac = 0.0;
2840 }
2841 else if ( tr.startsolid )
2842 {//started inside them
2843 //we don't know how much was inside, we know it's less than all, so use half?
2844 trFrac = 0.5;
2845 dmgFrac = 0.0;
2846 }
2847 else
2848 {//started outside them and hit them
2849 //yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2)
2850 trFrac = (1.0f - tr.fraction);
2851 dmgFrac = tr.fraction;
2852 }
2853 vec3_t backdir;
2854 VectorScale( dir, -1, backdir );
2855 WP_SaberDamageAdd( trFrac, tr.entityNum, dir, bladeVec, backdir, tr.endpos, dmg, dmgFrac, HL_NONE, qfalse, HL_NONE );
2856 if ( !tr.allsolid && !tr.startsolid )
2857 {
2858 VectorScale( dir, -1, dir );
2859 }
2860 if ( hitEnt != NULL )
2861 {
2862 if ( hitEnt->client )
2863 {
2864 //don't do blood sparks on non-living things
2865 class_t npc_class = hitEnt->client->NPC_class;
2866 if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
2867 npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE ||
2868 npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
2869 npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
2870 {
2871 if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2872 && attacker->client->ps.saber[saberNum].hitOtherEffect )
2873 {
2874 hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect;
2875 }
2876 else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2877 && attacker->client->ps.saber[saberNum].hitOtherEffect2 )
2878 {
2879 hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect2;
2880 }
2881 else
2882 {
2883 hitEffect = G_EffectIndex( hit_sparks );
2884 }
2885 }
2886 }
2887 else
2888 {
2889 if ( dmg )
2890 {//only do these effects if actually trying to damage the thing...
2891 if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush
2892 && ( (hitEnt->spawnflags&1)//INVINCIBLE
2893 ||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY)//HEAVY weapon damage only
2894 ||(hitEnt->NPC_targetname&&attacker&&attacker->targetname&&Q_stricmp(attacker->targetname,hitEnt->NPC_targetname)) ) )//only breakable by an entity who is not the attacker
2895 {//no hit effect (besides regular client-side one)
2896 }
2897 else
2898 {
2899 if ( !WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2900 && attacker->client->ps.saber[saberNum].hitOtherEffect )
2901 {
2902 hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect;
2903 }
2904 else if ( WP_SaberBladeUseSecondBladeStyle( &attacker->client->ps.saber[saberNum], bladeNum )
2905 && attacker->client->ps.saber[saberNum].hitOtherEffect2 )
2906 {
2907 hitEffect = attacker->client->ps.saber[saberNum].hitOtherEffect2;
2908 }
2909 else
2910 {
2911 hitEffect = G_EffectIndex( hit_sparks );
2912 }
2913 }
2914 }
2915 }
2916 }
2917
2918 if ( !g_saberNoEffects && hitEffect != 0 )
2919 {
2920 G_PlayEffect( hitEffect, tr.endpos, dir );//"saber_cut"
2921 }
2922 }
2923 else
2924 {//we were doing a ghoul trace
2925 if ( !attacker
2926 || !attacker->client
2927 || attacker->client->ps.saberLockTime < level.time )
2928 {
2929 if ( !WP_SaberDamageEffects( &tr, start, len, dmg, dir, bladeVec, attacker->client->enemyTeam, saberType, &attacker->client->ps.saber[saberNum], bladeNum ) )
2930 {//didn't hit a ghoul ent
2931 /*
2932 if ( && hitEnt->ghoul2.size() )
2933 {//it was a ghoul2 model so we should have hit it
2934 return qfalse;
2935 }
2936 */
2937 }
2938 }
2939 }
2940 }
2941 }
2942 }
2943
2944 return qfalse;
2945 }
2946
2947 #define LOCK_IDEAL_DIST_TOP 32.0f
2948 #define LOCK_IDEAL_DIST_CIRCLE 48.0f
2949 #define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
2950 extern void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs );
2951 extern qboolean ValidAnimFileIndex ( int index );
G_SaberLockAnim(int attackerSaberStyle,int defenderSaberStyle,int topOrSide,int lockOrBreakOrSuperBreak,int winOrLose)2952 int G_SaberLockAnim( int attackerSaberStyle, int defenderSaberStyle, int topOrSide, int lockOrBreakOrSuperBreak, int winOrLose )
2953 {
2954 int baseAnim = -1;
2955 if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
2956 {//special case: if we're using the same style and locking
2957 if ( attackerSaberStyle == defenderSaberStyle
2958 || (attackerSaberStyle>=SS_FAST&&attackerSaberStyle<=SS_TAVION&&defenderSaberStyle>=SS_FAST&&defenderSaberStyle<=SS_TAVION) )
2959 {//using same style
2960 if ( winOrLose == SABERLOCK_LOSE )
2961 {//you want the defender's stance...
2962 switch ( defenderSaberStyle )
2963 {
2964 case SS_DUAL:
2965 if ( topOrSide == SABERLOCK_TOP )
2966 {
2967 baseAnim = BOTH_LK_DL_DL_T_L_2;
2968 }
2969 else
2970 {
2971 baseAnim = BOTH_LK_DL_DL_S_L_2;
2972 }
2973 break;
2974 case SS_STAFF:
2975 if ( topOrSide == SABERLOCK_TOP )
2976 {
2977 baseAnim = BOTH_LK_ST_ST_T_L_2;
2978 }
2979 else
2980 {
2981 baseAnim = BOTH_LK_ST_ST_S_L_2;
2982 }
2983 break;
2984 default:
2985 if ( topOrSide == SABERLOCK_TOP )
2986 {
2987 baseAnim = BOTH_LK_S_S_T_L_2;
2988 }
2989 else
2990 {
2991 baseAnim = BOTH_LK_S_S_S_L_2;
2992 }
2993 break;
2994 }
2995 }
2996 }
2997 }
2998 if ( baseAnim == -1 )
2999 {
3000 switch ( attackerSaberStyle )
3001 {
3002 case SS_DUAL:
3003 switch ( defenderSaberStyle )
3004 {
3005 case SS_DUAL:
3006 baseAnim = BOTH_LK_DL_DL_S_B_1_L;
3007 break;
3008 case SS_STAFF:
3009 baseAnim = BOTH_LK_DL_ST_S_B_1_L;
3010 break;
3011 default://single
3012 baseAnim = BOTH_LK_DL_S_S_B_1_L;
3013 break;
3014 }
3015 break;
3016 case SS_STAFF:
3017 switch ( defenderSaberStyle )
3018 {
3019 case SS_DUAL:
3020 baseAnim = BOTH_LK_ST_DL_S_B_1_L;
3021 break;
3022 case SS_STAFF:
3023 baseAnim = BOTH_LK_ST_ST_S_B_1_L;
3024 break;
3025 default://single
3026 baseAnim = BOTH_LK_ST_S_S_B_1_L;
3027 break;
3028 }
3029 break;
3030 default://single
3031 switch ( defenderSaberStyle )
3032 {
3033 case SS_DUAL:
3034 baseAnim = BOTH_LK_S_DL_S_B_1_L;
3035 break;
3036 case SS_STAFF:
3037 baseAnim = BOTH_LK_S_ST_S_B_1_L;
3038 break;
3039 default://single
3040 baseAnim = BOTH_LK_S_S_S_B_1_L;
3041 break;
3042 }
3043 break;
3044 }
3045 //side lock or top lock?
3046 if ( topOrSide == SABERLOCK_TOP )
3047 {
3048 baseAnim += 5;
3049 }
3050 //lock, break or superbreak?
3051 if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK )
3052 {
3053 baseAnim += 2;
3054 }
3055 else
3056 {//a break or superbreak
3057 if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK )
3058 {
3059 baseAnim += 3;
3060 }
3061 //winner or loser?
3062 if ( winOrLose == SABERLOCK_WIN )
3063 {
3064 baseAnim += 1;
3065 }
3066 }
3067 }
3068 return baseAnim;
3069 }
3070
G_CheckIncrementLockAnim(int anim,int winOrLose)3071 qboolean G_CheckIncrementLockAnim( int anim, int winOrLose )
3072 {
3073 qboolean increment = qfalse;//???
3074 //RULE: if you are the first style in the lock anim, you advance from LOSING position to WINNING position
3075 // if you are the second style in the lock anim, you advance from WINNING position to LOSING position
3076 switch ( anim )
3077 {
3078 //increment to win:
3079 case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated
3080 case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated
3081 case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated
3082 case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated
3083 case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single
3084 case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single
3085 case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff
3086 case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff
3087 case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated
3088 case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated
3089 case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single
3090 case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single
3091 case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated
3092 case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated
3093 if ( winOrLose == SABERLOCK_WIN )
3094 {
3095 increment = qtrue;
3096 }
3097 else
3098 {
3099 increment = qfalse;
3100 }
3101 break;
3102
3103 //decrement to win:
3104 case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual
3105 case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual
3106 case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated
3107 case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated
3108 case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff
3109 case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff
3110 case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual
3111 case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual
3112 case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated
3113 case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated
3114 if ( winOrLose == SABERLOCK_WIN )
3115 {
3116 increment = qfalse;
3117 }
3118 else
3119 {
3120 increment = qtrue;
3121 }
3122 break;
3123 default:
3124 #ifndef FINAL_BUILD
3125 Com_Printf( S_COLOR_RED"ERROR: unknown Saber Lock Anim: %s!!!\n", animTable[anim].name );
3126 #endif
3127 break;
3128 }
3129 return increment;
3130 }
3131
WP_SabersCheckLock2(gentity_t * attacker,gentity_t * defender,sabersLockMode_t lockMode)3132 qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
3133 {
3134 animation_t *anim;
3135 int attAnim, defAnim, advance = 0;
3136 float attStart = 0.5f, defStart = 0.5f;
3137 float idealDist = 48.0f;
3138 //FIXME: this distances need to be modified by the lengths of the sabers involved...
3139 //MATCH ANIMS
3140 if ( lockMode == LOCK_KYLE_GRAB1
3141 || lockMode == LOCK_KYLE_GRAB2
3142 || lockMode == LOCK_KYLE_GRAB3 )
3143 {
3144 float numSpins = 1.0f;
3145 idealDist = 46.0f;//42.0f;
3146 attStart = defStart = 0.0f;
3147
3148 switch ( lockMode )
3149 {
3150 default:
3151 case LOCK_KYLE_GRAB1:
3152 attAnim = BOTH_KYLE_PA_1;
3153 defAnim = BOTH_PLAYER_PA_1;
3154 numSpins = 2.0f;
3155 break;
3156 case LOCK_KYLE_GRAB2:
3157 attAnim = BOTH_KYLE_PA_3;
3158 defAnim = BOTH_PLAYER_PA_3;
3159 numSpins = 1.0f;
3160 break;
3161 case LOCK_KYLE_GRAB3:
3162 attAnim = BOTH_KYLE_PA_2;
3163 defAnim = BOTH_PLAYER_PA_2;
3164 defender->forcePushTime = level.time + PM_AnimLength( defender->client->clientInfo.animFileIndex, BOTH_PLAYER_PA_2 );
3165 numSpins = 3.0f;
3166 break;
3167 }
3168 attacker->client->ps.SaberDeactivate();
3169 defender->client->ps.SaberDeactivate();
3170 if ( d_slowmodeath->integer > 3
3171 && ( defender->s.number < MAX_CLIENTS
3172 || attacker->s.number < MAX_CLIENTS ) )
3173 {
3174 if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
3175 {
3176 int effectTime = PM_AnimLength( attacker->client->clientInfo.animFileIndex, (animNumber_t)attAnim );
3177 int spinTime = floor((float)effectTime/numSpins);
3178 int meFlags = (MEF_MULTI_SPIN);//MEF_NO_TIMESCALE|MEF_NO_VERTBOB|
3179 if ( Q_irand( 0, 1 ) )
3180 {
3181 meFlags |= MEF_REVERSE_SPIN;
3182 }
3183 G_StartMatrixEffect( attacker, meFlags, effectTime, 0.75f, spinTime );
3184 }
3185 }
3186 }
3187 else if ( lockMode == LOCK_FORCE_DRAIN )
3188 {
3189 idealDist = 46.0f;//42.0f;
3190 attStart = defStart = 0.0f;
3191
3192 attAnim = BOTH_FORCE_DRAIN_GRAB_START;
3193 defAnim = BOTH_FORCE_DRAIN_GRABBED;
3194 attacker->client->ps.SaberDeactivate();
3195 defender->client->ps.SaberDeactivate();
3196 }
3197 else
3198 {
3199 if ( lockMode == LOCK_RANDOM )
3200 {
3201 lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
3202 }
3203 //FIXME: attStart% and idealDist will change per saber lock anim pairing... do we need a big table like in bg_panimate.cpp?
3204 if ( attacker->client->ps.saberAnimLevel >= SS_FAST
3205 && attacker->client->ps.saberAnimLevel <= SS_TAVION
3206 && defender->client->ps.saberAnimLevel >= SS_FAST
3207 && defender->client->ps.saberAnimLevel <= SS_TAVION )
3208 {//2 single sabers? Just do it the old way...
3209 switch ( lockMode )
3210 {
3211 case LOCK_TOP:
3212 attAnim = BOTH_BF2LOCK;// - starts in middle
3213 defAnim = BOTH_BF1LOCK;// - starts in middle
3214 attStart = defStart = 0.5f;
3215 idealDist = LOCK_IDEAL_DIST_TOP;
3216 break;
3217 case LOCK_DIAG_TR:
3218 attAnim = BOTH_CCWCIRCLELOCK; //- starts in middle
3219 defAnim = BOTH_CWCIRCLELOCK;// - starts in middle
3220 attStart = defStart = 0.5f;
3221 idealDist = LOCK_IDEAL_DIST_CIRCLE;
3222 break;
3223 case LOCK_DIAG_TL:
3224 attAnim = BOTH_CWCIRCLELOCK;// - starts in middle
3225 defAnim = BOTH_CCWCIRCLELOCK;// - starts in middle
3226 attStart = defStart = 0.5f;
3227 idealDist = LOCK_IDEAL_DIST_CIRCLE;
3228 break;
3229 case LOCK_DIAG_BR:
3230 attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3231 defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3232 attStart = defStart = 0.85f;//move to end of anim
3233 idealDist = LOCK_IDEAL_DIST_CIRCLE;
3234 break;
3235 case LOCK_DIAG_BL:
3236 attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3237 defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3238 attStart = defStart = 0.85f;//move to end of anim
3239 idealDist = LOCK_IDEAL_DIST_CIRCLE;
3240 break;
3241 case LOCK_R:
3242 attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3243 defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3244 attStart = defStart = 0.75f;//move to end of anim
3245 idealDist = LOCK_IDEAL_DIST_CIRCLE;
3246 break;
3247 case LOCK_L:
3248 attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left
3249 defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right
3250 attStart = defStart = 0.75f;//move to end of anim
3251 idealDist = LOCK_IDEAL_DIST_CIRCLE;
3252 break;
3253 default:
3254 return qfalse;
3255 break;
3256 }
3257 }
3258 else
3259 {//use the new system
3260 idealDist = LOCK_IDEAL_DIST_JKA;//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN
3261 if ( lockMode == LOCK_TOP )
3262 {//top lock
3263 attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN );
3264 defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_LOSE );
3265 attStart = defStart = 0.5f;
3266 }
3267 else
3268 {//side lock
3269 switch ( lockMode )
3270 {
3271 case LOCK_DIAG_TR:
3272 attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3273 defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3274 attStart = defStart = 0.5f;
3275 break;
3276 case LOCK_DIAG_TL:
3277 attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3278 defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3279 attStart = defStart = 0.5f;
3280 break;
3281 case LOCK_DIAG_BR:
3282 attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3283 defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3284 if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3285 {
3286 attStart = 0.85f;//move to end of anim
3287 }
3288 else
3289 {
3290 attStart = 0.15f;//start at beginning of anim
3291 }
3292 if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3293 {
3294 defStart = 0.85f;//start at end of anim
3295 }
3296 else
3297 {
3298 defStart = 0.15f;//start at beginning of anim
3299 }
3300 break;
3301 case LOCK_DIAG_BL:
3302 attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3303 defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3304 if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3305 {
3306 attStart = 0.85f;//move to end of anim
3307 }
3308 else
3309 {
3310 attStart = 0.15f;//start at beginning of anim
3311 }
3312 if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3313 {
3314 defStart = 0.85f;//start at end of anim
3315 }
3316 else
3317 {
3318 defStart = 0.15f;//start at beginning of anim
3319 }
3320 break;
3321 case LOCK_R:
3322 attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3323 defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3324 if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3325 {
3326 attStart = 0.75f;//move to end of anim
3327 }
3328 else
3329 {
3330 attStart = 0.25f;//start at beginning of anim
3331 }
3332 if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3333 {
3334 defStart = 0.75f;//start at end of anim
3335 }
3336 else
3337 {
3338 defStart = 0.25f;//start at beginning of anim
3339 }
3340 break;
3341 case LOCK_L:
3342 attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN );
3343 defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE );
3344 //attacker starts with advantage
3345 if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) )
3346 {
3347 attStart = 0.75f;//move to end of anim
3348 }
3349 else
3350 {
3351 attStart = 0.25f;//start at beginning of anim
3352 }
3353 if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) )
3354 {
3355 defStart = 0.75f;//start at end of anim
3356 }
3357 else
3358 {
3359 defStart = 0.25f;//start at beginning of anim
3360 }
3361 break;
3362 default:
3363 return qfalse;
3364 break;
3365 }
3366 }
3367 }
3368 }
3369 //set the proper anims
3370 NPC_SetAnim( attacker, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3371 NPC_SetAnim( defender, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3372 //don't let them store a kick for the whole saberlock....
3373 attacker->client->ps.saberMoveNext = defender->client->ps.saberMoveNext = LS_NONE;
3374 //
3375 if ( attStart > 0.0f )
3376 {
3377 if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
3378 {
3379 anim = &level.knownAnimFileSets[attacker->client->clientInfo.animFileIndex].animations[attAnim];
3380 advance = floor( anim->numFrames*attStart );
3381 PM_SetAnimFrame( attacker, anim->firstFrame + advance, qtrue, qtrue );
3382 #ifndef FINAL_BUILD
3383 if ( d_saberCombat->integer )
3384 {
3385 Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", attacker->NPC_type, animTable[attAnim].name, anim->numFrames-advance );
3386 }
3387 #endif
3388 }
3389 }
3390 if ( defStart > 0.0f )
3391 {
3392 if( ValidAnimFileIndex( defender->client->clientInfo.animFileIndex ) )
3393 {
3394 anim = &level.knownAnimFileSets[defender->client->clientInfo.animFileIndex].animations[defAnim];
3395 advance = ceil( anim->numFrames*defStart );
3396 PM_SetAnimFrame( defender, anim->firstFrame + advance, qtrue, qtrue );//was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims
3397 #ifndef FINAL_BUILD
3398 if ( d_saberCombat->integer )
3399 {
3400 Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", defender->NPC_type, animTable[defAnim].name, advance );
3401 }
3402 #endif
3403 }
3404 }
3405 VectorClear( attacker->client->ps.velocity );
3406 VectorClear( attacker->client->ps.moveDir );
3407 VectorClear( defender->client->ps.velocity );
3408 VectorClear( defender->client->ps.moveDir );
3409 if ( lockMode == LOCK_KYLE_GRAB1
3410 || lockMode == LOCK_KYLE_GRAB2
3411 || lockMode == LOCK_KYLE_GRAB3
3412 || lockMode == LOCK_FORCE_DRAIN )
3413 {//not a real lock, just freeze them both in place
3414 //can't move or attack
3415 attacker->client->ps.pm_time = attacker->client->ps.weaponTime = attacker->client->ps.legsAnimTimer;
3416 attacker->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3417 attacker->painDebounceTime = level.time + attacker->client->ps.pm_time;
3418 if ( lockMode != LOCK_FORCE_DRAIN )
3419 {
3420 defender->client->ps.torsoAnimTimer += 200;
3421 defender->client->ps.legsAnimTimer += 200;
3422 }
3423 defender->client->ps.pm_time = defender->client->ps.weaponTime = defender->client->ps.legsAnimTimer;
3424 defender->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3425 if ( lockMode != LOCK_FORCE_DRAIN )
3426 {
3427 attacker->aimDebounceTime = level.time + attacker->client->ps.pm_time;
3428 }
3429 }
3430 else
3431 {
3432 attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + SABER_LOCK_TIME;
3433 attacker->client->ps.legsAnimTimer = attacker->client->ps.torsoAnimTimer = defender->client->ps.legsAnimTimer = defender->client->ps.torsoAnimTimer = SABER_LOCK_TIME;
3434 //attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME;
3435 attacker->client->ps.saberLockEnemy = defender->s.number;
3436 defender->client->ps.saberLockEnemy = attacker->s.number;
3437 }
3438
3439 //MATCH ANGLES
3440 if ( lockMode == LOCK_KYLE_GRAB1
3441 || lockMode == LOCK_KYLE_GRAB2
3442 || lockMode == LOCK_KYLE_GRAB3 )
3443 {//not a real lock, just set pitch to 0
3444 attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH] = 0;
3445 }
3446 else
3447 {
3448 //FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer?
3449 float defPitchAdd = 0, zDiff = ((attacker->currentOrigin[2]+attacker->client->standheight)-(defender->currentOrigin[2]+defender->client->standheight));
3450 if ( zDiff > 24 )
3451 {
3452 defPitchAdd = -30;
3453 }
3454 else if ( zDiff < -24 )
3455 {
3456 defPitchAdd = 30;
3457 }
3458 else
3459 {
3460 defPitchAdd = zDiff/24.0f*-30.0f;
3461 }
3462 if ( attacker->NPC && defender->NPC )
3463 {//if 2 NPCs, just set pitch to 0
3464 attacker->client->ps.viewangles[PITCH] = -defPitchAdd;
3465 defender->client->ps.viewangles[PITCH] = defPitchAdd;
3466 }
3467 else
3468 {//if a player is involved, clamp player's pitch and match NPC's to player
3469 if ( !attacker->s.number )
3470 {
3471 //clamp to defPitch
3472 if ( attacker->client->ps.viewangles[PITCH] > -defPitchAdd + 10 )
3473 {
3474 attacker->client->ps.viewangles[PITCH] = -defPitchAdd + 10;
3475 }
3476 else if ( attacker->client->ps.viewangles[PITCH] < -defPitchAdd-10 )
3477 {
3478 attacker->client->ps.viewangles[PITCH] = -defPitchAdd-10;
3479 }
3480 //clamp to sane numbers
3481 if ( attacker->client->ps.viewangles[PITCH] > 50 )
3482 {
3483 attacker->client->ps.viewangles[PITCH] = 50;
3484 }
3485 else if ( attacker->client->ps.viewangles[PITCH] < -50 )
3486 {
3487 attacker->client->ps.viewangles[PITCH] = -50;
3488 }
3489 defender->client->ps.viewangles[PITCH] = attacker->client->ps.viewangles[PITCH]*-1;
3490 defPitchAdd = defender->client->ps.viewangles[PITCH];
3491 }
3492 else if ( !defender->s.number )
3493 {
3494 //clamp to defPitch
3495 if ( defender->client->ps.viewangles[PITCH] > defPitchAdd + 10 )
3496 {
3497 defender->client->ps.viewangles[PITCH] = defPitchAdd + 10;
3498 }
3499 else if ( defender->client->ps.viewangles[PITCH] < defPitchAdd-10 )
3500 {
3501 defender->client->ps.viewangles[PITCH] = defPitchAdd-10;
3502 }
3503 //clamp to sane numbers
3504 if ( defender->client->ps.viewangles[PITCH] > 50 )
3505 {
3506 defender->client->ps.viewangles[PITCH] = 50;
3507 }
3508 else if ( defender->client->ps.viewangles[PITCH] < -50 )
3509 {
3510 defender->client->ps.viewangles[PITCH] = -50;
3511 }
3512 defPitchAdd = defender->client->ps.viewangles[PITCH];
3513 attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH]*-1;
3514 }
3515 }
3516 }
3517 vec3_t attAngles, defAngles, defDir;
3518 VectorSubtract( defender->currentOrigin, attacker->currentOrigin, defDir );
3519 VectorCopy( attacker->client->ps.viewangles, attAngles );
3520 attAngles[YAW] = vectoyaw( defDir );
3521 SetClientViewAngle( attacker, attAngles );
3522 defAngles[PITCH] = attAngles[PITCH]*-1;
3523 defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
3524 defAngles[ROLL] = 0;
3525 SetClientViewAngle( defender, defAngles );
3526
3527 //MATCH POSITIONS
3528 vec3_t newOrg;
3529 /*
3530 idealDist -= fabs(defPitchAdd)/8.0f;
3531 */
3532 float scale = (attacker->s.modelScale[0]+attacker->s.modelScale[1])*0.5f;
3533 if ( scale && scale != 1.0f )
3534 {
3535 idealDist += 8*(scale-1.0f);
3536 }
3537 scale = (defender->s.modelScale[0]+defender->s.modelScale[1])*0.5f;
3538 if ( scale && scale != 1.0f )
3539 {
3540 idealDist += 8*(scale-1.0f);
3541 }
3542
3543 float diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
3544 //try to move attacker half the diff towards the defender
3545 VectorMA( attacker->currentOrigin, diff*0.5f, defDir, newOrg );
3546 trace_t trace;
3547 gi.trace( &trace, attacker->currentOrigin, attacker->mins, attacker->maxs, newOrg, attacker->s.number, attacker->clipmask, (EG2_Collision)0, 0 );
3548 if ( !trace.startsolid && !trace.allsolid )
3549 {
3550 G_SetOrigin( attacker, trace.endpos );
3551 gi.linkentity( attacker );
3552 }
3553 //now get the defender's dist and do it for him too
3554 vec3_t attDir;
3555 VectorSubtract( attacker->currentOrigin, defender->currentOrigin, attDir );
3556 diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
3557 //try to move defender all of the remaining diff towards the attacker
3558 VectorMA( defender->currentOrigin, diff, attDir, newOrg );
3559 gi.trace( &trace, defender->currentOrigin, defender->mins, defender->maxs, newOrg, defender->s.number, defender->clipmask, (EG2_Collision)0, 0 );
3560 if ( !trace.startsolid && !trace.allsolid )
3561 {
3562 G_SetOrigin( defender, trace.endpos );
3563 gi.linkentity( defender );
3564 }
3565
3566 //DONE!
3567
3568 return qtrue;
3569 }
3570
WP_SabersCheckLock(gentity_t * ent1,gentity_t * ent2)3571 qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
3572 {
3573 if ( ent1->client->playerTeam == ent2->client->playerTeam )
3574 {
3575 return qfalse;
3576 }
3577 if ( ent1->client->NPC_class == CLASS_SABER_DROID
3578 || ent2->client->NPC_class == CLASS_SABER_DROID )
3579 {//they don't have saberlock anims
3580 return qfalse;
3581 }
3582 if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
3583 ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
3584 {
3585 return qfalse;
3586 }
3587 if ( (ent1->client->ps.saber[0].saberFlags&SFL_NOT_LOCKABLE)
3588 || (ent2->client->ps.saber[0].saberFlags&SFL_NOT_LOCKABLE) )
3589 {//one of these sabers cannot lock (like a lance)
3590 return qfalse;
3591 }
3592 if ( ent1->client->ps.dualSabers
3593 && ent1->client->ps.saber[1].Active()
3594 && (ent1->client->ps.saber[1].saberFlags&SFL_NOT_LOCKABLE) )
3595 {//one of these sabers cannot lock (like a lance)
3596 return qfalse;
3597 }
3598 if ( ent2->client->ps.dualSabers
3599 && ent2->client->ps.saber[1].Active()
3600 && (ent2->client->ps.saber[1].saberFlags&SFL_NOT_LOCKABLE) )
3601 {//one of these sabers cannot lock (like a lance)
3602 return qfalse;
3603 }
3604 if ( ent1->painDebounceTime > level.time-1000 || ent2->painDebounceTime > level.time-1000 )
3605 {//can't saberlock if you're not ready
3606 return qfalse;
3607 }
3608 if ( fabs( ent1->currentOrigin[2]-ent2->currentOrigin[2]) > 18 )
3609 {
3610 return qfalse;
3611 }
3612 float dist = DistanceSquared(ent1->currentOrigin,ent2->currentOrigin);
3613 if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 )
3614 {//between 8 and 80 from each other//was 16 and 48
3615 return qfalse;
3616 }
3617 if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) )
3618 {
3619 return qfalse;
3620 }
3621 //Check for certain anims that *cannot* lock
3622 //FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up
3623 if ( ent1->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent1->client->ps.torsoAnimTimer > 300 )
3624 {//can't lock when saber is behind you
3625 return qfalse;
3626 }
3627 if ( ent2->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent2->client->ps.torsoAnimTimer > 300 )
3628 {//can't lock when saber is behind you
3629 return qfalse;
3630 }
3631 if ( PM_LockedAnim( ent1->client->ps.torsoAnim )
3632 || PM_LockedAnim( ent2->client->ps.torsoAnim ) )
3633 {//stuck doing something else
3634 return qfalse;
3635 }
3636 if ( PM_SaberLockBreakAnim( ent1->client->ps.torsoAnim )
3637 || PM_SaberLockBreakAnim( ent2->client->ps.torsoAnim ) )
3638 {//still finishing the last lock break!
3639 return qfalse;
3640 }
3641 //BR to TL lock
3642 if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3643 ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3644 ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3645 ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3646 ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3647 ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3648 ent1->client->ps.torsoAnim == BOTH_A7_BR_TL )
3649 {//ent1 is attacking in the opposite diagonal
3650 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
3651 }
3652 if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3653 ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3654 ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3655 ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3656 ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3657 ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3658 ent2->client->ps.torsoAnim == BOTH_A7_BR_TL )
3659 {//ent2 is attacking in the opposite diagonal
3660 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
3661 }
3662 //BL to TR lock
3663 if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3664 ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3665 ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3666 ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3667 ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3668 ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3669 ent1->client->ps.torsoAnim == BOTH_A7_BL_TR )
3670 {//ent1 is attacking in the opposite diagonal
3671 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
3672 }
3673 if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3674 ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3675 ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3676 ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3677 ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3678 ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3679 ent2->client->ps.torsoAnim == BOTH_A7_BL_TR )
3680 {//ent2 is attacking in the opposite diagonal
3681 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
3682 }
3683 //L to R lock
3684 if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
3685 ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
3686 ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
3687 ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
3688 ent1->client->ps.torsoAnim == BOTH_A5__L__R ||
3689 ent1->client->ps.torsoAnim == BOTH_A6__L__R ||
3690 ent1->client->ps.torsoAnim == BOTH_A7__L__R )
3691 {//ent1 is attacking l to r
3692 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3693 /*
3694 if ( ent2BlockingPlayer )
3695 {//player will block this anyway
3696 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3697 }
3698 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3699 ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3700 ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3701 ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3702 ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3703 ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3704 ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3705 ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
3706 ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
3707 {//ent2 is attacking or blocking on the r
3708 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3709 }
3710 if ( ent2Boss && !Q_irand( 0, 3 ) )
3711 {
3712 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
3713 }
3714 */
3715 }
3716 if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
3717 ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
3718 ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
3719 ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
3720 ent2->client->ps.torsoAnim == BOTH_A5__L__R ||
3721 ent2->client->ps.torsoAnim == BOTH_A6__L__R ||
3722 ent2->client->ps.torsoAnim == BOTH_A7__L__R )
3723 {//ent2 is attacking l to r
3724 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3725 /*
3726 if ( ent1BlockingPlayer )
3727 {//player will block this anyway
3728 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3729 }
3730 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3731 ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3732 ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3733 ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3734 ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3735 ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3736 ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3737 ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
3738 ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
3739 {//ent1 is attacking or blocking on the r
3740 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3741 }
3742 if ( ent1Boss && !Q_irand( 0, 3 ) )
3743 {
3744 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
3745 }
3746 */
3747 }
3748 //R to L lock
3749 if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
3750 ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
3751 ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
3752 ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
3753 ent1->client->ps.torsoAnim == BOTH_A5__R__L ||
3754 ent1->client->ps.torsoAnim == BOTH_A6__R__L ||
3755 ent1->client->ps.torsoAnim == BOTH_A7__R__L )
3756 {//ent1 is attacking r to l
3757 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3758 /*
3759 if ( ent2BlockingPlayer )
3760 {//player will block this anyway
3761 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3762 }
3763 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3764 ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3765 ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3766 ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3767 ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3768 ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3769 ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3770 ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
3771 ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
3772 {//ent2 is attacking or blocking on the l
3773 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3774 }
3775 if ( ent2Boss && !Q_irand( 0, 3 ) )
3776 {
3777 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
3778 }
3779 */
3780 }
3781 if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
3782 ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
3783 ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
3784 ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
3785 ent2->client->ps.torsoAnim == BOTH_A5__R__L ||
3786 ent2->client->ps.torsoAnim == BOTH_A6__R__L ||
3787 ent2->client->ps.torsoAnim == BOTH_A7__R__L )
3788 {//ent2 is attacking r to l
3789 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3790 /*
3791 if ( ent1BlockingPlayer )
3792 {//player will block this anyway
3793 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3794 }
3795 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3796 ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3797 ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3798 ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3799 ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3800 ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3801 ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3802 ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
3803 ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
3804 {//ent1 is attacking or blocking on the l
3805 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3806 }
3807 if ( ent1Boss && !Q_irand( 0, 3 ) )
3808 {
3809 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
3810 }
3811 */
3812 }
3813 //TR to BL lock
3814 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3815 ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3816 ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3817 ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3818 ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3819 ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3820 ent1->client->ps.torsoAnim == BOTH_A7_TR_BL )
3821 {//ent1 is attacking diagonally
3822 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3823 /*
3824 if ( ent2BlockingPlayer )
3825 {//player will block this anyway
3826 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3827 }
3828 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3829 ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3830 ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3831 ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3832 ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3833 ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3834 ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3835 ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
3836 {//ent2 is attacking in the opposite diagonal
3837 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3838 }
3839 if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3840 ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3841 ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3842 ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3843 ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3844 ent2->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3845 ent2->client->ps.torsoAnim == BOTH_A7_BR_TL ||
3846 ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
3847 {//ent2 is attacking in the opposite diagonal
3848 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
3849 }
3850 if ( ent2Boss && !Q_irand( 0, 3 ) )
3851 {
3852 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
3853 }
3854 */
3855 }
3856
3857 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3858 ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3859 ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3860 ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3861 ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3862 ent2->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3863 ent2->client->ps.torsoAnim == BOTH_A7_TR_BL )
3864 {//ent2 is attacking diagonally
3865 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3866 /*
3867 if ( ent1BlockingPlayer )
3868 {//player will block this anyway
3869 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3870 }
3871 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
3872 ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
3873 ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
3874 ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
3875 ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
3876 ent1->client->ps.torsoAnim == BOTH_A6_TR_BL ||
3877 ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ||
3878 ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
3879 {//ent1 is attacking in the opposite diagonal
3880 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3881 }
3882 if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
3883 ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
3884 ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
3885 ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
3886 ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
3887 ent1->client->ps.torsoAnim == BOTH_A6_BR_TL ||
3888 ent1->client->ps.torsoAnim == BOTH_A7_BR_TL ||
3889 ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
3890 {//ent1 is attacking in the opposite diagonal
3891 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
3892 }
3893 if ( ent1Boss && !Q_irand( 0, 3 ) )
3894 {
3895 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
3896 }
3897 */
3898 }
3899
3900 //TL to BR lock
3901 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3902 ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3903 ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3904 ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3905 ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3906 ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3907 ent1->client->ps.torsoAnim == BOTH_A7_TL_BR )
3908 {//ent1 is attacking diagonally
3909 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3910 /*
3911 if ( ent2BlockingPlayer )
3912 {//player will block this anyway
3913 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3914 }
3915 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3916 ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3917 ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3918 ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3919 ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3920 ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3921 ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3922 ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
3923 {//ent2 is attacking in the opposite diagonal
3924 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3925 }
3926 if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3927 ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3928 ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3929 ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3930 ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3931 ent2->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3932 ent2->client->ps.torsoAnim == BOTH_A7_BL_TR ||
3933 ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
3934 {//ent2 is attacking in the opposite diagonal
3935 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
3936 }
3937 if ( ent2Boss && !Q_irand( 0, 3 ) )
3938 {
3939 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
3940 }
3941 */
3942 }
3943
3944 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3945 ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3946 ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3947 ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3948 ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3949 ent2->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3950 ent2->client->ps.torsoAnim == BOTH_A7_TL_BR )
3951 {//ent2 is attacking diagonally
3952 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3953 /*
3954 if ( ent1BlockingPlayer )
3955 {//player will block this anyway
3956 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3957 }
3958 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
3959 ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
3960 ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
3961 ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
3962 ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
3963 ent1->client->ps.torsoAnim == BOTH_A6_TL_BR ||
3964 ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ||
3965 ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
3966 {//ent1 is attacking in the opposite diagonal
3967 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3968 }
3969 if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
3970 ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
3971 ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
3972 ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
3973 ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
3974 ent1->client->ps.torsoAnim == BOTH_A6_BL_TR ||
3975 ent1->client->ps.torsoAnim == BOTH_A7_BL_TR ||
3976 ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
3977 {//ent1 is attacking in the opposite diagonal
3978 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
3979 }
3980 if ( ent1Boss && !Q_irand( 0, 3 ) )
3981 {
3982 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
3983 }
3984 */
3985 }
3986 //T to B lock
3987 if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
3988 ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
3989 ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
3990 ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
3991 ent1->client->ps.torsoAnim == BOTH_A5_T__B_ ||
3992 ent1->client->ps.torsoAnim == BOTH_A6_T__B_ ||
3993 ent1->client->ps.torsoAnim == BOTH_A7_T__B_ )
3994 {//ent1 is attacking top-down
3995 /*
3996 if ( ent2->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
3997 ent2->client->ps.torsoAnim == BOTH_K1_S1_T_ )
3998 */
3999 {//ent2 is blocking at top
4000 return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
4001 }
4002 }
4003
4004 if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
4005 ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
4006 ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
4007 ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
4008 ent2->client->ps.torsoAnim == BOTH_A5_T__B_ ||
4009 ent2->client->ps.torsoAnim == BOTH_A6_T__B_ ||
4010 ent2->client->ps.torsoAnim == BOTH_A7_T__B_ )
4011 {//ent2 is attacking top-down
4012 /*
4013 if ( ent1->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
4014 ent1->client->ps.torsoAnim == BOTH_K1_S1_T_ )
4015 */
4016 {//ent1 is blocking at top
4017 return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
4018 }
4019 }
4020 /*
4021 if ( !Q_irand( 0, 10 ) )
4022 {
4023 return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
4024 }
4025 */
4026 return qfalse;
4027 }
4028
WP_SaberParry(gentity_t * victim,gentity_t * attacker,int saberNum,int bladeNum)4029 qboolean WP_SaberParry( gentity_t *victim, gentity_t *attacker, int saberNum, int bladeNum )
4030 {
4031 if ( !victim || !victim->client || !attacker )
4032 {
4033 return qfalse;
4034 }
4035 if ( Rosh_BeingHealed( victim ) )
4036 {
4037 return qfalse;
4038 }
4039 if ( G_InCinematicSaberAnim( victim ) )
4040 {
4041 return qfalse;
4042 }
4043 if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim )
4044 || PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) )
4045 {
4046 return qfalse;
4047 }
4048 if ( victim->s.number || g_saberAutoBlocking->integer || victim->client->ps.saberBlockingTime > level.time )
4049 {//either an NPC or a player who is blocking
4050 if ( !PM_SaberInTransitionAny( victim->client->ps.saberMove )
4051 && !PM_SaberInBounce( victim->client->ps.saberMove )
4052 && !PM_SaberInKnockaway( victim->client->ps.saberMove ) )
4053 {//I'm not attacking, in transition or in a bounce or knockaway, so play a parry
4054 WP_SaberBlockNonRandom( victim, saberHitLocation, qfalse );
4055 }
4056 victim->client->ps.saberEventFlags |= SEF_PARRIED;
4057
4058 //since it was parried, take away any damage done
4059 //FIXME: what if the damage was done before the parry?
4060 WP_SaberClearDamageForEntNum( attacker, victim->s.number, saberNum, bladeNum );
4061
4062 //tell the victim to get mad at me
4063 if ( victim->enemy != attacker && victim->client->playerTeam != attacker->client->playerTeam )
4064 {//they're not mad at me and they're not on my team
4065 G_ClearEnemy( victim );
4066 G_SetEnemy( victim, attacker );
4067 }
4068 return qtrue;
4069 }
4070 return qfalse;
4071 }
4072
WP_BrokenParryKnockDown(gentity_t * victim)4073 qboolean WP_BrokenParryKnockDown( gentity_t *victim )
4074 {
4075 if ( !victim || !victim->client )
4076 {
4077 return qfalse;
4078 }
4079 if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim )
4080 || PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) )
4081 {
4082 return qfalse;
4083 }
4084 if ( victim->client->ps.saberMove == LS_PARRY_UP
4085 || victim->client->ps.saberMove == LS_PARRY_UR
4086 || victim->client->ps.saberMove == LS_PARRY_UL
4087 || victim->client->ps.saberMove == LS_H1_BR
4088 || victim->client->ps.saberMove == LS_H1_B_
4089 || victim->client->ps.saberMove == LS_H1_BL )
4090 {//knock their asses down!
4091 int knockAnim = BOTH_KNOCKDOWN1;
4092 if ( PM_CrouchAnim( victim->client->ps.legsAnim ) )
4093 {
4094 knockAnim = BOTH_KNOCKDOWN4;
4095 }
4096 NPC_SetAnim( victim, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4097 G_AddEvent( victim, EV_PAIN, victim->health );
4098 return qtrue;
4099 }
4100 return qfalse;
4101 }
4102
G_TryingKataAttack(gentity_t * self,usercmd_t * cmd)4103 qboolean G_TryingKataAttack( gentity_t *self, usercmd_t *cmd )
4104 {
4105 if ( g_saberNewControlScheme->integer )
4106 {//use the new control scheme: force focus button
4107 if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4108 {
4109 return qtrue;
4110 }
4111 else
4112 {
4113 return qfalse;
4114 }
4115 }
4116 else //if ( self && self->client )
4117 {//use the old control scheme
4118 if ( (cmd->buttons&BUTTON_ALT_ATTACK) )
4119 {//pressing alt-attack
4120 //if ( !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
4121 {//haven't been holding alt-attack
4122 if ( (cmd->buttons&BUTTON_ATTACK) )
4123 {//pressing attack
4124 return qtrue;
4125 }
4126 }
4127 }
4128 }
4129 return qfalse;
4130 }
4131
G_TryingPullAttack(gentity_t * self,usercmd_t * cmd,qboolean amPulling)4132 qboolean G_TryingPullAttack( gentity_t *self, usercmd_t *cmd, qboolean amPulling )
4133 {
4134 if ( g_saberNewControlScheme->integer )
4135 {//use the new control scheme: force focus button
4136 if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4137 {
4138 if ( self && self->client )
4139 {
4140 if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 )
4141 {//force pull 3
4142 if ( amPulling
4143 || (self->client->ps.forcePowersActive&(1<<FP_PULL))
4144 || self->client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling
4145 {//pulling
4146 return qtrue;
4147 }
4148 }
4149 }
4150 }
4151 else
4152 {
4153 return qfalse;
4154 }
4155 }
4156 else
4157 {//use the old control scheme
4158 if ( (cmd->buttons&BUTTON_ATTACK) )
4159 {//pressing attack
4160 if ( self && self->client )
4161 {
4162 if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 )
4163 {//force pull 3
4164 if ( amPulling
4165 || (self->client->ps.forcePowersActive&(1<<FP_PULL))
4166 || self->client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling
4167 {//pulling
4168 return qtrue;
4169 }
4170 }
4171 }
4172 }
4173 }
4174 return qfalse;
4175 }
4176
G_TryingCartwheel(gentity_t * self,usercmd_t * cmd)4177 qboolean G_TryingCartwheel( gentity_t *self, usercmd_t *cmd )
4178 {
4179 if ( g_saberNewControlScheme->integer )
4180 {//use the new control scheme: force focus button
4181 if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4182 {
4183 return qtrue;
4184 }
4185 else
4186 {
4187 return qfalse;
4188 }
4189 }
4190 else
4191 {//use the old control scheme
4192 if ( (cmd->buttons&BUTTON_ATTACK) )
4193 {//pressing attack
4194 if ( cmd->rightmove )
4195 {
4196 if ( self && self->client )
4197 {
4198 if ( cmd->upmove>0 //)
4199 && self->client->ps.groundEntityNum != ENTITYNUM_NONE )
4200 {//on ground, pressing jump
4201 return qtrue;
4202 }
4203 else
4204 {//just jumped?
4205 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
4206 && level.time - self->client->ps.lastOnGround <= 50//250
4207 && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
4208 {//just jumped this or last frame
4209 return qtrue;
4210 }
4211 }
4212 }
4213 }
4214 }
4215 }
4216 return qfalse;
4217 }
4218
G_TryingSpecial(gentity_t * self,usercmd_t * cmd)4219 qboolean G_TryingSpecial( gentity_t *self, usercmd_t *cmd )
4220 {
4221 if ( g_saberNewControlScheme->integer )
4222 {//use the new control scheme: force focus button
4223 if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4224 {
4225 return qtrue;
4226 }
4227 else
4228 {
4229 return qfalse;
4230 }
4231 }
4232 else
4233 {//use the old control scheme
4234 return qfalse;
4235 }
4236 }
4237
G_TryingJumpAttack(gentity_t * self,usercmd_t * cmd)4238 qboolean G_TryingJumpAttack( gentity_t *self, usercmd_t *cmd )
4239 {
4240 if ( g_saberNewControlScheme->integer )
4241 {//use the new control scheme: force focus button
4242 if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4243 {
4244 return qtrue;
4245 }
4246 else
4247 {
4248 return qfalse;
4249 }
4250 }
4251 else
4252 {//use the old control scheme
4253 if ( (cmd->buttons&BUTTON_ATTACK) )
4254 {//pressing attack
4255 if ( cmd->upmove>0 )
4256 {//pressing jump
4257 return qtrue;
4258 }
4259 else if ( self && self->client )
4260 {//just jumped?
4261 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
4262 && level.time - self->client->ps.lastOnGround <= 250
4263 && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
4264 {//jumped within the last quarter second
4265 return qtrue;
4266 }
4267 }
4268 }
4269 }
4270 return qfalse;
4271 }
4272
G_TryingJumpForwardAttack(gentity_t * self,usercmd_t * cmd)4273 qboolean G_TryingJumpForwardAttack( gentity_t *self, usercmd_t *cmd )
4274 {
4275 if ( g_saberNewControlScheme->integer )
4276 {//use the new control scheme: force focus button
4277 if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4278 {
4279 return qtrue;
4280 }
4281 else
4282 {
4283 return qfalse;
4284 }
4285 }
4286 else
4287 {//use the old control scheme
4288 if ( (cmd->buttons&BUTTON_ATTACK) )
4289 {//pressing attack
4290 if ( cmd->forwardmove > 0 )
4291 {//moving forward
4292 if ( self && self->client )
4293 {
4294 if ( cmd->upmove>0
4295 && self->client->ps.groundEntityNum != ENTITYNUM_NONE )
4296 {//pressing jump
4297 return qtrue;
4298 }
4299 else
4300 {//no slop on forward jumps - must be precise!
4301 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
4302 && level.time - self->client->ps.lastOnGround <= 50
4303 && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping
4304 {//just jumped this or last frame
4305 return qtrue;
4306 }
4307 }
4308 }
4309 }
4310 }
4311 }
4312 return qfalse;
4313 }
4314
G_TryingLungeAttack(gentity_t * self,usercmd_t * cmd)4315 qboolean G_TryingLungeAttack( gentity_t *self, usercmd_t *cmd )
4316 {
4317 if ( g_saberNewControlScheme->integer )
4318 {//use the new control scheme: force focus button
4319 if ( (cmd->buttons&BUTTON_FORCE_FOCUS) )
4320 {
4321 return qtrue;
4322 }
4323 else
4324 {
4325 return qfalse;
4326 }
4327 }
4328 else
4329 {//use the old control scheme
4330 if ( (cmd->buttons&BUTTON_ATTACK) )
4331 {//pressing attack
4332 if ( cmd->upmove<0 )
4333 {//pressing crouch
4334 return qtrue;
4335 }
4336 else if ( self && self->client )
4337 {//just unducked?
4338 if ( (self->client->ps.pm_flags&PMF_DUCKED) )
4339 {//just unducking
4340 return qtrue;
4341 }
4342 }
4343 }
4344 }
4345 return qfalse;
4346 }
4347
4348
4349 //FIXME: for these below funcs, maybe in the old control scheme some moves should still cost power... if so, pass in the saberMove and use a switch statement
G_EnoughPowerForSpecialMove(int forcePower,int cost,qboolean kataMove)4350 qboolean G_EnoughPowerForSpecialMove( int forcePower, int cost, qboolean kataMove )
4351 {
4352 if ( g_saberNewControlScheme->integer || kataMove )
4353 {//special moves cost power
4354 if ( forcePower >= cost )
4355 {
4356 return qtrue;
4357 }
4358 else
4359 {
4360 cg.forceHUDTotalFlashTime = level.time + 1000;
4361 return qfalse;
4362 }
4363 }
4364 else
4365 {//old control scheme: uses no power, so just do it
4366 return qtrue;
4367 }
4368 }
4369
G_DrainPowerForSpecialMove(gentity_t * self,forcePowers_t fp,int cost,qboolean kataMove)4370 void G_DrainPowerForSpecialMove( gentity_t *self, forcePowers_t fp, int cost, qboolean kataMove )
4371 {
4372 if ( !self || !self->client || self->s.number >= MAX_CLIENTS )
4373 {
4374 return;
4375 }
4376 if ( g_saberNewControlScheme->integer || kataMove )
4377 {//special moves cost power
4378 WP_ForcePowerDrain( self, fp, cost );//drain the required force power
4379 }
4380 else
4381 {//old control scheme: uses no power, so just do it
4382 }
4383 }
4384
G_CostForSpecialMove(int cost,qboolean kataMove)4385 int G_CostForSpecialMove( int cost, qboolean kataMove )
4386 {
4387 if ( g_saberNewControlScheme->integer || kataMove )
4388 {//special moves cost power
4389 return cost;
4390 }
4391 else
4392 {//old control scheme: uses no power, so just do it
4393 return 0;
4394 }
4395 }
4396
4397 extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker );
WP_SaberRadiusDamage(gentity_t * ent,vec3_t point,float radius,int damage,float knockBack)4398 void WP_SaberRadiusDamage( gentity_t *ent, vec3_t point, float radius, int damage, float knockBack )
4399 {
4400 if ( !ent || !ent->client )
4401 {
4402 return;
4403 }
4404 else if ( radius <= 0.0f || (damage <= 0 && knockBack <= 0) )
4405 {
4406 return;
4407 }
4408 else
4409 {
4410 vec3_t mins, maxs, entDir;
4411 gentity_t *radiusEnts[128];
4412 int numEnts, i;
4413 float dist;
4414
4415 //Setup the bbox to search in
4416 for ( i = 0; i < 3; i++ )
4417 {
4418 mins[i] = point[i] - radius;
4419 maxs[i] = point[i] + radius;
4420 }
4421
4422 //Get the number of entities in a given space
4423 numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
4424
4425 for ( i = 0; i < numEnts; i++ )
4426 {
4427 if ( !radiusEnts[i]->inuse )
4428 {
4429 continue;
4430 }
4431
4432 if ( radiusEnts[i] == ent )
4433 {//Skip myself
4434 continue;
4435 }
4436
4437 if ( radiusEnts[i]->client == NULL )
4438 {//must be a client
4439 if ( G_EntIsBreakable( radiusEnts[i]->s.number, ent ) )
4440 {//damage breakables within range, but not as much
4441 G_Damage( radiusEnts[i], ent, ent, vec3_origin, radiusEnts[i]->currentOrigin, 10, 0, MOD_EXPLOSIVE_SPLASH );
4442 }
4443 continue;
4444 }
4445
4446 if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR)
4447 || (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) )
4448 {//can't be one being held
4449 continue;
4450 }
4451
4452 VectorSubtract( radiusEnts[i]->currentOrigin, point, entDir );
4453 dist = VectorNormalize( entDir );
4454 if ( dist <= radius )
4455 {//in range
4456 if ( damage > 0 )
4457 {//do damage
4458 int points = ceil((float)damage*dist/radius);
4459 G_Damage( radiusEnts[i], ent, ent, vec3_origin, radiusEnts[i]->currentOrigin, points, DAMAGE_NO_KNOCKBACK, MOD_EXPLOSIVE_SPLASH );
4460 }
4461 if ( knockBack > 0 )
4462 {//do knockback
4463 if ( radiusEnts[i]->client
4464 && radiusEnts[i]->client->NPC_class != CLASS_RANCOR
4465 && radiusEnts[i]->client->NPC_class != CLASS_ATST
4466 && !(radiusEnts[i]->flags&FL_NO_KNOCKBACK) )//don't throw them back
4467 {
4468 float knockbackStr = knockBack*dist/radius;
4469 entDir[2] += 0.1f;
4470 VectorNormalize( entDir );
4471 G_Throw( radiusEnts[i], entDir, knockbackStr );
4472 if ( radiusEnts[i]->health > 0 )
4473 {//still alive
4474 if ( knockbackStr > 50 )
4475 {//close enough and knockback high enough to possibly knock down
4476 if ( dist < (radius*0.5f)
4477 || radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE )
4478 {//within range of my fist or within ground-shaking range and not in the air
4479 G_Knockdown( radiusEnts[i], ent, entDir, 500, qtrue );
4480 }
4481 }
4482 }
4483 }
4484 }
4485 }
4486 }
4487 }
4488 }
4489 /*
4490 ---------------------------------------------------------
4491 void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum )
4492
4493 Constantly trace from the old blade pos to new, down the saber beam and do damage
4494
4495 FIXME: if the dot product of the old muzzle dir and the new muzzle dir is < 0.75, subdivide it and do multiple traces so we don't flatten out the arc!
4496 ---------------------------------------------------------
4497 */
4498 #define MAX_SABER_SWING_INC 0.33f
WP_SaberDamageTrace(gentity_t * ent,int saberNum,int bladeNum)4499 void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum )
4500 {
4501 vec3_t mp1, mp2, md1, md2, baseOld, baseNew, baseDiff, endOld, endNew, bladePointOld, bladePointNew;
4502 float tipDmgMod = 1.0f;
4503 float baseDamage;
4504 int baseDFlags = 0;
4505 qboolean hit_wall = qfalse;
4506 qboolean brokenParry = qfalse;
4507
4508 for ( int ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
4509 {
4510 victimEntityNum[ven] = ENTITYNUM_NONE;
4511 }
4512 memset( totalDmg, 0, sizeof( totalDmg) );
4513 memset( dmgDir, 0, sizeof( dmgDir ) );
4514 memset( dmgNormal, 0, sizeof( dmgNormal ) );
4515 memset( dmgSpot, 0, sizeof( dmgSpot ) );
4516 memset( dmgFraction, 0, sizeof( dmgFraction ) );
4517 memset( hitLoc, HL_NONE, sizeof( hitLoc ) );
4518 memset( hitDismemberLoc, HL_NONE, sizeof( hitDismemberLoc ) );
4519 memset( hitDismember, qfalse, sizeof( hitDismember ) );
4520 numVictims = 0;
4521 VectorClear(saberHitLocation);
4522 VectorClear(saberHitNormal);
4523 saberHitFraction = 1.0; // Closest saber hit. The saber can do no damage past this point.
4524 saberHitEntity = ENTITYNUM_NONE;
4525 sabersCrossed = -1;
4526
4527 if ( !ent->client )
4528 {
4529 return;
4530 }
4531
4532 if ( !ent->s.number )
4533 {//player never uses these
4534 ent->client->ps.saberEventFlags &= ~SEF_EVENTS;
4535 }
4536
4537 if ( ent->client->ps.saber[saberNum].blade[bladeNum].length <= 1 )//cen get down to 1 when in a wall
4538 {//saber is not on
4539 return;
4540 }
4541
4542 if ( VectorCompare( ent->client->renderInfo.muzzlePointOld, vec3_origin ) || VectorCompare( ent->client->renderInfo.muzzleDirOld, vec3_origin ) )
4543 {
4544 //just started up the saber?
4545 return;
4546 }
4547
4548 int saberContents = 0;
4549 if ( !(ent->client->ps.saber[saberNum].saberFlags&SFL_ON_IN_WATER) )
4550 {//saber can't stay on underwater
4551 saberContents = gi.pointcontents( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberEntityNum );
4552 }
4553 if ( (saberContents&CONTENTS_WATER)||
4554 (saberContents&CONTENTS_SLIME)||
4555 (saberContents&CONTENTS_LAVA) )
4556 {//um... turn off? Or just set length to 1?
4557 //FIXME: short-out effect/sound?
4558 ent->client->ps.saber[saberNum].blade[bladeNum].active = qfalse;
4559 return;
4560 }
4561 else if (!g_saberNoEffects && gi.WE_IsOutside(ent->client->renderInfo.muzzlePoint))
4562 {
4563 float chanceOfFizz = gi.WE_GetChanceOfSaberFizz();
4564 if (chanceOfFizz>0 && Q_flrand(0.0f, 1.0f)<chanceOfFizz)
4565 {
4566 vec3_t end; /*normal = {0,0,1};//FIXME: opposite of rain angles?*/
4567 VectorMA( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].length*Q_flrand(0, 1), ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, end );
4568 G_PlayEffect( "saber/fizz", end );
4569 }
4570 }
4571
4572 //FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because
4573 // we animate so much faster that the arc is pretty much flat...
4574
4575 int entPowerLevel = 0;
4576 if ( ent->client->NPC_class == CLASS_SABER_DROID )
4577 {
4578 entPowerLevel = SaberDroid_PowerLevelForSaberAnim( ent );
4579 }
4580 else if ( !ent->s.number && (ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
4581 {
4582 entPowerLevel = FORCE_LEVEL_3;
4583 }
4584 else
4585 {
4586 entPowerLevel = PM_PowerLevelForSaberAnim( &ent->client->ps, saberNum );
4587 }
4588
4589 if ( entPowerLevel )
4590 {
4591 if ( ent->client->ps.forceRageRecoveryTime > level.time )
4592 {
4593 entPowerLevel = FORCE_LEVEL_1;
4594 }
4595 else if ( ent->client->ps.forcePowersActive & (1 << FP_RAGE) )
4596 {
4597 entPowerLevel += ent->client->ps.forcePowerLevel[FP_RAGE];
4598 }
4599 }
4600
4601 if ( ent->client->ps.saberInFlight )
4602 {//flying sabers are much more deadly
4603 //unless you're dead
4604 if ( ent->health <= 0 && g_saberRealisticCombat->integer < 2 )
4605 {//so enemies don't keep trying to block it
4606 //FIXME: still do damage, just not to humanoid clients who should try to avoid it
4607 //baseDamage = 0.0f;
4608 return;
4609 }
4610 //or unless returning
4611 else if ( ent->client->ps.saberEntityState == SES_RETURNING
4612 && !(ent->client->ps.saber[0].saberFlags&SFL_RETURN_DAMAGE) )//type != SABER_STAR )
4613 {//special case, since we're returning, chances are if we hit something
4614 //it's going to be butt-first. So do less damage.
4615 baseDamage = 0.1f;
4616 }
4617 else
4618 {
4619 if ( !ent->s.number )
4620 {//cheat for player
4621 baseDamage = 10.0f;
4622 }
4623 else
4624 {
4625 baseDamage = 2.5f;
4626 }
4627 }
4628 //Use old to current since can't predict it
4629 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 );
4630 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 );
4631 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 );
4632 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 );
4633 }
4634 else
4635 {
4636 if ( ent->client->ps.torsoAnim == BOTH_A7_HILT )
4637 {//no effects, no damage
4638 return;
4639 }
4640 else if ( G_InCinematicSaberAnim( ent ) )
4641 {
4642 baseDFlags = DAMAGE_NO_KILL;
4643 baseDamage = 0.1f;
4644 }
4645 else if ( ent->client->ps.saberMove == LS_READY
4646 && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
4647 {//just do effects
4648 if ( g_saberRealisticCombat->integer < 2 )
4649 {//don't kill with this hit
4650 baseDFlags = DAMAGE_NO_KILL;
4651 }
4652 if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4653 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4654 || ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4655 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4656 )
4657 {//do nothing at all when idle
4658 return;
4659 }
4660 baseDamage = 0;
4661 }
4662 else if ( ent->client->ps.saberLockTime > level.time )
4663 {//just do effects
4664 if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4665 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4666 || ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4667 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4668 )
4669 {//do nothing at all when idle
4670 return;
4671 }
4672 baseDamage = 0;
4673 }
4674 else if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4675 && ent->client->ps.saber[saberNum].damageScale <= 0.0f
4676 && ent->client->ps.saber[saberNum].knockbackScale <= 0.0f )
4677 {//this blade does no damage and no knockback (only for blocking?)
4678 baseDamage = 0;
4679 }
4680 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4681 && ent->client->ps.saber[saberNum].damageScale2 <= 0.0f
4682 && ent->client->ps.saber[saberNum].knockbackScale2 <= 0.0f )
4683 {//this blade does no damage and no knockback (only for blocking?)
4684 baseDamage = 0;
4685 }
4686 else if ( ent->client->ps.saberBlocked > BLOCKED_NONE
4687 || ( !PM_SaberInAttack( ent->client->ps.saberMove )
4688 && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
4689 && !PM_SaberInTransitionAny( ent->client->ps.saberMove )
4690 )
4691 )
4692 {//don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks
4693 if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4694 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4695 || ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4696 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4697 )
4698 {//do nothing at all when idle
4699 return;
4700 }
4701 baseDamage = 0;
4702 }
4703 else
4704 {//okay, in a saberMove that does damage
4705 //make sure we're in the right anim
4706 if ( !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
4707 && !PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) )
4708 {//forced into some other animation somehow, like a pain or death?
4709 if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4710 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT) )
4711 || ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
4712 && (ent->client->ps.saber[saberNum].saberFlags2&SFL2_NO_IDLE_EFFECT2) )
4713 )
4714 {//do nothing at all when idle
4715 return;
4716 }
4717 baseDamage = 0;
4718 }
4719 else if ( ent->client->ps.weaponstate == WEAPON_FIRING && ent->client->ps.saberBlocked == BLOCKED_NONE &&
4720 ( PM_SaberInAttack(ent->client->ps.saberMove) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SpinningSaberAnim(ent->client->ps.torsoAnim) || entPowerLevel > FORCE_LEVEL_2 || (WP_SaberBladeDoTransitionDamage( &ent->client->ps.saber[saberNum], bladeNum )&&PM_SaberInTransitionAny(ent->client->ps.saberMove)) ) )// || ent->client->ps.saberAnimLevel == SS_STAFF ) )
4721 {//normal attack swing swinging/spinning (or if using strong set), do normal damage //FIXME: or if using staff?
4722 //FIXME: more damage for higher attack power levels?
4723 // More damage based on length/color of saber?
4724 //FIXME: Desann does double damage?
4725 if ( g_saberRealisticCombat->integer )
4726 {
4727 switch ( entPowerLevel )
4728 {
4729 default:
4730 case FORCE_LEVEL_3:
4731 baseDamage = 10.0f;
4732 break;
4733 case FORCE_LEVEL_2:
4734 baseDamage = 5.0f;
4735 break;
4736 case FORCE_LEVEL_0:
4737 case FORCE_LEVEL_1:
4738 baseDamage = 2.5f;
4739 break;
4740 }
4741 }
4742 else
4743 {
4744 if ( g_spskill->integer > 0
4745 && ent->s.number < MAX_CLIENTS
4746 && ( ent->client->ps.torsoAnim == BOTH_ROLL_STAB
4747 || ent->client->ps.torsoAnim == BOTH_SPINATTACK6
4748 || ent->client->ps.torsoAnim == BOTH_SPINATTACK7
4749 || ent->client->ps.torsoAnim == BOTH_LUNGE2_B__T_ ) )
4750 {//*sigh*, these anim do less damage since they're so easy to do
4751 baseDamage = 2.5f;
4752 }
4753 else
4754 {
4755 baseDamage = 2.5f * (float)entPowerLevel;
4756 }
4757 }
4758 }
4759 else
4760 {//saber is transitioning, defending or idle, don't do as much damage
4761 //FIXME: strong attacks and returns should do damage and be unblockable
4762 if ( g_timescale->value < 1.0 )
4763 {//in slow mo or force speed, we need to do damage during the transitions
4764 if ( g_saberRealisticCombat->integer )
4765 {
4766 switch ( entPowerLevel )
4767 {
4768 case FORCE_LEVEL_3:
4769 baseDamage = 10.0f;
4770 break;
4771 case FORCE_LEVEL_2:
4772 baseDamage = 5.0f;
4773 break;
4774 default:
4775 case FORCE_LEVEL_1:
4776 baseDamage = 2.5f;
4777 break;
4778 }
4779 }
4780 else
4781 {
4782 baseDamage = 2.5f * (float)entPowerLevel;
4783 }
4784 }
4785 else// if ( !ent->s.number )
4786 {//I have to do *some* damage in transitions or else you feel like a total gimp
4787 baseDamage = 0.1f;
4788 }
4789 /*
4790 else
4791 {
4792 baseDamage = 0;//was 1.0f;//was 0.25
4793 }
4794 */
4795 }
4796 }
4797
4798 //Use current to next since can predict it
4799 //FIXME: if they're closer than the saber blade start, we don't want the
4800 // arm to pass through them without any damage... so check the radius
4801 // and push them away (do pain & knockback)
4802 //FIXME: if going into/coming from a parry/reflection or going into a deflection, don't use old mp & dir? Otherwise, deflections will cut through?
4803 //VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 );
4804 //VectorCopy( ent->client->renderInfo.muzzleDir, md1 );
4805 //VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 );
4806 //VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 );
4807 //prediction was causing gaps in swing (G2 problem) so *don't* predict
4808 if ( ent->client->ps.saberDamageDebounceTime > level.time )
4809 {//really only used when a saber attack start anim starts, not actually for stopping damage
4810 //we just want to not use the old position to trace the attack from...
4811 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld );
4812 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld );
4813 }
4814 //do the damage trace from the last position...
4815 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 );
4816 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 );
4817 //...to the current one.
4818 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 );
4819 VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 );
4820
4821 //NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow...
4822 //see if anyone is so close that they're within the dist from my origin to the start of the saber
4823 if ( ent->health > 0 && !ent->client->ps.saberLockTime && saberNum == 0 && bladeNum == 0
4824 && !G_InCinematicSaberAnim( ent ) )
4825 {//only do once - for first blade
4826 trace_t trace;
4827 gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, mp1, ent->s.number, (MASK_SHOT&~(CONTENTS_CORPSE|CONTENTS_ITEM)), (EG2_Collision)0, 0 );
4828 if ( trace.entityNum < ENTITYNUM_WORLD && (trace.entityNum > 0||ent->client->NPC_class == CLASS_DESANN) )//NPCs don't push player away, unless it's Desann
4829 {//a valid ent
4830 gentity_t *traceEnt = &g_entities[trace.entityNum];
4831 if ( traceEnt
4832 && traceEnt->client
4833 && traceEnt->client->NPC_class != CLASS_RANCOR
4834 && traceEnt->client->NPC_class != CLASS_ATST
4835 && traceEnt->client->NPC_class != CLASS_WAMPA
4836 && traceEnt->client->NPC_class != CLASS_SAND_CREATURE
4837 && traceEnt->health > 0
4838 && traceEnt->client->playerTeam != ent->client->playerTeam
4839 && !PM_SuperBreakLoseAnim( traceEnt->client->ps.legsAnim )
4840 && !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim )
4841 && !PM_SuperBreakWinAnim( traceEnt->client->ps.legsAnim )
4842 && !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim )
4843 && !PM_InKnockDown( &traceEnt->client->ps )
4844 && !PM_LockedAnim( traceEnt->client->ps.legsAnim )
4845 && !PM_LockedAnim( traceEnt->client->ps.torsoAnim )
4846 && !G_InCinematicSaberAnim( traceEnt ))
4847 {//enemy client, push them away
4848 if ( !traceEnt->client->ps.saberLockTime
4849 && !traceEnt->message
4850 && !(traceEnt->flags&FL_NO_KNOCKBACK)
4851 && (!traceEnt->NPC||traceEnt->NPC->jumpState!=JS_JUMPING) )
4852 {//don't push people in saberlock or with security keys or who are in BS_JUMP
4853 vec3_t hitDir;
4854 VectorSubtract( trace.endpos, ent->currentOrigin, hitDir );
4855 float totalDist = Distance( mp1, ent->currentOrigin );
4856 float knockback = (totalDist-VectorNormalize( hitDir ))/totalDist * 200.0f;
4857 hitDir[2] = 0;
4858 //FIXME: do we need to call G_Throw? Seems unfair to put actual knockback on them, stops the attack
4859 //G_Throw( traceEnt, hitDir, knockback );
4860 VectorMA( traceEnt->client->ps.velocity, knockback, hitDir, traceEnt->client->ps.velocity );
4861 traceEnt->client->ps.pm_time = 200;
4862 traceEnt->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
4863 #ifndef FINAL_BUILD
4864 if ( d_saberCombat->integer )
4865 {
4866 gi.Printf( "%s pushing away %s at %s\n", ent->NPC_type, traceEnt->NPC_type, vtos( traceEnt->client->ps.velocity ) );
4867 }
4868 #endif
4869 }
4870 }
4871 }
4872 }
4873 }
4874
4875 //the thicker the blade, the more damage... the thinner, the less damage
4876 baseDamage *= ent->client->ps.saber[saberNum].blade[bladeNum].radius/SABER_RADIUS_STANDARD;
4877
4878 if ( g_saberRealisticCombat->integer > 1 )
4879 {//always do damage, and lots of it
4880 if ( g_saberRealisticCombat->integer > 2 )
4881 {//always do damage, and lots of it
4882 baseDamage = 25.0f;
4883 }
4884 else if ( baseDamage > 0.1f )
4885 {//only do super damage if we would have done damage according to normal rules
4886 baseDamage = 25.0f;
4887 }
4888 }
4889 else if ( ((!ent->s.number&&ent->client->ps.forcePowersActive&(1<<FP_SPEED))||ent->client->ps.forcePowersActive&(1<<FP_RAGE))
4890 && g_timescale->value < 1.0f )
4891 {
4892 baseDamage *= (1.0f-g_timescale->value);
4893 }
4894 if ( baseDamage > 0.1f )
4895 {
4896 if ( (ent->client->ps.forcePowersActive&(1<<FP_RAGE)) )
4897 {//add some damage if raged
4898 baseDamage += ent->client->ps.forcePowerLevel[FP_RAGE] * 5.0f;
4899 }
4900 else if ( ent->client->ps.forceRageRecoveryTime )
4901 {//halve it if recovering
4902 baseDamage *= 0.5f;
4903 }
4904 }
4905 // Get the old state of the blade
4906 VectorCopy( mp1, baseOld );
4907 VectorMA( baseOld, ent->client->ps.saber[saberNum].blade[bladeNum].length, md1, endOld );
4908 // Get the future state of the blade
4909 VectorCopy( mp2, baseNew );
4910 VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew );
4911
4912 sabersCrossed = -1;
4913 if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
4914 {
4915 hit_wall = WP_SaberDamageForTrace( ent->s.number, mp2, endNew, baseDamage*4, md2,
4916 qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
4917 saberNum, bladeNum );
4918 }
4919 else
4920 {
4921 float aveLength, step = 8, stepsize = 8;
4922 vec3_t ma1, ma2, md2ang, curBase1, curBase2;
4923 int xx;
4924 //do the trace at the base first
4925 hit_wall = WP_SaberDamageForTrace( ent->s.number, baseOld, baseNew, baseDamage, md2,
4926 qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
4927 saberNum, bladeNum );
4928
4929 //if hit a saber, shorten rest of traces to match
4930 if ( saberHitFraction < 1.0 )
4931 {
4932 //adjust muzzleDir...
4933 vec3_t ma1, ma2;
4934 vectoangles( md1, ma1 );
4935 vectoangles( md2, ma2 );
4936 for ( xx = 0; xx < 3; xx++ )
4937 {
4938 md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
4939 }
4940 AngleVectors( md2ang, md2, NULL, NULL );
4941 //shorten the base pos
4942 VectorSubtract( mp2, mp1, baseDiff );
4943 VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
4944 VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew );
4945 }
4946
4947 //If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
4948 float dirInc, curDirFrac;
4949 if ( PM_SaberInAttack( ent->client->ps.saberMove )
4950 || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
4951 || PM_SpinningSaberAnim( ent->client->ps.torsoAnim )
4952 || PM_InSpecialJump( ent->client->ps.torsoAnim )
4953 || (g_timescale->value<1.0f&&PM_SaberInTransitionAny( ent->client->ps.saberMove )) )
4954 {
4955 curDirFrac = DotProduct( md1, md2 );
4956 }
4957 else
4958 {
4959 curDirFrac = 1.0f;
4960 }
4961 //NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
4962 if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
4963 {//the saber blade spun more than 33 degrees since the last damage trace
4964 curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
4965 }
4966 else
4967 {
4968 curDirFrac = 1.0f;
4969 dirInc = 0.0f;
4970 }
4971 qboolean hit_saber = qfalse;
4972
4973 vectoangles( md1, ma1 );
4974 vectoangles( md2, ma2 );
4975
4976 vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
4977 //VectorSubtract( md2, md1, mdDiff );
4978 VectorCopy( md1, curMD2 );
4979 VectorCopy( baseOld, curBase2 );
4980
4981 while ( 1 )
4982 {
4983 VectorCopy( curMD2, curMD1 );
4984 VectorCopy( curBase2, curBase1 );
4985 if ( curDirFrac >= 1.0f )
4986 {
4987 VectorCopy( md2, curMD2 );
4988 VectorCopy( baseNew, curBase2 );
4989 }
4990 else
4991 {
4992 for ( xx = 0; xx < 3; xx++ )
4993 {
4994 md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
4995 }
4996 AngleVectors( md2ang, curMD2, NULL, NULL );
4997 //VectorMA( md1, curDirFrac, mdDiff, curMD2 );
4998 VectorSubtract( baseNew, baseOld, baseDiff );
4999 VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
5000 }
5001 // Move up the blade in intervals of stepsize
5002 for ( step = stepsize; step < ent->client->ps.saber[saberNum].blade[bladeNum].length && step < ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld; step+=12 )
5003 {
5004 VectorMA( curBase1, step, curMD1, bladePointOld );
5005 VectorMA( curBase2, step, curMD2, bladePointNew );
5006 if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2,
5007 qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue,
5008 saberNum, bladeNum ) )
5009 {
5010 hit_wall = qtrue;
5011 }
5012
5013 //if hit a saber, shorten rest of traces to match
5014 if ( saberHitFraction < 1.0 )
5015 {
5016 //adjust muzzle endpoint
5017 VectorSubtract( mp2, mp1, baseDiff );
5018 VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
5019 VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, curMD2, endNew );
5020 //adjust muzzleDir...
5021 vec3_t curMA1, curMA2;
5022 vectoangles( curMD1, curMA1 );
5023 vectoangles( curMD2, curMA2 );
5024 for ( xx = 0; xx < 3; xx++ )
5025 {
5026 md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
5027 }
5028 AngleVectors( md2ang, curMD2, NULL, NULL );
5029 /*
5030 VectorSubtract( curMD2, curMD1, dirDiff );
5031 VectorMA( curMD1, saberHitFraction, dirDiff, curMD2 );
5032 */
5033 hit_saber = qtrue;
5034 }
5035 if (hit_wall)
5036 {
5037 break;
5038 }
5039 }
5040 if ( hit_wall || hit_saber )
5041 {
5042 break;
5043 }
5044 if ( curDirFrac >= 1.0f )
5045 {
5046 break;
5047 }
5048 else
5049 {
5050 curDirFrac += dirInc;
5051 if ( curDirFrac >= 1.0f )
5052 {
5053 curDirFrac = 1.0f;
5054 }
5055 }
5056 }
5057
5058 //do the trace at the end last
5059 //Special check- adjust for length of blade not being a multiple of 12
5060 aveLength = (ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld + ent->client->ps.saber[saberNum].blade[bladeNum].length)/2;
5061 if ( step > aveLength )
5062 {//less dmg if the last interval was not stepsize
5063 tipDmgMod = (stepsize-(step-aveLength))/stepsize;
5064 }
5065 //NOTE: since this is the tip, we do not extrapolate the extra 16
5066 if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2,
5067 qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse,
5068 saberNum, bladeNum ) )
5069 {
5070 hit_wall = qtrue;
5071 }
5072 }
5073
5074 if ( (saberHitFraction < 1.0f||(sabersCrossed>=0&&sabersCrossed<=32.0f)) && (ent->client->ps.weaponstate == WEAPON_FIRING || ent->client->ps.saberInFlight || G_InCinematicSaberAnim( ent ) ) )
5075 {// The saber (in-hand) hit another saber, mano.
5076 qboolean inFlightSaberBlocked = qfalse;
5077 qboolean collisionResolved = qfalse;
5078 qboolean deflected = qfalse;
5079
5080 gentity_t *hitEnt = &g_entities[saberHitEntity];
5081 gentity_t *hitOwner = NULL;
5082 int hitOwnerPowerLevel = FORCE_LEVEL_0;
5083
5084 if ( hitEnt )
5085 {
5086 hitOwner = hitEnt->owner;
5087 }
5088 if ( hitOwner && hitOwner->client )
5089 {
5090 hitOwnerPowerLevel = PM_PowerLevelForSaberAnim( &hitOwner->client->ps );
5091 /*
5092 if ( entPowerLevel >= FORCE_LEVEL_3
5093 && PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
5094 {//a special "unblockable" attack
5095 if ( hitOwner->client->NPC_class == CLASS_ALORA
5096 || hitOwner->client->NPC_class == CLASS_SHADOWTROOPER
5097 || (hitOwner->NPC&&(hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER)) )
5098 {//these masters can even block unblockables (stops cheap kills)
5099 entPowerLevel = FORCE_LEVEL_2;
5100 }
5101 }
5102 */
5103 }
5104
5105 //FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose
5106 //SEF_LOCKED
5107
5108 if ( ent->client->ps.saberInFlight && saberNum == 0 &&
5109 ent->client->ps.saber[saberNum].blade[bladeNum].active &&
5110 ent->client->ps.saberEntityNum != ENTITYNUM_NONE &&
5111 ent->client->ps.saberEntityState != SES_RETURNING )
5112 {//saber was blocked, return it
5113 inFlightSaberBlocked = qtrue;
5114 }
5115
5116 //FIXME: based on strength, position and angle of attack & defense, decide if:
5117 // defender and attacker lock sabers
5118 // *defender's parry should hold and attack bounces (or deflects, based on angle of sabers)
5119 // *defender's parry is somewhat broken and both bounce (or deflect)
5120 // *defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead)
5121 // defender is knocked down and attack goes through
5122
5123 //Check deflections and broken parries
5124 if ( hitOwner && hitOwner->health > 0 && ent->health > 0 //both are alive
5125 && !inFlightSaberBlocked && hitOwner->client && !hitOwner->client->ps.saberInFlight && !ent->client->ps.saberInFlight//both have sabers in-hand
5126 && ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
5127 && ent->client->ps.saberLockTime < level.time
5128 && hitOwner->client->ps.saberLockTime < level.time )
5129 {//2 in-hand sabers hit
5130 //FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown...
5131 if ( baseDamage )
5132 {//there is damage involved, not just effects
5133 qboolean entAttacking = qfalse;
5134 qboolean hitOwnerAttacking = qfalse;
5135 qboolean entDefending = qfalse;
5136 qboolean hitOwnerDefending = qfalse;
5137 qboolean forceLock = qfalse;
5138
5139 if ( (ent->client->NPC_class == CLASS_KYLE && (ent->spawnflags&1) && hitOwner->s.number < MAX_CLIENTS )
5140 || (hitOwner->client->NPC_class == CLASS_KYLE && (hitOwner->spawnflags&1) && ent->s.number < MAX_CLIENTS ) )
5141 {//Player vs. Kyle Boss == lots of saberlocks
5142 if ( !Q_irand( 0, 2 ) )
5143 {
5144 forceLock = qtrue;
5145 }
5146 }
5147
5148 if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
5149 {
5150 entAttacking = qtrue;
5151 }
5152 else if ( entPowerLevel > FORCE_LEVEL_2 )
5153 {//stronger styles count as attacking even if in a transition
5154 if ( PM_SaberInTransitionAny( ent->client->ps.saberMove ) )
5155 {
5156 entAttacking = qtrue;
5157 }
5158 }
5159 if ( PM_SaberInParry( ent->client->ps.saberMove )
5160 || ent->client->ps.saberMove == LS_READY )
5161 {
5162 entDefending = qtrue;
5163 }
5164
5165 if ( ent->client->ps.torsoAnim == BOTH_A1_SPECIAL
5166 || ent->client->ps.torsoAnim == BOTH_A2_SPECIAL
5167 || ent->client->ps.torsoAnim == BOTH_A3_SPECIAL )
5168 {//parry/block/break-parry bonus for single-style kata moves
5169 entPowerLevel++;
5170 }
5171 if ( entAttacking )
5172 {//add twoHanded bonus and breakParryBonus to entPowerLevel here
5173 //This makes staff too powerful
5174 if ( (ent->client->ps.saber[saberNum].saberFlags&SFL_TWO_HANDED) )
5175 {
5176 entPowerLevel++;
5177 }
5178 //FIXME: what if dualSabers && both sabers are hitting at same time?
5179 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum ) )
5180 {
5181 entPowerLevel += ent->client->ps.saber[saberNum].breakParryBonus;
5182 }
5183 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum ) )
5184 {
5185 entPowerLevel += ent->client->ps.saber[saberNum].breakParryBonus2;
5186 }
5187 }
5188 else if ( entDefending )
5189 {//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here
5190 if ( (ent->client->ps.saber[saberNum].saberFlags&SFL_TWO_HANDED)
5191 || (ent->client->ps.dualSabers && ent->client->ps.saber[1].Active()) )
5192 {
5193 entPowerLevel++;
5194 }
5195 //FIXME: what about second saber if dualSabers?
5196 entPowerLevel += ent->client->ps.saber[saberNum].parryBonus;
5197 }
5198
5199 if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) )
5200 {
5201 hitOwnerAttacking = qtrue;
5202 }
5203 else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
5204 {//stronger styles count as attacking even if in a transition
5205 if ( PM_SaberInTransitionAny( hitOwner->client->ps.saberMove ) )
5206 {
5207 hitOwnerAttacking = qtrue;
5208 }
5209 }
5210 if ( PM_SaberInParry( hitOwner->client->ps.saberMove )
5211 || hitOwner->client->ps.saberMove == LS_READY )
5212 {
5213 hitOwnerDefending = qtrue;
5214 }
5215
5216 if ( hitOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL
5217 || hitOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL
5218 || hitOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL )
5219 {//parry/block/break-parry bonus for single-style kata moves
5220 hitOwnerPowerLevel++;
5221 }
5222 if ( hitOwnerAttacking )
5223 {//add twoHanded bonus and breakParryBonus to entPowerLevel here
5224 if ( (hitOwner->client->ps.saber[0].saberFlags&SFL_TWO_HANDED) )
5225 {
5226 hitOwnerPowerLevel++;
5227 }
5228 hitOwnerPowerLevel += hitOwner->client->ps.saber[0].breakParryBonus;
5229 if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) )
5230 {//FIXME: assumes both sabers are hitting at same time...?
5231 hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].breakParryBonus;
5232 }
5233 }
5234 else if ( hitOwnerDefending )
5235 {//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here
5236 if ( (hitOwner->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)
5237 || (hitOwner->client->ps.dualSabers && hitOwner->client->ps.saber[1].Active()) )
5238 {
5239 hitOwnerPowerLevel++;
5240 }
5241 hitOwnerPowerLevel += hitOwner->client->ps.saber[0].parryBonus;
5242 if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) )
5243 {//FIXME: assumes both sabers are defending at same time...?
5244 hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].parryBonus;
5245 }
5246 }
5247
5248 if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim )
5249 || PM_SuperBreakWinAnim( ent->client->ps.torsoAnim )
5250 || PM_SuperBreakLoseAnim( hitOwner->client->ps.torsoAnim )
5251 || PM_SuperBreakWinAnim( hitOwner->client->ps.torsoAnim ) )
5252 {//don't mess with this
5253 collisionResolved = qtrue;
5254 }
5255 else if ( entAttacking
5256 && hitOwnerAttacking
5257 && !Q_irand( 0, g_saberLockRandomNess->integer )
5258 && ( g_debugSaberLock->integer || forceLock
5259 || entPowerLevel == hitOwnerPowerLevel
5260 || (entPowerLevel > FORCE_LEVEL_2 && hitOwnerPowerLevel > FORCE_LEVEL_2 )
5261 || (entPowerLevel < FORCE_LEVEL_3 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && Q_irand( 0, 3 ))
5262 || (entPowerLevel < FORCE_LEVEL_2 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 2 ))
5263 || (hitOwnerPowerLevel < FORCE_LEVEL_3 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && !Q_irand( 0, 1 ))
5264 || (hitOwnerPowerLevel < FORCE_LEVEL_2 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && !Q_irand( 0, 1 )))
5265 && WP_SabersCheckLock( ent, hitOwner ) )
5266 {
5267 collisionResolved = qtrue;
5268 }
5269 else if ( hitOwnerAttacking
5270 && entDefending
5271 && !Q_irand( 0, g_saberLockRandomNess->integer*3 )
5272 && (g_debugSaberLock->integer || forceLock ||
5273 ((ent->client->ps.saberMove != LS_READY || (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
5274 && ((hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )||
5275 (hitOwnerPowerLevel < FORCE_LEVEL_2 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )||
5276 (hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 ))) ))
5277 && WP_SabersCheckLock( hitOwner, ent ) )
5278 {
5279 collisionResolved = qtrue;
5280 }
5281 else if ( entAttacking && hitOwnerDefending )
5282 {//I'm attacking hit, they're parrying
5283 qboolean activeDefense = (qboolean)(hitOwner->s.number||g_saberAutoBlocking->integer||hitOwner->client->ps.saberBlockingTime > level.time);
5284 if ( !Q_irand( 0, g_saberLockRandomNess->integer*3 )
5285 && activeDefense
5286 && (g_debugSaberLock->integer || forceLock ||
5287 ((hitOwner->client->ps.saberMove != LS_READY || (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
5288 && ( ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
5289 || ( entPowerLevel < FORCE_LEVEL_2 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )
5290 || ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 )) ) ))
5291 && WP_SabersCheckLock( ent, hitOwner ) )
5292 {
5293 collisionResolved = qtrue;
5294 }
5295 else if ( saberHitFraction < 1.0f )
5296 {//an actual collision
5297 if ( entPowerLevel < FORCE_LEVEL_3 && activeDefense )
5298 {//strong attacks cannot be deflected
5299 //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
5300 deflected = WP_GetSaberDeflectionAngle( ent, hitOwner );
5301 //just so Jedi knows that he was blocked
5302 ent->client->ps.saberEventFlags |= SEF_BLOCKED;
5303 }
5304 //base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
5305 if ( entPowerLevel < FORCE_LEVEL_3
5306 //&& ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_3//if you have high saber offense, you cannot have your attack knocked away, regardless of what style you're using?
5307 //&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
5308 && activeDefense
5309 && (hitOwnerPowerLevel > FORCE_LEVEL_2||(hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&Q_irand(0,hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE]))) )
5310 {//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion)
5311 //make me parry
5312 WP_SaberParry( hitOwner, ent, saberNum, bladeNum );
5313 //turn the parry into a knockaway
5314 hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
5315 //make them go into a broken parry
5316 ent->client->ps.saberBounceMove = PM_BrokenParryForAttack( ent->client->ps.saberMove );
5317 ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5318 if ( saberNum == 0 )
5319 {//FIXME: can only lose right-hand saber for now
5320 if ( !(ent->client->ps.saber[saberNum].saberFlags&SFL_NOT_DISARMABLE)
5321 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
5322 //&& (ent->s.number||g_saberRealisticCombat->integer)
5323 && Q_irand( 0, hitOwner->client->ps.SaberDisarmBonus( 0 ) ) > 0
5324 && (hitOwner->s.number || g_saberAutoBlocking->integer || !Q_irand( 0, 2 )) )//if player defending and autoblocking is on, this is less likely to happen, so don't do the random check
5325 {//knocked the saber right out of his hand! (never happens to player)
5326 //Get a good velocity to send the saber in based on my parry move
5327 vec3_t throwDir;
5328 if ( !PM_VelocityForBlockedMove( &hitOwner->client->ps, throwDir ) )
5329 {
5330 PM_VelocityForSaberMove( &ent->client->ps, throwDir );
5331 }
5332 WP_SaberLose( ent, throwDir );
5333 }
5334 }
5335 //just so Jedi knows that he was blocked
5336 ent->client->ps.saberEventFlags |= SEF_BLOCKED;
5337 #ifndef FINAL_BUILD
5338 if ( d_saberCombat->integer )
5339 {
5340 gi.Printf( S_COLOR_RED"%s knockaway %s's attack, new move = %s, anim = %s\n", hitOwner->NPC_type, ent->NPC_type, saberMoveData[ent->client->ps.saberBounceMove].name, animTable[saberMoveData[ent->client->ps.saberBounceMove].animToUse].name );
5341 }
5342 #endif
5343 }
5344 else if ( !activeDefense//they're not defending
5345 || (entPowerLevel > FORCE_LEVEL_2 //I hit hard
5346 && hitOwnerPowerLevel < entPowerLevel)//they are defending, but their defense strength is lower than my attack...
5347 || (!deflected && Q_irand( 0, Q_max(0, PM_PowerLevelForSaberAnim( &ent->client->ps, saberNum ) - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE])/*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) )
5348 {//broke their parry altogether
5349 if ( entPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, Q_max(0, ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) ) )
5350 {//chance of continuing with the attack (not bouncing back)
5351 ent->client->ps.saberEventFlags &= ~SEF_BLOCKED;
5352 ent->client->ps.saberBounceMove = LS_NONE;
5353 brokenParry = qtrue;
5354 }
5355 //do some time-consuming saber-knocked-aside broken parry anim
5356 hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5357 hitOwner->client->ps.saberBounceMove = LS_NONE;
5358 //FIXME: for now, you always disarm the right-hand saber
5359 if ( !(hitOwner->client->ps.saber[0].saberFlags&SFL_NOT_DISARMABLE)
5360 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
5361 //&& (ent->s.number||g_saberRealisticCombat->integer)
5362 && Q_irand( 0, 2-ent->client->ps.SaberDisarmBonus( bladeNum ) ) <= 0 )
5363 {//knocked the saber right out of his hand!
5364 //get the right velocity for my attack direction
5365 vec3_t throwDir;
5366 PM_VelocityForSaberMove( &ent->client->ps, throwDir );
5367 WP_SaberLose( hitOwner, throwDir );
5368 if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,3) )
5369 || ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,1) ) )
5370 {// a strong attack
5371 if ( WP_BrokenParryKnockDown( hitOwner ) )
5372 {
5373 hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
5374 hitOwner->client->ps.saberBounceMove = LS_NONE;
5375 }
5376 }
5377 }
5378 else
5379 {
5380 if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,5) )
5381 || ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,3) ) )
5382 {// a strong attack
5383 if ( WP_BrokenParryKnockDown( hitOwner ) )
5384 {
5385 hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
5386 hitOwner->client->ps.saberBounceMove = LS_NONE;
5387 }
5388 }
5389 }
5390 #ifndef FINAL_BUILD
5391 if ( d_saberCombat->integer )
5392 {
5393 if ( ent->client->ps.saberEventFlags&SEF_BLOCKED )
5394 {
5395 gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", hitOwner->targetname );
5396 }
5397 else
5398 {
5399 gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", hitOwner->targetname );
5400 }
5401 }
5402 #endif
5403 }
5404 else
5405 {//just a parry, possibly the hitOwner can knockaway the ent
5406 WP_SaberParry( hitOwner, ent, saberNum, bladeNum );
5407 if ( PM_SaberInBounce( ent->client->ps.saberMove ) //FIXME: saberMove not set until pmove!
5408 && activeDefense
5409 && hitOwner->client->ps.saberAnimLevel != SS_FAST //&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
5410 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
5411 {//attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks
5412 //turn the parry into a knockaway
5413 hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
5414 }
5415 else if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,6) )
5416 || ( ent->client->ps.saberAnimLevel==SS_DESANN && !Q_irand(0,3) ) )
5417 {// a strong attack can sometimes do a knockdown
5418 //HMM... maybe only if they're moving backwards?
5419 if ( WP_BrokenParryKnockDown( hitOwner ) )
5420 {
5421 hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
5422 hitOwner->client->ps.saberBounceMove = LS_NONE;
5423 }
5424 }
5425 }
5426 collisionResolved = qtrue;
5427 }
5428 }
5429 /*
5430 else if ( entDefending && hitOwnerAttacking )
5431 {//I'm parrying, they're attacking
5432 if ( hitOwnerPowerLevel < FORCE_LEVEL_3 )
5433 {//strong attacks cannot be deflected
5434 //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
5435 deflected = WP_GetSaberDeflectionAngle( hitOwner, ent );
5436 //just so Jedi knows that he was blocked
5437 hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
5438 }
5439 //FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
5440 if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &hitOwner->client->ps ) - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) > 0 ) )
5441 {//broke my parry altogether
5442 if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
5443 {//chance of continuing with the attack (not bouncing back)
5444 hitOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
5445 hitOwner->client->ps.saberBounceMove = LS_NONE;
5446 }
5447 //do some time-consuming saber-knocked-aside broken parry anim
5448 ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5449 #ifndef FINAL_BUILD
5450 if ( d_saberCombat->integer )
5451 {
5452 if ( hitOwner->client->ps.saberEventFlags&SEF_BLOCKED )
5453 {
5454 gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", ent->targetname );
5455 }
5456 else
5457 {
5458 gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", ent->targetname );
5459 }
5460 }
5461 #endif
5462 }
5463 else
5464 {
5465 WP_SaberParry( ent, hitOwner, saberNum, bladeNum );
5466 }
5467 collisionResolved = qtrue;
5468 }
5469 */
5470 else
5471 {//some other kind of in-hand saber collision
5472 }
5473 }
5474 }
5475 else
5476 {//some kind of in-flight collision
5477 }
5478
5479 if ( saberHitFraction < 1.0f )
5480 {
5481 if ( !collisionResolved && baseDamage )
5482 {//some other kind of in-hand saber collision
5483 //handle my reaction
5484 if ( !ent->client->ps.saberInFlight
5485 && ent->client->ps.saberLockTime < level.time )
5486 {//my saber is in hand
5487 if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5488 {
5489 if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ||
5490 (entPowerLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(ent->client->ps.saberMove)&&!PM_SaberInParry(ent->client->ps.saberMove)&&!PM_SaberInReflect(ent->client->ps.saberMove)) )
5491 {//in the middle of attacking
5492 if ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->health > 0 )
5493 {//don't deflect/bounce in strong attack or when enemy is dead
5494 WP_GetSaberDeflectionAngle( ent, hitOwner );
5495 ent->client->ps.saberEventFlags |= SEF_BLOCKED;
5496 //since it was blocked/deflected, take away any damage done
5497 //FIXME: what if the damage was done before the parry?
5498 WP_SaberClearDamageForEntNum( ent, hitOwner->s.number, saberNum, bladeNum );
5499 }
5500 }
5501 else
5502 {//saber collided when not attacking, parry it
5503 //since it was blocked/deflected, take away any damage done
5504 //FIXME: what if the damage was done before the parry?
5505 WP_SaberClearDamageForEntNum( ent, hitOwner->s.number, saberNum, bladeNum );
5506 /*
5507 if ( ent->s.number || g_saberAutoBlocking->integer || ent->client->ps.saberBlockingTime > level.time )
5508 {//either an NPC or a player who has blocking
5509 if ( !PM_SaberInTransitionAny( ent->client->ps.saberMove ) && !PM_SaberInBounce( ent->client->ps.saberMove ) )
5510 {//I'm not attacking, in transition or in a bounce, so play a parry
5511 //just so Jedi knows that he parried something
5512 WP_SaberBlockNonRandom( ent, saberHitLocation, qfalse );
5513 }
5514 ent->client->ps.saberEventFlags |= SEF_PARRIED;
5515 }
5516 */
5517 }
5518 }
5519 else
5520 {
5521 //since it was blocked/deflected, take away any damage done
5522 //FIXME: what if the damage was done before the parry?
5523 WP_SaberClearDamageForEntNum( ent, hitOwner->s.number, saberNum, bladeNum );
5524 }
5525 }
5526 else
5527 {//nothing happens to *me* when my inFlight saber hits something
5528 }
5529 //handle their reaction
5530 if ( hitOwner
5531 && hitOwner->health > 0
5532 && hitOwner->client
5533 && !hitOwner->client->ps.saberInFlight
5534 && hitOwner->client->ps.saberLockTime < level.time )
5535 {//their saber is in hand
5536 if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) ||
5537 (hitOwner->client->ps.saberAnimLevel > SS_MEDIUM&&!PM_SaberInIdle(hitOwner->client->ps.saberMove)&&!PM_SaberInParry(hitOwner->client->ps.saberMove)&&!PM_SaberInReflect(hitOwner->client->ps.saberMove)) )
5538 {//in the middle of attacking
5539 /*
5540 if ( hitOwner->client->ps.saberAnimLevel < SS_STRONG )
5541 {//don't deflect/bounce in strong attack
5542 WP_GetSaberDeflectionAngle( hitOwner, ent );
5543 hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
5544 }
5545 */
5546 }
5547 else
5548 {//saber collided when not attacking, parry it
5549 if ( !PM_SaberInBrokenParry( hitOwner->client->ps.saberMove ) )
5550 {//not currently in a broken parry
5551 if ( !WP_SaberParry( hitOwner, ent, saberNum, bladeNum ) )
5552 {//FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim?
5553 //hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
5554 }
5555 }
5556 }
5557 }
5558 else
5559 {//nothing happens to *hitOwner* when their inFlight saber hits something
5560 }
5561 }
5562
5563 //collision must have been handled by now
5564 //Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim
5565 if ( ent->client->ps.saberEventFlags & SEF_BLOCKED )
5566 {
5567 if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
5568 {
5569 ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5570 }
5571 }
5572 /*
5573 if ( hitOwner && hitOwner->client->ps.saberEventFlags & SEF_BLOCKED )
5574 {
5575 hitOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5576 }
5577 */
5578 }
5579
5580 if ( saberHitFraction < 1.0f || collisionResolved )
5581 {//either actually hit or locked
5582 if ( ent->client->ps.saberLockTime < level.time )
5583 {
5584 if ( inFlightSaberBlocked )
5585 {//FIXME: never hear this sound
5586 WP_SaberBounceSound( ent, hitOwner, &g_entities[ent->client->ps.saberEntityNum], 0, 0, qfalse );
5587 }
5588 else
5589 {
5590 if ( deflected )
5591 {
5592 WP_SaberBounceSound( ent, hitOwner, NULL, saberNum, bladeNum, qtrue );
5593 }
5594 else
5595 {
5596 WP_SaberBlockSound( ent, hitOwner, saberNum, bladeNum );
5597 }
5598 }
5599 if ( !g_saberNoEffects )
5600 {
5601 WP_SaberBlockEffect( ent, saberNum, bladeNum, saberHitLocation, saberHitNormal, qfalse );
5602 }
5603 }
5604 // Set the little screen flash - only when an attack is blocked
5605 if ( !g_noClashFlare )
5606 {
5607 g_saberFlashTime = level.time-50;
5608 VectorCopy( saberHitLocation, g_saberFlashPos );
5609 }
5610 }
5611
5612 if ( saberHitFraction < 1.0f )
5613 {
5614 if ( inFlightSaberBlocked )
5615 {//we threw a saber and it was blocked, do any effects, etc.
5616 int knockAway = 5;
5617 if ( hitEnt
5618 && hitOwner
5619 && hitOwner->client
5620 && (PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SpinningSaberAnim( hitOwner->client->ps.torsoAnim )) )
5621 {//if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away
5622 if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
5623 {//strong attacks almost always knock it aside!
5624 knockAway = 1;
5625 }
5626 else
5627 {//33% chance
5628 knockAway = 2;
5629 }
5630 knockAway -= hitOwner->client->ps.SaberDisarmBonus( 0 );
5631 }
5632 if ( Q_irand( 0, knockAway ) <= 0 || //random
5633 ( hitOwner
5634 && hitOwner->client
5635 && hitOwner->NPC
5636 && (hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER)
5637 ) //or if blocked by a Boss character FIXME: or base on defense level?
5638 )//FIXME: player should not auto-block a flying saber, let him override the parry with an attack to knock the saber from the air, rather than this random chance
5639 {//knock it aside and turn it off
5640 if ( !g_saberNoEffects )
5641 {
5642 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5643 && ent->client->ps.saber[saberNum].hitOtherEffect )
5644 {
5645 G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect, saberHitLocation, saberHitNormal );
5646 }
5647 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5648 && ent->client->ps.saber[saberNum].hitOtherEffect2 )
5649 {
5650 G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect2, saberHitLocation, saberHitNormal );
5651 }
5652 else
5653 {
5654 G_PlayEffect( "saber/saber_cut", saberHitLocation, saberHitNormal );
5655 }
5656 }
5657 if ( hitEnt )
5658 {
5659 vec3_t newDir;
5660
5661 VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
5662 VectorNormalize( newDir );
5663 G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
5664 }
5665 Jedi_PlayDeflectSound( hitOwner );
5666 WP_SaberDrop( ent, &g_entities[ent->client->ps.saberEntityNum] );
5667 }
5668 else
5669 {
5670 if ( !Q_irand( 0, 2 ) && hitEnt )
5671 {
5672 vec3_t newDir;
5673 VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
5674 VectorNormalize( newDir );
5675 G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
5676 }
5677 WP_SaberReturn( ent, &g_entities[ent->client->ps.saberEntityNum] );
5678 }
5679 }
5680 }
5681 }
5682
5683 if ( ent->client->ps.saberLockTime > level.time )
5684 {
5685 if ( ent->s.number < ent->client->ps.saberLockEnemy
5686 && !Q_irand( 0, 3 ) )
5687 {//need to make some kind of effect
5688 vec3_t hitNorm = {0,0,1};
5689 if ( WP_SabersIntersection( ent, &g_entities[ent->client->ps.saberLockEnemy], g_saberFlashPos ) )
5690 {
5691 if ( Q_irand( 0, 10 ) )
5692 {
5693 if ( !g_saberNoEffects )
5694 {
5695 WP_SaberBlockEffect( ent, saberNum, bladeNum, g_saberFlashPos, hitNorm, qfalse );
5696 }
5697 }
5698 else
5699 {
5700 if ( !g_noClashFlare )
5701 {
5702 g_saberFlashTime = level.time-50;
5703 }
5704 if ( !g_saberNoEffects )
5705 {
5706 WP_SaberBlockEffect( ent, saberNum, bladeNum, g_saberFlashPos, hitNorm, qtrue );
5707 }
5708 }
5709 WP_SaberBlockSound( ent, &g_entities[ent->client->ps.saberLockEnemy], 0, 0 );
5710 }
5711 }
5712 }
5713 else
5714 {
5715 if ( hit_wall
5716 && (ent->client->ps.saber[saberNum].saberFlags&SFL_BOUNCE_ON_WALLS)
5717 && (PM_SaberInAttackPure( ent->client->ps.saberMove ) //only in a normal attack anim
5718 || ent->client->ps.saberMove == LS_A_JUMP_T__B_ ) //or in the strong jump-fwd-attack "death from above" move
5719 )
5720 {//bounce off walls
5721 //do anim
5722 ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
5723 ent->client->ps.saberBounceMove = LS_D1_BR+(saberMoveData[ent->client->ps.saberMove].startQuad-Q_BR);
5724 //do bounce sound & force feedback
5725 WP_SaberBounceOnWallSound( ent, saberNum, bladeNum );
5726 //do hit effect
5727 if ( !g_saberNoEffects )
5728 {
5729 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5730 && ent->client->ps.saber[saberNum].hitOtherEffect )
5731 {
5732 G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect, saberHitLocation, saberHitNormal );
5733 }
5734 else if ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum )
5735 && ent->client->ps.saber[saberNum].hitOtherEffect2 )
5736 {
5737 G_PlayEffect( ent->client->ps.saber[saberNum].hitOtherEffect2, saberHitLocation, saberHitNormal );
5738 }
5739 else
5740 {
5741 G_PlayEffect( "saber/saber_cut", saberHitLocation, saberHitNormal );
5742 }
5743 }
5744 //do radius damage/knockback, if any
5745 if ( !WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[saberNum], bladeNum ) )
5746 {
5747 WP_SaberRadiusDamage( ent, saberHitLocation, ent->client->ps.saber[saberNum].splashRadius, ent->client->ps.saber[saberNum].splashDamage, ent->client->ps.saber[saberNum].splashKnockback );
5748 }
5749 else
5750 {
5751 WP_SaberRadiusDamage( ent, saberHitLocation, ent->client->ps.saber[saberNum].splashRadius2, ent->client->ps.saber[saberNum].splashDamage2, ent->client->ps.saber[saberNum].splashKnockback2 );
5752 }
5753 }
5754 }
5755
5756 if ( WP_SaberApplyDamage( ent, baseDamage, baseDFlags, brokenParry, saberNum, bladeNum, (qboolean)(saberNum==0&&ent->client->ps.saberInFlight) ) )
5757 {//actually did damage to something
5758 #ifndef FINAL_BUILD
5759 if ( d_saberCombat->integer )
5760 {
5761 gi.Printf( "base damage was %4.2f\n", baseDamage );
5762 }
5763 #endif
5764 WP_SaberHitSound( ent, saberNum, bladeNum );
5765 }
5766
5767 if ( hit_wall )
5768 {
5769 //just so Jedi knows that he hit a wall
5770 ent->client->ps.saberEventFlags |= SEF_HITWALL;
5771 if ( ent->s.number == 0 )
5772 {
5773 AddSoundEvent( ent, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: is this impact on ground or not?
5774 AddSightEvent( ent, ent->currentOrigin, 256, AEL_DISCOVERED, 50 );
5775 }
5776 }
5777 }
5778
WP_SabersDamageTrace(gentity_t * ent,qboolean noEffects)5779 void WP_SabersDamageTrace( gentity_t *ent, qboolean noEffects )
5780 {
5781 if ( !ent->client )
5782 {
5783 return;
5784 }
5785 if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) )
5786 {
5787 return;
5788 }
5789 // Saber 1.
5790 g_saberNoEffects = noEffects;
5791 for ( int i = 0; i < ent->client->ps.saber[0].numBlades; i++ )
5792 {
5793 // If the Blade is not active and the length is 0, don't trace it, try the next blade...
5794 if ( !ent->client->ps.saber[0].blade[i].active && ent->client->ps.saber[0].blade[i].length == 0 )
5795 continue;
5796
5797 if ( i != 0 )
5798 {//not first blade
5799 if ( ent->client->ps.saber[0].type == SABER_BROAD ||
5800 ent->client->ps.saber[0].type == SABER_SAI ||
5801 ent->client->ps.saber[0].type == SABER_CLAW )
5802 {
5803 g_saberNoEffects = qtrue;
5804 }
5805 }
5806 g_noClashFlare = qfalse;
5807 if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[0], i ) && (ent->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
5808 || ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[0], i ) && (ent->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE2) ) )
5809 {
5810 g_noClashFlare = qtrue;
5811 }
5812 WP_SaberDamageTrace( ent, 0, i );
5813 }
5814 // Saber 2.
5815 g_saberNoEffects = noEffects;
5816 if ( ent->client->ps.dualSabers )
5817 {
5818 for ( int i = 0; i < ent->client->ps.saber[1].numBlades; i++ )
5819 {
5820 // If the Blade is not active and the length is 0, don't trace it, try the next blade...
5821 if ( !ent->client->ps.saber[1].blade[i].active && ent->client->ps.saber[1].blade[i].length == 0 )
5822 continue;
5823
5824 if ( i != 0 )
5825 {//not first blade
5826 if ( ent->client->ps.saber[1].type == SABER_BROAD ||
5827 ent->client->ps.saber[1].type == SABER_SAI ||
5828 ent->client->ps.saber[1].type == SABER_CLAW )
5829 {
5830 g_saberNoEffects = qtrue;
5831 }
5832 }
5833 g_noClashFlare = qfalse;
5834 if ( (!WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[1], i ) && (ent->client->ps.saber[1].saberFlags2&SFL2_NO_CLASH_FLARE) )
5835 || ( WP_SaberBladeUseSecondBladeStyle( &ent->client->ps.saber[1], i ) && (ent->client->ps.saber[1].saberFlags2&SFL2_NO_CLASH_FLARE2) ) )
5836 {
5837 g_noClashFlare = qtrue;
5838 }
5839 WP_SaberDamageTrace( ent, 1, i );
5840 }
5841 }
5842 g_saberNoEffects = qfalse;
5843 g_noClashFlare = qfalse;
5844 }
5845
5846 //SABER THROWING============================================================================
5847 //SABER THROWING============================================================================
5848 //SABER THROWING============================================================================
5849 //SABER THROWING============================================================================
5850 //SABER THROWING============================================================================
5851 //SABER THROWING============================================================================
5852
5853 /*
5854 ================
5855 WP_SaberImpact
5856
5857 ================
5858 */
WP_SaberImpact(gentity_t * owner,gentity_t * saber,trace_t * trace)5859 void WP_SaberImpact( gentity_t *owner, gentity_t *saber, trace_t *trace )
5860 {
5861 gentity_t *other;
5862
5863 other = &g_entities[trace->entityNum];
5864
5865 if ( other->takedamage && (other->svFlags&SVF_BBRUSH) )
5866 {//a breakable brush? break it!
5867 if ( (other->spawnflags&1)//INVINCIBLE
5868 ||(other->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only
5869 {//can't actually break it
5870 //no hit effect (besides regular client-side one)
5871 }
5872 else if ( other->NPC_targetname &&
5873 (!owner||!owner->targetname||Q_stricmp(owner->targetname,other->NPC_targetname)) )
5874 {//only breakable by an entity who is not the attacker
5875 //no hit effect (besides regular client-side one)
5876 }
5877 else
5878 {
5879 vec3_t dir;
5880 VectorCopy( saber->s.pos.trDelta, dir );
5881 VectorNormalize( dir );
5882
5883 int dmg = other->health*2;
5884 if ( other->health > 50 && dmg > 20 && !(other->svFlags&SVF_GLASS_BRUSH) )
5885 {
5886 dmg = 20;
5887 }
5888 G_Damage( other, saber, owner, dir, trace->endpos, dmg, 0, MOD_SABER );
5889 if ( owner
5890 && owner->client
5891 && owner->client->ps.saber[0].hitOtherEffect )
5892 {
5893 G_PlayEffect( owner->client->ps.saber[0].hitOtherEffect, trace->endpos, dir );
5894 }
5895 else
5896 {
5897 G_PlayEffect( "saber/saber_cut", trace->endpos, dir );
5898 }
5899 if ( owner->s.number == 0 )
5900 {
5901 AddSoundEvent( owner, trace->endpos, 256, AEL_DISCOVERED );
5902 AddSightEvent( owner, trace->endpos, 512, AEL_DISCOVERED, 50 );
5903 }
5904 return;
5905 }
5906 }
5907
5908 if ( saber->s.pos.trType == TR_LINEAR )
5909 {
5910 //hit a wall? send it back
5911 WP_SaberReturn( saber->owner, saber );
5912 }
5913
5914 if ( other && !other->client && (other->contents&CONTENTS_LIGHTSABER) )//&& other->s.weapon == WP_SABER )
5915 {//2 in-flight sabers collided!
5916 //Big flash
5917 //FIXME: bigger effect/sound?
5918 //FIXME: STILL DOESNT WORK!!!
5919 WP_SaberBlockSound( saber->owner, NULL, 0, 0 );
5920 //G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
5921 WP_SaberBlockEffect( saber->owner, 0, 0, trace->endpos, NULL, qfalse);
5922 qboolean noFlare = qfalse;
5923 if ( saber->owner
5924 && saber->owner->client
5925 && (saber->owner->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
5926 {
5927 noFlare = qtrue;
5928 }
5929 if ( !noFlare )
5930 {
5931 g_saberFlashTime = level.time-50;
5932 VectorCopy( trace->endpos, g_saberFlashPos );
5933 }
5934 }
5935
5936 if ( owner && owner->s.number == 0 && owner->client )
5937 {
5938 //Add the event
5939 if ( owner->client->ps.SaberLength() > 0 )
5940 {//saber is on, very suspicious
5941 if ( (!owner->client->ps.saberInFlight && owner->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground
5942 || saber->s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground
5943 {//an on-ground alert
5944 AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );
5945 }
5946 else
5947 {//an in-air alert
5948 AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED );
5949 }
5950 AddSightEvent( owner, saber->currentOrigin, 256, AEL_DISCOVERED, 50 );
5951 }
5952 else
5953 {//saber is off, not as suspicious
5954 AddSoundEvent( owner, saber->currentOrigin, 128, AEL_SUSPICIOUS );
5955 AddSightEvent( owner, saber->currentOrigin, 256, AEL_SUSPICIOUS );
5956 }
5957 }
5958
5959 // check for bounce
5960 if ( !other->takedamage && ( saber->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
5961 {
5962 // Check to see if there is a bounce count
5963 if ( saber->bounceCount ) {
5964 // decrement number of bounces and then see if it should be done bouncing
5965 if ( --saber->bounceCount <= 0 ) {
5966 // He (or she) will bounce no more (after this current bounce, that is).
5967 saber->s.eFlags &= ~( EF_BOUNCE | EF_BOUNCE_HALF );
5968 if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
5969 {
5970 WP_SaberDrop( saber->owner, saber );
5971 }
5972 return;
5973 }
5974 else
5975 {//bounced and still have bounces left
5976 if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
5977 {//under telekinetic control
5978 if ( !gi.inPVS( saber->currentOrigin, owner->client->renderInfo.handRPoint ) )
5979 {//not in the PVS of my master
5980 saber->bounceCount -= 25;
5981 }
5982 }
5983 }
5984 }
5985
5986 if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
5987 {
5988 //don't home for a few frames so we can get around this thing
5989 trace_t bounceTr;
5990 vec3_t end;
5991 float owner_dist = Distance( owner->client->renderInfo.handRPoint, saber->currentOrigin );
5992
5993 VectorMA( saber->currentOrigin, 10, trace->plane.normal, end );
5994 gi.trace( &bounceTr, saber->currentOrigin, saber->mins, saber->maxs, end, owner->s.number, saber->clipmask, (EG2_Collision)0, 0 );
5995 VectorCopy( bounceTr.endpos, saber->currentOrigin );
5996 if ( owner_dist > 0 )
5997 {
5998 if ( owner_dist > 50 )
5999 {
6000 owner->client->ps.saberEntityDist = owner_dist-50;
6001 }
6002 else
6003 {
6004 owner->client->ps.saberEntityDist = 0;
6005 }
6006 }
6007 return;
6008 }
6009
6010 G_BounceMissile( saber, trace );
6011
6012 if ( saber->s.pos.trType == TR_GRAVITY )
6013 {//bounced
6014 //play a bounce sound
6015 WP_SaberFallSound( owner, saber );
6016 //change rotation
6017 VectorCopy( saber->currentAngles, saber->s.apos.trBase );
6018 saber->s.apos.trType = TR_LINEAR;
6019 saber->s.apos.trTime = level.time;
6020 VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
6021 }
6022 //see if we stopped
6023 else if ( saber->s.pos.trType == TR_STATIONARY )
6024 {//stopped
6025 //play a bounce sound
6026 WP_SaberFallSound( owner, saber );
6027 //stop rotation
6028 VectorClear( saber->s.apos.trDelta );
6029 pitch_roll_for_slope( saber, trace->plane.normal, saber->currentAngles );
6030 saber->currentAngles[0] += SABER_PITCH_HACK;
6031 VectorCopy( saber->currentAngles, saber->s.apos.trBase );
6032 //remember when it fell so it can return automagically
6033 saber->aimDebounceTime = level.time;
6034 }
6035 }
6036 else if ( other->client && other->health > 0
6037 && ( (other->NPC && (other->NPC->aiFlags&NPCAI_BOSS_CHARACTER))
6038 //|| other->client->NPC_class == CLASS_ALORA
6039 || other->client->NPC_class == CLASS_BOBAFETT
6040 || ( other->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) ) )
6041 {//Luke, Desann and Tavion slap thrown sabers aside
6042 WP_SaberDrop( owner, saber );
6043 WP_SaberBlockSound( owner, other, 0, 0 );
6044 //G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
6045 WP_SaberBlockEffect( owner, 0, 0, trace->endpos, NULL, qfalse );
6046 qboolean noFlare = qfalse;
6047 if ( owner
6048 && owner->client
6049 && (owner->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
6050 {
6051 noFlare = qtrue;
6052 }
6053 if ( !noFlare )
6054 {
6055 g_saberFlashTime = level.time-50;
6056 VectorCopy( trace->endpos, g_saberFlashPos );
6057 }
6058 //FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens
6059 Jedi_PlayDeflectSound( other );
6060 }
6061 }
6062
6063 extern float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from );
WP_SaberInFlightReflectCheck(gentity_t * self,usercmd_t * ucmd)6064 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd )
6065 {
6066 gentity_t *ent;
6067 gentity_t *entityList[MAX_GENTITIES];
6068 gentity_t *missile_list[MAX_GENTITIES];
6069 int numListedEntities;
6070 vec3_t mins, maxs;
6071 int i, e, numSabers;
6072 int ent_count = 0;
6073 int radius = 180;
6074 vec3_t center;
6075 vec3_t tip;
6076 vec3_t up = {0,0,1};
6077 qboolean willHit = qfalse;
6078
6079 if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
6080 {//don't react to things flying at me...
6081 return;
6082 }
6083 //sanity checks: make sure we actually have a saberent
6084 if ( self->client->ps.weapon != WP_SABER )
6085 {
6086 return;
6087 }
6088 if ( !self->client->ps.saberInFlight )
6089 {
6090 return;
6091 }
6092 if ( !self->client->ps.SaberLength() )
6093 {
6094 return;
6095 }
6096 if ( self->client->ps.saberEntityNum == ENTITYNUM_NONE )
6097 {
6098 return;
6099 }
6100 gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
6101 if ( !saberent )
6102 {
6103 return;
6104 }
6105 //okay, enough damn sanity checks
6106
6107 VectorCopy( saberent->currentOrigin, center );
6108
6109 for ( i = 0 ; i < 3 ; i++ )
6110 {
6111 mins[i] = center[i] - radius;
6112 maxs[i] = center[i] + radius;
6113 }
6114
6115 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
6116
6117 //FIXME: check visibility?
6118 for ( e = 0 ; e < numListedEntities ; e++ )
6119 {
6120 ent = entityList[ e ];
6121
6122 if (ent == self)
6123 continue;
6124 if (ent->owner == self)
6125 continue;
6126 if ( !(ent->inuse) )
6127 continue;
6128 if ( ent->s.eType != ET_MISSILE )
6129 {
6130 if ( ent->client || ent->s.weapon != WP_SABER )
6131 {//FIXME: wake up bad guys?
6132 continue;
6133 }
6134 if ( ent->s.eFlags & EF_NODRAW )
6135 {
6136 continue;
6137 }
6138 if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
6139 {//not a lightsaber
6140 continue;
6141 }
6142 }
6143 else
6144 {//FIXME: make exploding missiles explode?
6145 if ( ent->s.pos.trType == TR_STATIONARY )
6146 {//nothing you can do with a stationary missile
6147 continue;
6148 }
6149 if ( ent->splashDamage || ent->splashRadius )
6150 {//can't deflect exploding missiles
6151 if ( DistanceSquared( ent->currentOrigin, center ) < 256 )//16 squared
6152 {
6153 G_MissileImpacted( ent, saberent, ent->currentOrigin, up );
6154 }
6155 continue;
6156 }
6157 }
6158
6159 //don't deflect it if it's not within 16 units of the blade
6160 //do this for all blades
6161 willHit = qfalse;
6162 numSabers = 1;
6163 if ( self->client->ps.dualSabers )
6164 {
6165 numSabers = 2;
6166 }
6167 for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
6168 {
6169 for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ )
6170 {
6171 VectorMA( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, tip );
6172
6173 if( G_PointDistFromLineSegment( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, tip, ent->currentOrigin ) <= 32 )
6174 {
6175 willHit = qtrue;
6176 break;
6177 }
6178 }
6179 if ( willHit )
6180 {
6181 break;
6182 }
6183 }
6184 if ( !willHit )
6185 {
6186 continue;
6187 }
6188 // ok, we are within the radius, add us to the incoming list
6189 missile_list[ent_count] = ent;
6190 ent_count++;
6191
6192 }
6193
6194 if ( ent_count )
6195 {
6196 vec3_t fx_dir;
6197 // we are done, do we have any to deflect?
6198 if ( ent_count )
6199 {
6200 for ( int x = 0; x < ent_count; x++ )
6201 {
6202 if ( missile_list[x]->s.weapon == WP_SABER )
6203 {//just send it back
6204 if ( missile_list[x]->owner && missile_list[x]->owner->client && missile_list[x]->owner->client->ps.saber[0].Active() && missile_list[x]->s.pos.trType == TR_LINEAR && missile_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
6205 {//it's on and being controlled
6206 //FIXME: prevent it from damaging me?
6207 WP_SaberReturn( missile_list[x]->owner, missile_list[x] );
6208 VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
6209 WP_SaberBlockEffect( self, 0, 0, missile_list[x]->currentOrigin, fx_dir, qfalse );
6210 if ( missile_list[x]->owner->client->ps.saberInFlight && self->client->ps.saberInFlight )
6211 {
6212 WP_SaberBlockSound( self, missile_list[x]->owner, 0, 0 );
6213 //G_Sound( missile_list[x], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
6214 qboolean noFlare = qfalse;
6215 if ( (missile_list[x]->owner->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE)
6216 && (self->client->ps.saber[0].saberFlags2&SFL2_NO_CLASH_FLARE) )
6217 {
6218 noFlare = qtrue;
6219 }
6220 if ( !noFlare )
6221 {
6222 g_saberFlashTime = level.time-50;
6223 gentity_t *saber = &g_entities[self->client->ps.saberEntityNum];
6224 vec3_t org;
6225 VectorSubtract( missile_list[x]->currentOrigin, saber->currentOrigin, org );
6226 VectorMA( saber->currentOrigin, 0.5, org, org );
6227 VectorCopy( org, g_saberFlashPos );
6228 }
6229 }
6230 }
6231 }
6232 else
6233 {//bounce it
6234 vec3_t reflectAngle, forward;
6235 if ( self->client && !self->s.number )
6236 {
6237 self->client->sess.missionStats.saberBlocksCnt++;
6238 }
6239 VectorCopy( saberent->s.apos.trBase, reflectAngle );
6240 reflectAngle[PITCH] = Q_flrand( -90, 90 );
6241 AngleVectors( reflectAngle, forward, NULL, NULL );
6242
6243 G_ReflectMissile( self, missile_list[x], forward );
6244 //do an effect
6245 VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
6246 G_PlayEffect( "blaster/deflect", missile_list[x]->currentOrigin, fx_dir );
6247 }
6248 }
6249 }
6250 }
6251 }
6252
WP_SaberValidateEnemy(gentity_t * self,gentity_t * enemy)6253 qboolean WP_SaberValidateEnemy( gentity_t *self, gentity_t *enemy )
6254 {
6255 if ( !enemy )
6256 {
6257 return qfalse;
6258 }
6259
6260 if ( !enemy || enemy == self || !enemy->inuse || !enemy->client )
6261 {//not valid
6262 return qfalse;
6263 }
6264
6265 if ( enemy->health <= 0 )
6266 {//corpse
6267 return qfalse;
6268 }
6269
6270 /*
6271 if ( enemy->client->ps.weapon == WP_SABER
6272 && enemy->client->ps.SaberActive() )
6273 {//not other saber-users?
6274 return qfalse;
6275 }
6276 */
6277 if ( enemy->s.number >= MAX_CLIENTS )
6278 {//NPCs can cheat and use the homing saber throw 3 on the player
6279 if ( enemy->client->ps.forcePowersKnown )
6280 {//not other jedi?
6281 return qfalse;
6282 }
6283 }
6284
6285 if ( DistanceSquared( self->client->renderInfo.handRPoint, enemy->currentOrigin ) > saberThrowDistSquared[self->client->ps.forcePowerLevel[FP_SABERTHROW]] )
6286 {//too far
6287 return qfalse;
6288 }
6289
6290 if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
6291 && ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) )
6292 {//(not in front or not clear LOS) & greater than 256 away
6293 return qfalse;
6294 }
6295
6296 if ( enemy->client->playerTeam == self->client->playerTeam )
6297 {//on same team
6298 return qfalse;
6299 }
6300
6301 //LOS?
6302
6303 return qtrue;
6304 }
6305
WP_SaberRateEnemy(gentity_t * enemy,vec3_t center,vec3_t forward,float radius)6306 float WP_SaberRateEnemy( gentity_t *enemy, vec3_t center, vec3_t forward, float radius )
6307 {
6308 float rating;
6309 vec3_t dir;
6310
6311 VectorSubtract( enemy->currentOrigin, center, dir );
6312 rating = (1.0f-(VectorNormalize( dir )/radius));
6313 rating *= DotProduct( forward, dir );
6314 return rating;
6315 }
6316
WP_SaberFindEnemy(gentity_t * self,gentity_t * saber)6317 gentity_t *WP_SaberFindEnemy( gentity_t *self, gentity_t *saber )
6318 {
6319 //FIXME: should be a more intelligent way of doing this, like auto aim?
6320 //closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
6321 gentity_t *ent, *bestEnt = NULL;
6322 gentity_t *entityList[MAX_GENTITIES];
6323 int numListedEntities;
6324 vec3_t center, mins, maxs, fwdangles, forward;
6325 int i, e;
6326 float radius = 400;
6327 float rating, bestRating = 0.0f;
6328
6329 //FIXME: no need to do this in 1st person?
6330 fwdangles[1] = self->client->ps.viewangles[1];
6331 AngleVectors( fwdangles, forward, NULL, NULL );
6332
6333 VectorCopy( saber->currentOrigin, center );
6334
6335 for ( i = 0 ; i < 3 ; i++ )
6336 {
6337 mins[i] = center[i] - radius;
6338 maxs[i] = center[i] + radius;
6339 }
6340
6341 //if the saber has an enemy from the last time it looked, init to that one
6342 if ( WP_SaberValidateEnemy( self, saber->enemy ) )
6343 {
6344 if ( gi.inPVS( self->currentOrigin, saber->enemy->currentOrigin ) )
6345 {//potentially visible
6346 if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, saber->enemy ) )
6347 {//can see him
6348 bestEnt = saber->enemy;
6349 bestRating = WP_SaberRateEnemy( bestEnt, center, forward, radius );
6350 }
6351 }
6352 }
6353
6354 //If I have an enemy, see if that's even better
6355 if ( WP_SaberValidateEnemy( self, self->enemy ) )
6356 {
6357 float myEnemyRating = WP_SaberRateEnemy( self->enemy, center, forward, radius );
6358 if ( myEnemyRating > bestRating )
6359 {
6360 if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) )
6361 {//potentially visible
6362 if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, self->enemy ) )
6363 {//can see him
6364 bestEnt = self->enemy;
6365 bestRating = myEnemyRating;
6366 }
6367 }
6368 }
6369 }
6370
6371 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
6372
6373 if ( !numListedEntities )
6374 {//should we clear the enemy?
6375 return bestEnt;
6376 }
6377
6378 for ( e = 0 ; e < numListedEntities ; e++ )
6379 {
6380 ent = entityList[ e ];
6381
6382 if ( ent == self || ent == saber || ent == bestEnt )
6383 {
6384 continue;
6385 }
6386 if ( !WP_SaberValidateEnemy( self, ent ) )
6387 {//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
6388 continue;
6389 }
6390 if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
6391 {//not even potentially visible
6392 continue;
6393 }
6394 if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
6395 {//can't see him
6396 continue;
6397 }
6398 //rate him based on how close & how in front he is
6399 rating = WP_SaberRateEnemy( ent, center, forward, radius );
6400 if ( rating > bestRating )
6401 {
6402 bestEnt = ent;
6403 bestRating = rating;
6404 }
6405 }
6406 return bestEnt;
6407 }
6408
WP_RunSaber(gentity_t * self,gentity_t * saber)6409 void WP_RunSaber( gentity_t *self, gentity_t *saber )
6410 {
6411 vec3_t origin, oldOrg;
6412 trace_t tr;
6413
6414 VectorCopy( saber->currentOrigin, oldOrg );
6415 // get current position
6416 EvaluateTrajectory( &saber->s.pos, level.time, origin );
6417 // get current angles
6418 EvaluateTrajectory( &saber->s.apos, level.time, saber->currentAngles );
6419
6420 // trace a line from the previous position to the current position,
6421 // ignoring interactions with the missile owner
6422 int clipmask = saber->clipmask;
6423 if ( !self || !self->client || self->client->ps.SaberLength() <= 0 )
6424 {//don't keep hitting other sabers when turned off
6425 clipmask &= ~CONTENTS_LIGHTSABER;
6426 }
6427 gi.trace( &tr, saber->currentOrigin, saber->mins, saber->maxs, origin,
6428 saber->owner ? saber->owner->s.number : ENTITYNUM_NONE, clipmask, (EG2_Collision)0, 0 );
6429
6430 VectorCopy( tr.endpos, saber->currentOrigin );
6431
6432 if ( self->client->ps.SaberActive() )
6433 {
6434 if ( self->client->ps.saberInFlight || (self->client->ps.weaponTime&&!Q_irand( 0, 100 )) )
6435 {//make enemies run from a lit saber in flight or from me when I'm attacking
6436 if ( !Q_irand( 0, 10 ) )
6437 {//not so often...
6438 AddSightEvent( self, saber->currentOrigin, self->client->ps.SaberLength()*3, AEL_DANGER, 100 );
6439 }
6440 }
6441 }
6442
6443 if ( tr.startsolid )
6444 {
6445 tr.fraction = 0;
6446 }
6447
6448 gi.linkentity( saber );
6449
6450 //touch push triggers?
6451
6452 if ( tr.fraction != 1 )
6453 {
6454 WP_SaberImpact( self, saber, &tr );
6455 }
6456
6457 if ( saber->s.pos.trType == TR_LINEAR )
6458 {//home
6459 //figure out where saber should be
6460 vec3_t forward, saberHome, saberDest, fwdangles = {0};
6461
6462 VectorCopy( self->client->ps.viewangles, fwdangles );
6463 if ( self->s.number )
6464 {
6465 fwdangles[0] -= 8;
6466 }
6467 else if ( cg.renderingThirdPerson )
6468 {
6469 fwdangles[0] -= 5;
6470 }
6471
6472 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1
6473 || self->client->ps.saberEntityState == SES_RETURNING
6474 || VectorCompare( saber->s.pos.trDelta, vec3_origin ) )
6475 {//control if it's returning or just starting
6476 float saberSpeed = 500;//FIXME: based on force level?
6477 float dist;
6478 gentity_t *enemy = NULL;
6479
6480 AngleVectors( fwdangles, forward, NULL, NULL );
6481
6482 if ( self->client->ps.saberEntityDist < 100 )
6483 {//make the saber head to my hand- the bolt it was attached to
6484 VectorCopy( self->client->renderInfo.handRPoint, saberHome );
6485 }
6486 else
6487 {//aim saber from eyes
6488 VectorCopy( self->client->renderInfo.eyePoint, saberHome );
6489 }
6490 VectorMA( saberHome, self->client->ps.saberEntityDist, forward, saberDest );
6491
6492 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2
6493 && self->client->ps.saberEntityState == SES_LEAVING )
6494 {//max level
6495 if ( self->enemy &&
6496 !WP_SaberValidateEnemy( self, self->enemy ) )
6497 {//if my enemy isn't valid to auto-aim at, don't autoaim
6498 }
6499 else
6500 {
6501 //pick an enemy
6502 enemy = WP_SaberFindEnemy( self, saber );
6503 if ( enemy )
6504 {//home in on enemy
6505 float enemyDist = Distance( self->client->renderInfo.handRPoint, enemy->currentOrigin );
6506 VectorCopy( enemy->currentOrigin, saberDest );
6507 saberDest[2] += enemy->maxs[2]/2.0f;//FIXME: when in a knockdown anim, the saber float above them... do we care?
6508 self->client->ps.saberEntityDist = enemyDist;
6509 //once you pick an enemy, stay with it!
6510 saber->enemy = enemy;
6511 //FIXME: lock onto that enemy for a minimum amount of time (unless they become invalid?)
6512 }
6513 }
6514 }
6515
6516
6517 //Make the saber head there
6518 VectorSubtract( saberDest, saber->currentOrigin, saber->s.pos.trDelta );
6519 dist = VectorNormalize( saber->s.pos.trDelta );
6520 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING && !enemy )
6521 {
6522 if ( dist < 200 )
6523 {
6524 saberSpeed = 400 - (dist*2);
6525 }
6526 }
6527 else if ( self->client->ps.saberEntityState == SES_LEAVING && dist < 50 )
6528 {
6529 saberSpeed = dist * 2 + 30;
6530 if ( (enemy && dist > enemy->maxs[0]) || (!enemy && dist > 24) )
6531 {//auto-tracking an enemy and we can't hit him
6532 if ( saberSpeed < 120 )
6533 {//clamp to a minimum speed
6534 saberSpeed = 120;
6535 }
6536 }
6537 }
6538 /*
6539 if ( self->client->ps.saberEntityState == SES_RETURNING )
6540 {//FIXME: if returning, move faster?
6541 saberSpeed = 800;
6542 if ( dist < 200 )
6543 {
6544 saberSpeed -= 400 - (dist*2);
6545 }
6546 }
6547 */
6548 VectorScale( saber->s.pos.trDelta, saberSpeed, saber->s.pos.trDelta );
6549 //SnapVector( saber->s.pos.trDelta ); // save net bandwidth
6550 VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
6551 saber->s.pos.trTime = level.time;
6552 saber->s.pos.trType = TR_LINEAR;
6553 }
6554 else
6555 {
6556 VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
6557 saber->s.pos.trTime = level.time;
6558 saber->s.pos.trType = TR_LINEAR;
6559 }
6560
6561 //if it's heading back, point it's base at us
6562 if ( self->client->ps.saberEntityState == SES_RETURNING
6563 && !(self->client->ps.saber[0].saberFlags&SFL_RETURN_DAMAGE) )//type != SABER_STAR )
6564 {
6565 fwdangles[0] += SABER_PITCH_HACK;
6566 VectorCopy( fwdangles, saber->s.apos.trBase );
6567 saber->s.apos.trTime = level.time;
6568 saber->s.apos.trType = TR_INTERPOLATE;
6569 VectorClear( saber->s.apos.trDelta );
6570 }
6571 }
6572 }
6573
6574
WP_SaberLaunch(gentity_t * self,gentity_t * saber,qboolean thrown,qboolean noFail=qfalse)6575 qboolean WP_SaberLaunch( gentity_t *self, gentity_t *saber, qboolean thrown, qboolean noFail = qfalse )
6576 {//FIXME: probably need a debounce time
6577 vec3_t saberMins={-3.0f,-3.0f,-3.0f};
6578 vec3_t saberMaxs={3.0f,3.0f,3.0f};
6579 trace_t trace;
6580
6581 if ( self->client->NPC_class == CLASS_SABER_DROID )
6582 {//saber droids can't drop their saber
6583 return qfalse;
6584 }
6585 if ( !noFail )
6586 {
6587 if ( thrown )
6588 {//this is a regular throw, so see if it's legal
6589 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
6590 {
6591 if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 20 ) )
6592 {
6593 return qfalse;
6594 }
6595 }
6596 else
6597 {
6598 if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 0 ) )
6599 {
6600 return qfalse;
6601 }
6602 }
6603 }
6604 if ( !self->s.number && (cg.zoomMode || in_camera) )
6605 {//can't saber throw when zoomed in or in cinematic
6606 return qfalse;
6607 }
6608 //make sure it won't start in solid
6609 gi.trace( &trace, self->client->renderInfo.handRPoint, saberMins, saberMaxs, self->client->renderInfo.handRPoint, saber->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
6610 if ( trace.startsolid || trace.allsolid )
6611 {
6612 return qfalse;
6613 }
6614 //make sure I'm not throwing it on the other side of a door or wall or whatever
6615 gi.trace( &trace, self->currentOrigin, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
6616 if ( trace.startsolid || trace.allsolid || trace.fraction < 1.0f )
6617 {
6618 return qfalse;
6619 }
6620
6621 if ( thrown )
6622 {//this is a regular throw, so take force power
6623 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
6624 {//at max skill, the cost increases as keep it out
6625 WP_ForcePowerStart( self, FP_SABERTHROW, 10 );
6626 }
6627 else
6628 {
6629 WP_ForcePowerStart( self, FP_SABERTHROW, 0 );
6630 }
6631 }
6632 }
6633 //clear the enemy
6634 saber->enemy = NULL;
6635
6636 //===FIXME!!!==============================================================================================
6637 //We should copy the right-hand saber's g2 instance to the thrown saber
6638 //Then back again when you catch it!!!
6639 //===FIXME!!!==============================================================================================
6640
6641 //draw it
6642 saber->s.eFlags &= ~EF_NODRAW;
6643 saber->svFlags |= SVF_BROADCAST;
6644 saber->svFlags &= ~SVF_NOCLIENT;
6645
6646 //place it
6647 VectorCopy( self->client->renderInfo.handRPoint, saber->currentOrigin );//muzzlePoint
6648 VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
6649 saber->s.pos.trTime = level.time;
6650 saber->s.pos.trType = TR_LINEAR;
6651 VectorClear( saber->s.pos.trDelta );
6652 gi.linkentity( saber );
6653
6654 //spin it
6655 VectorClear( saber->s.apos.trBase );
6656 saber->s.apos.trTime = level.time;
6657 saber->s.apos.trType = TR_LINEAR;
6658 if ( self->health > 0 && thrown )
6659 {//throwing it
6660 saber->s.apos.trBase[1] = self->client->ps.viewangles[1];
6661 saber->s.apos.trBase[0] = SABER_PITCH_HACK;
6662 }
6663 else
6664 {//dropping it
6665 vectoangles( self->client->renderInfo.muzzleDir, saber->s.apos.trBase );
6666 }
6667 VectorClear( saber->s.apos.trDelta );
6668
6669 switch ( self->client->ps.forcePowerLevel[FP_SABERTHROW] )
6670 {//FIXME: make a table?
6671 default:
6672 case FORCE_LEVEL_1:
6673 saber->s.apos.trDelta[1] = 600;
6674 break;
6675 case FORCE_LEVEL_2:
6676 saber->s.apos.trDelta[1] = 800;
6677 break;
6678 case FORCE_LEVEL_3:
6679 saber->s.apos.trDelta[1] = 1200;
6680 break;
6681 }
6682
6683 //Take it out of my hand
6684 self->client->ps.saberInFlight = qtrue;
6685 self->client->ps.saberEntityState = SES_LEAVING;
6686 self->client->ps.saberEntityDist = saberThrowDist[self->client->ps.forcePowerLevel[FP_SABERTHROW]];
6687 self->client->ps.saberThrowTime = level.time;
6688 //if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
6689 {
6690 self->client->ps.forcePowerDebounce[FP_SABERTHROW] = level.time + 1000;//so we can keep it out for a minimum amount of time
6691 }
6692
6693 if ( thrown )
6694 {//this is a regular throw, so turn the saber on
6695 //turn saber on
6696 if ( (self->client->ps.saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE) )//SaberStaff() )
6697 {//only first blade can be on
6698 if ( !self->client->ps.saber[0].blade[0].active )
6699 {//turn on first one
6700 self->client->ps.SaberBladeActivate( 0, 0 );
6701 }
6702 for ( int i = 1; i < self->client->ps.saber[0].numBlades; i++ )
6703 {//turn off all others
6704 if ( self->client->ps.saber[0].blade[i].active )
6705 {
6706 self->client->ps.SaberBladeActivate( 0, i, qfalse );
6707 }
6708 }
6709 }
6710 else
6711 {//turn the sabers, all blades...?
6712 self->client->ps.saber[0].Activate();
6713 //self->client->ps.SaberActivate();
6714 }
6715 //turn on the saber trail
6716 self->client->ps.saber[0].ActivateTrail( 150 );
6717 }
6718
6719 //reset the mins
6720 VectorCopy( saberMins, saber->mins );
6721 VectorCopy( saberMaxs, saber->maxs );
6722 saber->contents = 0;//CONTENTS_LIGHTSABER;
6723 saber->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
6724
6725 // remove the ghoul2 right-hand saber model on the player
6726 if ( self->weaponModel[0] > 0 )
6727 {
6728 gi.G2API_RemoveGhoul2Model(self->ghoul2, self->weaponModel[0]);
6729 self->weaponModel[0] = -1;
6730 }
6731
6732 return qtrue;
6733 }
6734
WP_SaberLose(gentity_t * self,vec3_t throwDir)6735 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir )
6736 {
6737 if ( !self || !self->client || self->client->ps.saberEntityNum <= 0 )
6738 {//WTF?!! We lost it already?
6739 return qfalse;
6740 }
6741 if ( self->client->NPC_class == CLASS_SABER_DROID )
6742 {//saber droids can't drop their saber
6743 return qfalse;
6744 }
6745 gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
6746 if ( !self->client->ps.saberInFlight )
6747 {//not alreay in air
6748 /*
6749 qboolean noForceThrow = qfalse;
6750 //make it so we can throw it
6751 self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
6752 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 )
6753 {
6754 noForceThrow = qtrue;
6755 self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
6756 }
6757 */
6758 //throw it
6759 if ( !WP_SaberLaunch( self, dropped, qfalse ) )
6760 {//couldn't throw it
6761 return qfalse;
6762 }
6763 /*
6764 if ( noForceThrow )
6765 {
6766 self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0;
6767 }
6768 */
6769 }
6770 if ( self->client->ps.saber[0].Active() )
6771 {//on
6772 //drop it instantly
6773 WP_SaberDrop( self, dropped );
6774 }
6775 //optionally give it some thrown velocity
6776 if ( throwDir && !VectorCompare( throwDir, vec3_origin ) )
6777 {
6778 VectorCopy( throwDir, dropped->s.pos.trDelta );
6779 }
6780 //don't pull it back on the next frame
6781 if ( self->NPC )
6782 {
6783 self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
6784 }
6785 return qtrue;
6786 }
6787
WP_SetSaberOrigin(gentity_t * self,vec3_t newOrg)6788 void WP_SetSaberOrigin( gentity_t *self, vec3_t newOrg )
6789 {
6790 if ( !self || !self->client )
6791 {
6792 return;
6793 }
6794 if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
6795 {//no saber ent to reposition
6796 return;
6797 }
6798 if ( self->client->NPC_class == CLASS_SABER_DROID )
6799 {//saber droids can't drop their saber
6800 return;
6801 }
6802 gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
6803 if ( !self->client->ps.saberInFlight )
6804 {//not already in air
6805 qboolean noForceThrow = qfalse;
6806 //make it so we can throw it
6807 self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
6808 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 )
6809 {
6810 noForceThrow = qtrue;
6811 self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
6812 }
6813 //throw it
6814 if ( !WP_SaberLaunch( self, dropped, qfalse, qtrue ) )
6815 {//couldn't throw it
6816 return;
6817 }
6818 if ( noForceThrow )
6819 {
6820 self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0;
6821 }
6822 }
6823 VectorCopy( newOrg, dropped->s.origin );
6824 VectorCopy( newOrg, dropped->currentOrigin );
6825 VectorCopy( newOrg, dropped->s.pos.trBase );
6826 //drop it instantly
6827 WP_SaberDrop( self, dropped );
6828 //don't pull it back on the next frame
6829 if ( self->NPC )
6830 {
6831 self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
6832 }
6833 }
6834
WP_SaberCatch(gentity_t * self,gentity_t * saber,qboolean switchToSaber)6835 void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber )
6836 {//FIXME: probably need a debounce time
6837 if ( self->health > 0 && !PM_SaberInBrokenParry( self->client->ps.saberMove ) && self->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
6838 {
6839 //clear the enemy
6840 saber->enemy = NULL;
6841 //===FIXME!!!==============================================================================================
6842 //We should copy the thrown saber's g2 instance to the right-hand saber
6843 //When you catch it, and vice-versa when you throw it!!!
6844 //===FIXME!!!==============================================================================================
6845 //don't draw it
6846 saber->s.eFlags |= EF_NODRAW;
6847 saber->svFlags &= SVF_BROADCAST;
6848 saber->svFlags |= SVF_NOCLIENT;
6849
6850 //take off any gravity stuff if we'd dropped it
6851 saber->s.pos.trType = TR_LINEAR;
6852 saber->s.eFlags &= ~EF_BOUNCE_HALF;
6853
6854 //Put it in my hand
6855 self->client->ps.saberInFlight = qfalse;
6856 self->client->ps.saberEntityState = SES_LEAVING;
6857
6858 //turn off the saber trail
6859 self->client->ps.saber[0].DeactivateTrail( 75 );
6860
6861 //reset its contents/clipmask
6862 saber->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
6863 saber->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
6864
6865 //play catch sound
6866 G_Sound( saber, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
6867 //FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead...
6868 //if it's not our current weapon, make it our current weapon
6869 if ( self->client->ps.weapon == WP_SABER )
6870 {//only do the first saber since we only throw the first one
6871 WP_SaberAddG2SaberModels( self, qfalse );
6872 }
6873 if ( switchToSaber )
6874 {
6875 if ( self->client->ps.weapon != WP_SABER )
6876 {
6877 CG_ChangeWeapon( WP_SABER );
6878 }
6879 else
6880 {//if it's not active, turn it on
6881 if ( (self->client->ps.saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE) )//SaberStaff() )
6882 {//only first blade can be on
6883 if ( !self->client->ps.saber[0].blade[0].active )
6884 {//only turn it on if first blade is off, otherwise, leave as-is
6885 self->client->ps.saber[0].Activate();
6886 }
6887 }
6888 else
6889 {//turn all blades on
6890 self->client->ps.saber[0].Activate();
6891 }
6892 }
6893 }
6894 }
6895 }
6896
6897
WP_SaberReturn(gentity_t * self,gentity_t * saber)6898 void WP_SaberReturn( gentity_t *self, gentity_t *saber )
6899 {
6900 if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
6901 {
6902 return;
6903 }
6904 if ( self && self->client )
6905 {//still alive and stuff
6906 //FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm...
6907 self->client->ps.saberEntityState = SES_RETURNING;
6908 //turn down the saber trail
6909 if ( !(self->client->ps.saber[0].saberFlags&SFL_RETURN_DAMAGE) )//type != SABER_STAR )
6910 {
6911 self->client->ps.saber[0].DeactivateTrail( 75 );
6912 }
6913 }
6914 if ( !(saber->s.eFlags&EF_BOUNCE) )
6915 {
6916 saber->s.eFlags |= EF_BOUNCE;
6917 saber->bounceCount = 300;
6918 }
6919 }
6920
6921
WP_SaberDrop(gentity_t * self,gentity_t * saber)6922 void WP_SaberDrop( gentity_t *self, gentity_t *saber )
6923 {
6924 //clear the enemy
6925 saber->enemy = NULL;
6926 saber->s.eFlags &= ~EF_BOUNCE;
6927 saber->bounceCount = 0;
6928 //make it fall
6929 saber->s.pos.trType = TR_GRAVITY;
6930 //make it bounce some
6931 saber->s.eFlags |= EF_BOUNCE_HALF;
6932 //make it spin
6933 VectorCopy( saber->currentAngles, saber->s.apos.trBase );
6934 saber->s.apos.trType = TR_LINEAR;
6935 saber->s.apos.trTime = level.time;
6936 VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), saber->s.apos.trDelta[1], Q_irand( -300, 300 ) );
6937 if ( !saber->s.apos.trDelta[1] )
6938 {
6939 saber->s.apos.trDelta[1] = Q_irand( -300, 300 );
6940 }
6941 //force it to be ready to return
6942 self->client->ps.saberEntityDist = 0;
6943 self->client->ps.saberEntityState = SES_RETURNING;
6944 //turn it off
6945 self->client->ps.saber[0].Deactivate();
6946 //turn off the saber trail
6947 self->client->ps.saber[0].DeactivateTrail( 75 );
6948 //play the saber turning off sound
6949 G_SoundIndexOnEnt( saber, CHAN_AUTO, self->client->ps.saber[0].soundOff );
6950
6951 if ( self->health <= 0 )
6952 {//owner is dead!
6953 saber->s.time = level.time;//will make us free ourselves after a time
6954 }
6955 }
6956
6957
WP_SaberPull(gentity_t * self,gentity_t * saber)6958 void WP_SaberPull( gentity_t *self, gentity_t *saber )
6959 {
6960 if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
6961 {
6962 return;
6963 }
6964 if ( self->health > 0 )
6965 {
6966 //take off gravity
6967 saber->s.pos.trType = TR_LINEAR;
6968 //take off bounce
6969 saber->s.eFlags &= EF_BOUNCE_HALF;
6970 //play sound
6971 G_Sound( self, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
6972 }
6973 }
6974
6975 const char *saberColorStringForColor[SABER_PURPLE+1] =
6976 {
6977 "red",//SABER_RED
6978 "orange",//SABER_ORANGE
6979 "yellow",//SABER_YELLOW
6980 "green",//SABER_GREEN
6981 "blue",//SABER_BLUE
6982 "purple"//SABER_PURPLE
6983 };
6984
6985 // Check if we are throwing it, launch it if needed, update position if needed.
WP_SaberThrow(gentity_t * self,usercmd_t * ucmd)6986 void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd )
6987 {
6988 vec3_t saberDiff;
6989 trace_t tr;
6990 //static float SABER_SPEED = 10;
6991
6992 gentity_t *saberent;
6993
6994 if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
6995 {//WTF?!! We lost it?
6996 return;
6997 }
6998
6999 if ( self->client->ps.torsoAnim == BOTH_LOSE_SABER )
7000 {//can't catch it while it's being yanked from your hand!
7001 return;
7002 }
7003
7004 if ( !g_saberNewControlScheme->integer )
7005 {
7006 if ( PM_SaberInKata( (saberMoveName_t)self->client->ps.saberMove ) )
7007 {//don't throw saber when in special attack (alt+attack)
7008 return;
7009 }
7010 if ( (ucmd->buttons&BUTTON_ATTACK)
7011 && (ucmd->buttons&BUTTON_ALT_ATTACK)
7012 && !self->client->ps.saberInFlight )
7013 {//trying to do special attack, don't throw it
7014 return;
7015 }
7016 else if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL
7017 || self->client->ps.torsoAnim == BOTH_A2_SPECIAL
7018 || self->client->ps.torsoAnim == BOTH_A3_SPECIAL )
7019 {//don't throw in these anims!
7020 return;
7021 }
7022 }
7023 saberent = &g_entities[self->client->ps.saberEntityNum];
7024
7025 VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
7026
7027 //is our saber in flight?
7028 if ( !self->client->ps.saberInFlight )
7029 {//saber is not in flight right now
7030 if ( self->client->ps.weapon != WP_SABER )
7031 {//don't even have it out
7032 return;
7033 }
7034 else if ( (ucmd->buttons&BUTTON_ALT_ATTACK) && !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
7035 {//still holding it, not still holding attack from a previous throw, so throw it.
7036 if ( !(self->client->ps.saberEventFlags&SEF_INWATER) && WP_SaberLaunch( self, saberent, qtrue ) )
7037 {
7038 if ( self->client && !self->s.number )
7039 {
7040 self->client->sess.missionStats.saberThrownCnt++;
7041 }
7042 //need to recalc this because we just moved it
7043 VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
7044 }
7045 else
7046 {//couldn't throw it
7047 return;
7048 }
7049 }
7050 else
7051 {//holding it, don't want to throw it, go away.
7052 return;
7053 }
7054 }
7055 else
7056 {//inflight
7057 //is our saber currently on it's way back to us?
7058 if ( self->client->ps.saberEntityState == SES_RETURNING )
7059 {//see if we're close enough to pick it up
7060 if ( VectorLengthSquared( saberDiff ) <= 256 )//16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )//
7061 {//caught it
7062 vec3_t axisPoint;
7063 trace_t trace;
7064 VectorCopy( self->currentOrigin, axisPoint );
7065 axisPoint[2] = self->client->renderInfo.handRPoint[2];
7066 gi.trace( &trace, axisPoint, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
7067 if ( !trace.startsolid && trace.fraction >= 1.0f )
7068 {//our hand isn't through a wall
7069 WP_SaberCatch( self, saberent, qtrue );
7070 //NPC_SetAnim( self, SETANIM_TORSO, TORSO_HANDRETRACT1, SETANIM_FLAG_OVERRIDE );
7071 }
7072 return;
7073 }
7074 }
7075
7076 if ( saberent->s.pos.trType != TR_STATIONARY )
7077 {//saber is in flight, lerp it
7078 if ( self->health <= 0 )//&& level.time > saberent->s.time + 5000 )
7079 {//make us free ourselves after a time
7080 if ( g_saberPickuppableDroppedSabers->integer
7081 && G_DropSaberItem( self->client->ps.saber[0].name, self->client->ps.saber[0].blade[0].color, saberent->currentOrigin, saberent->s.pos.trDelta, saberent->currentAngles ) != NULL )
7082 {//dropped it
7083 //free it
7084 G_FreeEntity( saberent );
7085 //forget it
7086 self->client->ps.saberEntityNum = ENTITYNUM_NONE;
7087 return;
7088 }
7089 }
7090 WP_RunSaber( self, saberent );
7091 }
7092 else
7093 {//it fell on the ground
7094 if ( self->health <= 0 )//&& level.time > saberent->s.time + 5000 )
7095 {//make us free ourselves after a time
7096 if ( g_saberPickuppableDroppedSabers->integer )
7097 {//spawn an item
7098 G_DropSaberItem( self->client->ps.saber[0].name, self->client->ps.saber[0].blade[0].color, saberent->currentOrigin, saberent->s.pos.trDelta, saberent->currentAngles );
7099 }
7100 //free it
7101 G_FreeEntity( saberent );
7102 //forget it
7103 self->client->ps.saberEntityNum = ENTITYNUM_NONE;
7104 return;
7105 }
7106 if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000)
7107 || (self->s.number && level.time - saberent->aimDebounceTime > 5000) )
7108 {//(only for player) been missing for 15 seconds, automagicially return
7109 WP_SaberCatch( self, saberent, qfalse );
7110 return;
7111 }
7112 }
7113 }
7114
7115 //are we still trying to use the saber?
7116 if ( self->client->ps.weapon != WP_SABER )
7117 {//switched away
7118 if ( !self->client->ps.saberInFlight )
7119 {//wasn't throwing saber
7120 return;
7121 }
7122 else if ( saberent->s.pos.trType == TR_LINEAR )
7123 {//switched away while controlling it, just drop the saber
7124 WP_SaberDrop( self, saberent );
7125 return;
7126 }
7127 else
7128 {//it's on the ground, see if it's inside us (touching)
7129 if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
7130 {//it's in us, pick it up automatically
7131 WP_SaberPull( self, saberent );
7132 }
7133 }
7134 }
7135 else if ( saberent->s.pos.trType != TR_LINEAR )
7136 {//weapon is saber and not flying
7137 if ( self->client->ps.saberInFlight )
7138 {//we dropped it
7139 if ( ucmd->buttons & BUTTON_ATTACK )//|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK ||
7140 {//we actively want to pick it up or we just switched to it, so pull it back
7141 gi.trace( &tr, saberent->currentOrigin, saberent->mins, saberent->maxs, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, (EG2_Collision)0, 0 );
7142
7143 if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0f )
7144 {//can't pick it up yet, no LOS
7145 return;
7146 }
7147 //clear LOS, pick it up
7148 WP_SaberPull( self, saberent );
7149 }
7150 else
7151 {//see if it's inside us (touching)
7152 if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
7153 {//it's in us, pick it up automatically
7154 WP_SaberPull( self, saberent );
7155 }
7156 }
7157 }
7158 }
7159 else if ( self->health <= 0 && self->client->ps.saberInFlight )
7160 {//we died, drop it
7161 WP_SaberDrop( self, saberent );
7162 return;
7163 }
7164 else if ( !self->client->ps.saber[0].Active() && self->client->ps.saberEntityState != SES_RETURNING )
7165 {//we turned it off, drop it
7166 WP_SaberDrop( self, saberent );
7167 return;
7168 }
7169
7170 //TODO: if deactivate saber in flight, should it drop?
7171
7172 if ( saberent->s.pos.trType != TR_LINEAR )
7173 {//don't home
7174 return;
7175 }
7176
7177 float saberDist = VectorLength( saberDiff );
7178 if ( self->client->ps.saberEntityState == SES_LEAVING )
7179 {//saber still flying forward
7180 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
7181 {//still holding it out
7182 if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
7183 {//done throwing, return to me
7184 if ( self->client->ps.saber[0].Active() )
7185 {//still on
7186 WP_SaberReturn( self, saberent );
7187 }
7188 }
7189 else if ( level.time - self->client->ps.saberThrowTime >= 100 )
7190 {
7191 if ( WP_ForcePowerAvailable( self, FP_SABERTHROW, 1 ) )
7192 {
7193 WP_ForcePowerDrain( self, FP_SABERTHROW, 1 );
7194 self->client->ps.saberThrowTime = level.time;
7195 }
7196 else
7197 {//out of force power, return to me
7198 WP_SaberReturn( self, saberent );
7199 }
7200 }
7201 }
7202 else
7203 {
7204 if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
7205 {//not holding button and has been out at least 1 second, return to me
7206 if ( self->client->ps.saber[0].Active() )
7207 {//still on
7208 WP_SaberReturn( self, saberent );
7209 }
7210 }
7211 else if ( level.time - self->client->ps.saberThrowTime > 3000
7212 || (self->client->ps.forcePowerLevel[FP_SABERTHROW]==FORCE_LEVEL_1&&saberDist>=self->client->ps.saberEntityDist) )
7213 {//been out too long, or saber throw 1 went too far, return to me
7214 if ( self->client->ps.saber[0].Active() )
7215 {//still on
7216 WP_SaberReturn( self, saberent );
7217 }
7218 }
7219 }
7220 }
7221 if ( self->client->ps.saberEntityState == SES_RETURNING )
7222 {
7223 if ( self->client->ps.saberEntityDist > 0 )
7224 {
7225 self->client->ps.saberEntityDist -= 25;
7226 }
7227 if ( self->client->ps.saberEntityDist < 0 )
7228 {
7229 self->client->ps.saberEntityDist = 0;
7230 }
7231 else if ( saberDist < self->client->ps.saberEntityDist )
7232 {//if it's coming back to me, never push it away
7233 self->client->ps.saberEntityDist = saberDist;
7234 }
7235 }
7236 }
7237
7238
7239 //SABER BLOCKING============================================================================
7240 //SABER BLOCKING============================================================================
7241 //SABER BLOCKING============================================================================
7242 //SABER BLOCKING============================================================================
7243 //SABER BLOCKING============================================================================
WP_MissileBlockForBlock(int saberBlock)7244 int WP_MissileBlockForBlock( int saberBlock )
7245 {
7246 switch( saberBlock )
7247 {
7248 case BLOCKED_UPPER_RIGHT:
7249 return BLOCKED_UPPER_RIGHT_PROJ;
7250 break;
7251 case BLOCKED_UPPER_LEFT:
7252 return BLOCKED_UPPER_LEFT_PROJ;
7253 break;
7254 case BLOCKED_LOWER_RIGHT:
7255 return BLOCKED_LOWER_RIGHT_PROJ;
7256 break;
7257 case BLOCKED_LOWER_LEFT:
7258 return BLOCKED_LOWER_LEFT_PROJ;
7259 break;
7260 case BLOCKED_TOP:
7261 return BLOCKED_TOP_PROJ;
7262 break;
7263 }
7264 return saberBlock;
7265 }
7266
WP_SaberBlockNonRandom(gentity_t * self,vec3_t hitloc,qboolean missileBlock)7267 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock )
7268 {
7269 vec3_t diff, fwdangles={0,0,0}, right;
7270 float rightdot;
7271 float zdiff;
7272
7273 if ( self->client->ps.weaponstate == WEAPON_DROPPING ||
7274 self->client->ps.weaponstate == WEAPON_RAISING )
7275 {//don't block while changing weapons
7276 return;
7277 }
7278 if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
7279 || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
7280 {
7281 return;
7282 }
7283 //NPCs don't auto-block
7284 if ( !missileBlock && self->s.number != 0 && self->client->ps.saberBlocked != BLOCKED_NONE )
7285 {
7286 return;
7287 }
7288
7289 VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
7290 diff[2] = 0;
7291 VectorNormalize( diff );
7292
7293 fwdangles[1] = self->client->ps.viewangles[1];
7294 // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
7295 AngleVectors( fwdangles, NULL, right, NULL );
7296
7297 rightdot = DotProduct(right, diff);
7298 zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];
7299
7300 //FIXME: take torsoAngles into account?
7301 if ( zdiff > -5 )//0 )//40 )
7302 {
7303 if ( rightdot > 0.3 )
7304 {
7305 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
7306 }
7307 else if ( rightdot < -0.3 )
7308 {
7309 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
7310 }
7311 else
7312 {
7313 self->client->ps.saberBlocked = BLOCKED_TOP;
7314 }
7315 }
7316 else if ( zdiff > -22 )//-20 )//20 )
7317 {
7318 if ( zdiff < -10 )//30 )
7319 {//hmm, pretty low, but not low enough to use the low block, so we need to duck
7320 //NPC should duck, but NPC should never get here
7321 }
7322 if ( rightdot > 0.1 )
7323 {
7324 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
7325 }
7326 else if ( rightdot < -0.1 )
7327 {
7328 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
7329 }
7330 else
7331 {//FIXME: this looks really weird if the shot is too low!
7332 self->client->ps.saberBlocked = BLOCKED_TOP;
7333 }
7334 }
7335 else
7336 {
7337 if ( rightdot >= 0 )
7338 {
7339 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
7340 }
7341 else
7342 {
7343 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
7344 }
7345 }
7346
7347 #ifndef FINAL_BUILD
7348 if ( d_saberCombat->integer )
7349 {
7350 if ( !self->s.number )
7351 {
7352 gi.Printf( "EyeZ: %4.2f HitZ: %4.2f zdiff: %4.2f rdot: %4.2f\n", self->client->renderInfo.eyePoint[2], hitloc[2], zdiff, rightdot );
7353 switch ( self->client->ps.saberBlocked )
7354 {
7355 case BLOCKED_TOP:
7356 gi.Printf( "BLOCKED_TOP\n" );
7357 break;
7358 case BLOCKED_UPPER_RIGHT:
7359 gi.Printf( "BLOCKED_UPPER_RIGHT\n" );
7360 break;
7361 case BLOCKED_UPPER_LEFT:
7362 gi.Printf( "BLOCKED_UPPER_LEFT\n" );
7363 break;
7364 case BLOCKED_LOWER_RIGHT:
7365 gi.Printf( "BLOCKED_LOWER_RIGHT\n" );
7366 break;
7367 case BLOCKED_LOWER_LEFT:
7368 gi.Printf( "BLOCKED_LOWER_LEFT\n" );
7369 break;
7370 default:
7371 break;
7372 }
7373 }
7374 }
7375 #endif
7376
7377 if ( missileBlock )
7378 {
7379 self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
7380 }
7381
7382 if ( self->client->ps.saberBlocked != BLOCKED_NONE )
7383 {
7384 int parryReCalcTime = Jedi_ReCalcParryTime( self, EVASION_PARRY );
7385 if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
7386 {
7387 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
7388 }
7389 }
7390 }
7391
WP_SaberStartMissileBlockCheck(gentity_t * self,usercmd_t * ucmd)7392 void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd )
7393 {
7394 float dist;
7395 gentity_t *ent, *incoming = NULL;
7396 gentity_t *entityList[MAX_GENTITIES];
7397 int numListedEntities;
7398 vec3_t mins, maxs;
7399 int i, e;
7400 float closestDist, radius = 256;
7401 vec3_t forward, dir, missile_dir, fwdangles = {0};
7402 trace_t trace;
7403 vec3_t traceTo, entDir;
7404 qboolean dodgeOnlySabers = qfalse;
7405
7406
7407 if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
7408 {//don't react to things flying at me...
7409 return;
7410 }
7411 if ( self->health <= 0 )
7412 {//dead don't try to block (NOTE: actual deflection happens in missile code)
7413 return;
7414 }
7415
7416 if ( PM_InKnockDown( &self->client->ps ) )
7417 {//can't block when knocked down
7418 return;
7419 }
7420
7421 if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
7422 || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
7423 {//can't block while in break anim
7424 return;
7425 }
7426
7427 if ( Rosh_BeingHealed( self ) )
7428 {
7429 return;
7430 }
7431
7432 if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
7433 {//rockettrooper
7434 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
7435 {//must be in air
7436 return;
7437 }
7438 if ( Q_irand( 0, 4-(g_spskill->integer*2) ) )
7439 {//easier level guys do this less
7440 return;
7441 }
7442 if ( Q_irand( 0, 3 ) )
7443 {//base level: 25% chance of looking for something to dodge
7444 if ( Q_irand( 0, 1 ) )
7445 {//dodge sabers twice as frequently as other projectiles
7446 dodgeOnlySabers = qtrue;
7447 }
7448 else
7449 {
7450 return;
7451 }
7452 }
7453 }
7454
7455 if ( self->client->NPC_class == CLASS_BOBAFETT )
7456 {//Boba doesn't dodge quite as much
7457 if ( Q_irand( 0, 2-g_spskill->integer) )
7458 {//easier level guys do this less
7459 return;
7460 }
7461 }
7462
7463 if ( self->client->NPC_class != CLASS_BOBAFETT
7464 && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER)
7465 && (self->client->NPC_class != CLASS_ROCKETTROOPER||!self->NPC||self->NPC->rank<RANK_LT)//if a rockettrooper, but not an officer, do these normal checks
7466 )
7467 {
7468 if ( g_debugMelee->integer
7469 && (ucmd->buttons & BUTTON_USE)
7470 && cg.renderingThirdPerson
7471 && G_OkayToLean( &self->client->ps, ucmd, qfalse )
7472 && (self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
7473 {
7474 }
7475 else
7476 {
7477 if ( self->client->ps.weapon != WP_SABER )
7478 {
7479 return;
7480 }
7481
7482 if ( self->client->ps.saberInFlight )
7483 {
7484 return;
7485 }
7486
7487 if ( self->s.number < MAX_CLIENTS )
7488 {
7489 if ( !self->client->ps.SaberLength() )
7490 {//player doesn't auto-activate
7491 return;
7492 }
7493
7494 if ( !g_saberAutoBlocking->integer && self->client->ps.saberBlockingTime<level.time )
7495 {
7496 return;
7497 }
7498 }
7499
7500 if ( (self->client->ps.saber[0].saberFlags&SFL_NOT_ACTIVE_BLOCKING) )
7501 {//can't actively block with this saber type
7502 return;
7503 }
7504 }
7505
7506 if ( !self->s.number )
7507 {//don't do this if already attacking!
7508 if ( ucmd->buttons & BUTTON_ATTACK
7509 || PM_SaberInAttack( self->client->ps.saberMove )
7510 || PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
7511 || PM_SaberInTransitionAny( self->client->ps.saberMove ))
7512 {
7513 return;
7514 }
7515 }
7516
7517 if ( self->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1 )
7518 {//you have not the SKILLZ
7519 return;
7520 }
7521
7522 if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
7523 {//can't block while already blocking
7524 return;
7525 }
7526
7527 if ( self->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
7528 {//can't block while zapping
7529 return;
7530 }
7531
7532 if ( self->client->ps.forcePowersActive&(1<<FP_DRAIN) )
7533 {//can't block while draining
7534 return;
7535 }
7536
7537 if ( self->client->ps.forcePowersActive&(1<<FP_PUSH) )
7538 {//can't block while shoving
7539 return;
7540 }
7541
7542 if ( self->client->ps.forcePowersActive&(1<<FP_GRIP) )
7543 {//can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
7544 return;
7545 }
7546 }
7547
7548 fwdangles[1] = self->client->ps.viewangles[1];
7549 AngleVectors( fwdangles, forward, NULL, NULL );
7550
7551 for ( i = 0 ; i < 3 ; i++ )
7552 {
7553 mins[i] = self->currentOrigin[i] - radius;
7554 maxs[i] = self->currentOrigin[i] + radius;
7555 }
7556
7557 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
7558
7559 closestDist = radius;
7560
7561 for ( e = 0 ; e < numListedEntities ; e++ )
7562 {
7563 ent = entityList[ e ];
7564
7565 if (ent == self)
7566 continue;
7567 if (ent->owner == self)
7568 continue;
7569 if ( !(ent->inuse) )
7570 continue;
7571 if ( dodgeOnlySabers )
7572 {//only care about thrown sabers
7573 if ( ent->client
7574 || ent->s.weapon != WP_SABER
7575 || !ent->classname
7576 || !ent->classname[0]
7577 || Q_stricmp( "lightsaber", ent->classname ) )
7578 {//not a lightsaber, ignore it
7579 continue;
7580 }
7581 }
7582 if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
7583 {//not a normal projectile
7584 if ( ent->client || ent->s.weapon != WP_SABER )
7585 {//FIXME: wake up bad guys?
7586 continue;
7587 }
7588 if ( ent->s.eFlags & EF_NODRAW )
7589 {
7590 continue;
7591 }
7592 if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
7593 {//not a lightsaber
7594 //FIXME: what about general objects that are small in size- like rocks, etc...
7595 continue;
7596 }
7597 //a lightsaber.. make sure it's on and inFlight
7598 if ( !ent->owner || !ent->owner->client )
7599 {
7600 continue;
7601 }
7602 if ( !ent->owner->client->ps.saberInFlight )
7603 {//not in flight
7604 continue;
7605 }
7606 if ( ent->owner->client->ps.SaberLength() <= 0 )
7607 {//not on
7608 continue;
7609 }
7610 if ( ent->owner->health <= 0 && g_saberRealisticCombat->integer < 2 )
7611 {//it's not doing damage, so ignore it
7612 continue;
7613 }
7614 }
7615 else
7616 {
7617 if ( ent->s.pos.trType == TR_STATIONARY && !self->s.number )
7618 {//nothing you can do with a stationary missile if you're the player
7619 continue;
7620 }
7621 }
7622
7623 float dot1, dot2;
7624 //see if they're in front of me
7625 VectorSubtract( ent->currentOrigin, self->currentOrigin, dir );
7626 dist = VectorNormalize( dir );
7627 //FIXME: handle detpacks, proximity mines and tripmines
7628 if ( ent->s.weapon == WP_THERMAL )
7629 {//thermal detonator!
7630 if ( self->NPC && dist < ent->splashRadius )
7631 {
7632 if ( dist < ent->splashRadius &&
7633 ent->nextthink < level.time + 600 &&
7634 ent->count &&
7635 self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
7636 (ent->s.pos.trType == TR_STATIONARY||
7637 ent->s.pos.trType == TR_INTERPOLATE||
7638 (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
7639 !WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
7640 {//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!
7641 //FIXME: sometimes this might make me just jump into it...?
7642 self->client->ps.forceJumpCharge = 480;
7643 }
7644 else if ( self->client->NPC_class != CLASS_BOBAFETT
7645 && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER)
7646 && self->client->NPC_class != CLASS_ROCKETTROOPER )
7647 {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
7648 if ( !ent->owner || !OnSameTeam( self, ent->owner ) )
7649 {
7650 ForceThrow( self, qfalse );
7651 }
7652 }
7653 }
7654 continue;
7655 }
7656 else if ( ent->splashDamage && ent->splashRadius )
7657 {//exploding missile
7658 //FIXME: handle tripmines and detpacks somehow...
7659 // maybe do a force-gesture that makes them explode?
7660 // But what if we're within it's splashradius?
7661 if ( !self->s.number )
7662 {//players don't auto-handle these at all
7663 continue;
7664 }
7665 else
7666 {
7667 if ( self->client->NPC_class == CLASS_BOBAFETT
7668 || self->client->NPC_class == CLASS_ROCKETTROOPER )
7669 {
7670 /*
7671 if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
7672 {//sorry, you're scrooged here
7673 //FIXME: maybe jump or go up if on ground?
7674 continue;
7675 }
7676 //else it's a rocket, try to evade it
7677 */
7678 //HMM... let's see what happens if these guys try to avoid tripmines and detpacks, too...?
7679 }
7680 else
7681 {//normal Jedi
7682 if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK)
7683 && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
7684 {//a placed explosive like a tripmine or detpack
7685 if ( InFOV( ent->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
7686 {//in front of me
7687 if ( G_ClearLOS( self, ent ) )
7688 {//can see it
7689 vec3_t throwDir;
7690 //make the gesture
7691 ForceThrow( self, qfalse );
7692 //take it off the wall and toss it
7693 ent->s.pos.trType = TR_GRAVITY;
7694 ent->s.eType = ET_MISSILE;
7695 ent->s.eFlags &= ~EF_MISSILE_STICK;
7696 ent->s.eFlags |= EF_BOUNCE_HALF;
7697 AngleVectors( ent->currentAngles, throwDir, NULL, NULL );
7698 VectorMA( ent->currentOrigin, ent->maxs[0]+4, throwDir, ent->currentOrigin );
7699 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
7700 VectorScale( throwDir, 300, ent->s.pos.trDelta );
7701 ent->s.pos.trDelta[2] += 150;
7702 VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
7703 ent->s.pos.trTime = level.time; // move a bit on the very first frame
7704 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
7705 ent->owner = self;
7706 // make it explode, but with less damage
7707 ent->splashDamage /= 3;
7708 ent->splashRadius /= 3;
7709 ent->e_ThinkFunc = thinkF_WP_Explode;
7710 ent->nextthink = level.time + Q_irand( 500, 3000 );
7711 }
7712 }
7713 }
7714 else if ( dist < ent->splashRadius
7715 && self->client->ps.groundEntityNum != ENTITYNUM_NONE
7716 && ( DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE
7717 || !WP_ForcePowerUsable( self, FP_PUSH, 0 ) ) )
7718 {//NPCs try to evade it
7719 self->client->ps.forceJumpCharge = 480;
7720 }
7721 else if ( (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
7722 {//else, try to force-throw it away
7723 if ( !ent->owner || !OnSameTeam( self, ent->owner ) )
7724 {
7725 //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
7726 ForceThrow( self, qfalse );
7727 }
7728 }
7729 //otherwise, can't block it, so we're screwed
7730 continue;
7731 }
7732 }
7733 }
7734
7735 if ( ent->s.weapon != WP_SABER )
7736 {//only block shots coming from behind
7737 if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
7738 continue;
7739 }
7740 else if ( !self->s.number )
7741 {//player never auto-blocks thrown sabers
7742 continue;
7743 }//NPCs always try to block sabers coming from behind!
7744
7745 //see if they're heading towards me
7746 VectorCopy( ent->s.pos.trDelta, missile_dir );
7747 VectorNormalize( missile_dir );
7748 if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
7749 continue;
7750
7751 //FIXME: must have a clear trace to me, too...
7752 if ( dist < closestDist )
7753 {
7754 VectorCopy( self->currentOrigin, traceTo );
7755 traceTo[2] = self->absmax[2] - 4;
7756 gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, (EG2_Collision)0, 0 );
7757 if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
7758 {//okay, try one more check
7759 VectorNormalize2( ent->s.pos.trDelta, entDir );
7760 VectorMA( ent->currentOrigin, radius, entDir, traceTo );
7761 gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, (EG2_Collision)0, 0 );
7762 if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
7763 {//can't hit me, ignore it
7764 continue;
7765 }
7766 }
7767 if ( self->s.number != 0 )
7768 {//An NPC
7769 if ( self->NPC && !self->enemy && ent->owner )
7770 {
7771 if ( ent->owner->health >= 0 && (!ent->owner->client || ent->owner->client->playerTeam != self->client->playerTeam) )
7772 {
7773 G_SetEnemy( self, ent->owner );
7774 }
7775 }
7776 }
7777 //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?
7778 closestDist = dist;
7779 incoming = ent;
7780 }
7781 }
7782
7783 if ( incoming )
7784 {
7785 if ( self->NPC && !G_ControlledByPlayer( self ) )
7786 {
7787 if ( Jedi_WaitingAmbush( self ) )
7788 {
7789 Jedi_Ambush( self );
7790 }
7791 if ( ( self->client->NPC_class == CLASS_BOBAFETT || self->client->NPC_class == CLASS_ROCKETTROOPER )
7792 && self->client->moveType == MT_FLYSWIM
7793 && incoming->methodOfDeath != MOD_ROCKET_ALT )
7794 {//a hovering Boba Fett, not a tracking rocket
7795 if ( !Q_irand( 0, 1 ) )
7796 {//strafe
7797 self->NPC->standTime = 0;
7798 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
7799 }
7800 if ( !Q_irand( 0, 1 ) )
7801 {//go up/down
7802 TIMER_Set( self, "heightChange", Q_irand( 1000, 3000 ) );
7803 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 );
7804 }
7805 }
7806 else if ( self->client->NPC_class != CLASS_ROCKETTROOPER
7807 && Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming ) != EVASION_NONE )
7808 {//make sure to turn on your saber if it's not on
7809 if ( self->client->NPC_class != CLASS_BOBAFETT
7810 && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) )
7811 {
7812 self->client->ps.SaberActivate();
7813 }
7814 }
7815 }
7816 else//player
7817 {
7818 if ( !(ucmd->buttons & BUTTON_USE) )//self->s.weapon == WP_SABER && self->client->ps.SaberActive() )
7819 {
7820 WP_SaberBlockNonRandom( self, incoming->currentOrigin, qtrue );
7821 }
7822 else
7823 {
7824 vec3_t diff, start, end;
7825 float dist;
7826 VectorSubtract( incoming->currentOrigin, self->currentOrigin, diff );
7827 dist = VectorLength( diff );
7828 VectorNormalize2( incoming->s.pos.trDelta, entDir );
7829 VectorMA( incoming->currentOrigin, dist, entDir, start );
7830 VectorCopy( self->currentOrigin, end );
7831 end[2] += self->maxs[2]*0.75f;
7832 gi.trace( &trace, start, incoming->mins, incoming->maxs, end, incoming->s.number, MASK_SHOT, G2_COLLIDE, 10 );
7833
7834 Jedi_DodgeEvasion( self, incoming->owner, &trace, HL_NONE );
7835 }
7836 if ( incoming->owner && incoming->owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
7837 {
7838 self->enemy = incoming->owner;
7839 NPC_SetLookTarget( self, incoming->owner->s.number, level.time+1000 );
7840 }
7841 }
7842 }
7843 }
7844
7845
7846 //GENERAL SABER============================================================================
7847 //GENERAL SABER============================================================================
7848 //GENERAL SABER============================================================================
7849 //GENERAL SABER============================================================================
7850 //GENERAL SABER============================================================================
7851
WP_SetSaberMove(gentity_t * self,short blocked)7852 void WP_SetSaberMove(gentity_t *self, short blocked)
7853 {
7854 self->client->ps.saberBlocked = blocked;
7855 }
7856
7857 extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha );
WP_SaberUpdate(gentity_t * self,usercmd_t * ucmd)7858 void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd )
7859 {
7860 //float swap;
7861 float minsize = 16;
7862
7863 if(0)// if ( self->s.number != 0 )
7864 {//for now only the player can do this // not anymore
7865 return;
7866 }
7867
7868 if ( !self->client )
7869 {
7870 return;
7871 }
7872
7873 if ( self->client->ps.saberEntityNum < 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
7874 {//never got one
7875 return;
7876 }
7877
7878 // Check if we are throwing it, launch it if needed, update position if needed.
7879 WP_SaberThrow(self, ucmd);
7880
7881
7882 //vec3_t saberloc;
7883 //vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8};
7884
7885 gentity_t *saberent;
7886
7887 if (self->client->ps.saberEntityNum <= 0)
7888 {//WTF?!! We lost it?
7889 return;
7890 }
7891
7892 saberent = &g_entities[self->client->ps.saberEntityNum];
7893
7894 //FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller
7895 if ( self->client->ps.saberBlocked != BLOCKED_NONE )
7896 {//we're blocking, increase min size
7897 minsize = 32;
7898 }
7899
7900 if ( G_InCinematicSaberAnim( self ) )
7901 {//fake some blocking
7902 self->client->ps.saberBlocking = BLK_TIGHT;
7903 if ( self->client->ps.saber[0].Active() )
7904 {
7905 self->client->ps.saber[0].ActivateTrail( 150 );
7906 }
7907 if ( self->client->ps.saber[1].Active() )
7908 {
7909 self->client->ps.saber[1].ActivateTrail( 150 );
7910 }
7911 }
7912
7913 //is our saber in flight?
7914 if ( !self->client->ps.saberInFlight )
7915 { // It isn't, which means we can update its position as we will.
7916 qboolean alwaysBlock[MAX_SABERS][MAX_BLADES];
7917 qboolean forceBlock = qfalse;
7918 qboolean noBlocking = qfalse;
7919
7920 //clear out last frame's numbers
7921 VectorClear(saberent->mins);
7922 VectorClear(saberent->maxs);
7923
7924 Vehicle_t *pVeh = G_IsRidingVehicle( self );
7925 if ( !self->client->ps.SaberActive()
7926 || !self->client->ps.saberBlocking
7927 || PM_InKnockDown( &self->client->ps )
7928 || PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
7929 || (pVeh && pVeh->m_pVehicleInfo && pVeh->m_pVehicleInfo->type != VH_ANIMAL && pVeh->m_pVehicleInfo->type != VH_FLIER) )//riding a vehicle that you cannot block shots on
7930 {//can't block if saber isn't on
7931 int i, j;
7932 for ( i = 0; i < MAX_SABERS; i++ )
7933 {
7934 //initialize to not blocking
7935 for ( j = 0; j < MAX_BLADES; j++ )
7936 {
7937 alwaysBlock[i][j] = qfalse;
7938 }
7939 if ( i > 0 && !self->client->ps.dualSabers )
7940 {//not using a second saber, leave it not blocking
7941 }
7942 else
7943 {
7944 if ( (self->client->ps.saber[i].saberFlags2&SFL2_ALWAYS_BLOCK) )
7945 {
7946 for ( j = 0; j < self->client->ps.saber[i].numBlades; j++ )
7947 {
7948 alwaysBlock[i][j] = qtrue;
7949 forceBlock = qtrue;
7950 }
7951 }
7952 if ( self->client->ps.saber[i].bladeStyle2Start > 0 )
7953 {
7954 for ( j = self->client->ps.saber[i].bladeStyle2Start; j < self->client->ps.saber[i].numBlades; j++ )
7955 {
7956 if ( (self->client->ps.saber[i].saberFlags2&SFL2_ALWAYS_BLOCK2) )
7957 {
7958 alwaysBlock[i][j] = qtrue;
7959 forceBlock = qtrue;
7960 }
7961 else
7962 {
7963 alwaysBlock[i][j] = qfalse;
7964 }
7965 }
7966 }
7967 }
7968 }
7969 if ( !forceBlock )
7970 {
7971 noBlocking = qtrue;
7972 }
7973 else if ( !self->client->ps.saberBlocking )
7974 {//turn blocking on!
7975 self->client->ps.saberBlocking = BLK_TIGHT;
7976 }
7977 }
7978 if ( noBlocking )
7979 {
7980 //VectorClear(saberent->mins);
7981 //VectorClear(saberent->maxs);
7982 G_SetOrigin(saberent, self->currentOrigin);
7983 }
7984 else if ( self->client->ps.saberBlocking == BLK_TIGHT
7985 || self->client->ps.saberBlocking == BLK_WIDE )
7986 {//FIXME: keep bbox in front of player, even when wide?
7987 vec3_t saberOrg;
7988 if ( !forceBlock
7989 && ( (self->s.number&&!Jedi_SaberBusy(self)&&!g_saberRealisticCombat->integer) || (self->s.number == 0 && self->client->ps.saberBlocking == BLK_WIDE && (g_saberAutoBlocking->integer||self->client->ps.saberBlockingTime>level.time)) )
7990 && self->client->ps.weaponTime <= 0
7991 && !G_InCinematicSaberAnim( self ) )
7992 {//full-size blocking for non-attacking player with g_saberAutoBlocking on
7993 vec3_t saberang={0,0,0}, fwd, sabermins={-8,-8,-8}, sabermaxs={8,8,8};
7994
7995 saberang[YAW] = self->client->ps.viewangles[YAW];
7996 AngleVectors( saberang, fwd, NULL, NULL );
7997
7998 VectorMA( self->currentOrigin, 12, fwd, saberOrg );
7999
8000 VectorAdd( self->mins, sabermins, saberent->mins );
8001 VectorAdd( self->maxs, sabermaxs, saberent->maxs );
8002
8003 saberent->contents = CONTENTS_LIGHTSABER;
8004
8005 G_SetOrigin( saberent, saberOrg );
8006 }
8007 else
8008 {
8009 vec3_t saberBase, saberTip;
8010 int numSabers = 1;
8011 if ( self->client->ps.dualSabers )
8012 {
8013 numSabers = 2;
8014 }
8015 for ( int saberNum = 0; saberNum < numSabers; saberNum++ )
8016 {
8017 for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ )
8018 {
8019 if ( self->client->ps.saber[saberNum].blade[bladeNum].length <= 0.0f )
8020 {//don't include blades that are not on...
8021 continue;
8022 }
8023 if ( forceBlock )
8024 {//doing blade-specific bbox-sizing only, see if this blade should be counted
8025 if ( !alwaysBlock[saberNum][bladeNum] )
8026 {//this blade doesn't count right now
8027 continue;
8028 }
8029 }
8030 VectorCopy( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, saberBase );
8031 VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberTip );
8032 VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length*0.5, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberOrg );
8033 for ( int i = 0; i < 3; i++ )
8034 {
8035 /*
8036 if ( saberTip[i] > self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] )
8037 {
8038 saberent->maxs[i] = saberTip[i] - saberOrg[i] + 8;//self->client->renderInfo.muzzlePoint[i];
8039 saberent->mins[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] - 8;
8040 }
8041 else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] )
8042 {
8043 saberent->maxs[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] + 8;
8044 saberent->mins[i] = saberTip[i] - saberOrg[i] - 8;//self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i];
8045 }
8046 */
8047 float newSizeTip = (saberTip[i] - saberOrg[i]);
8048 newSizeTip += (newSizeTip>=0)?8:-8;
8049 float newSizeBase = (saberBase[i] - saberOrg[i]);
8050 newSizeBase += (newSizeBase>=0)?8:-8;
8051 if ( newSizeTip > saberent->maxs[i] )
8052 {
8053 saberent->maxs[i] = newSizeTip;
8054 }
8055 if ( newSizeBase > saberent->maxs[i] )
8056 {
8057 saberent->maxs[i] = newSizeBase;
8058 }
8059 if ( newSizeTip < saberent->mins[i] )
8060 {
8061 saberent->mins[i] = newSizeTip;
8062 }
8063 if ( newSizeBase < saberent->mins[i] )
8064 {
8065 saberent->mins[i] = newSizeBase;
8066 }
8067 }
8068 }
8069 }
8070 if ( !forceBlock )
8071 {//not doing special "alwaysBlock" bbox
8072 if ( self->client->ps.weaponTime > 0
8073 || self->s.number
8074 || g_saberAutoBlocking->integer
8075 || self->client->ps.saberBlockingTime > level.time )
8076 {//if attacking or blocking (or an NPC), inflate to a minimum size
8077 for ( int i = 0; i < 3; i++ )
8078 {
8079 if ( saberent->maxs[i] < minsize )
8080 {
8081 saberent->maxs[i] = minsize;
8082 }
8083 if ( saberent->mins[i] > -minsize )
8084 {
8085 saberent->mins[i] = -minsize;
8086 }
8087 }
8088 }
8089 }
8090 saberent->contents = CONTENTS_LIGHTSABER;
8091 G_SetOrigin( saberent, saberOrg );
8092 }
8093 }
8094 /*
8095 else if (self->client->ps.saberBlocking == BLK_WIDE)
8096 { // Assuming that we are not swinging, the saber's bounding box should be around the player.
8097 vec3_t saberang={0,0,0}, fwd;
8098
8099 saberang[YAW] = self->client->ps.viewangles[YAW];
8100 AngleVectors( saberang, fwd, NULL, NULL );
8101
8102 VectorMA(self->currentOrigin, 12, fwd, saberloc);
8103
8104 VectorAdd(self->mins, sabermins, saberent->mins);
8105 VectorAdd(self->maxs, sabermaxs, saberent->maxs);
8106
8107 saberent->contents = CONTENTS_LIGHTSABER;
8108
8109 G_SetOrigin( saberent, saberloc);
8110 }
8111 else if (self->client->ps.saberBlocking == BLK_TIGHT)
8112 { // If the player is swinging, the bbox is around just the saber
8113 VectorCopy(self->client->renderInfo.muzzlePoint, sabermins);
8114 // Put the limits of the bbox around the saber size.
8115 VectorMA(sabermins, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, sabermaxs);
8116
8117 // Now make the mins into mins and the maxs into maxs
8118 if (sabermins[0] > sabermaxs[0])
8119 {
8120 swap = sabermins[0];
8121 sabermins[0] = sabermaxs[0];
8122 sabermaxs[0] = swap;
8123 }
8124 if (sabermins[1] > sabermaxs[1])
8125 {
8126 swap = sabermins[1];
8127 sabermins[1] = sabermaxs[1];
8128 sabermaxs[1] = swap;
8129 }
8130 if (sabermins[2] > sabermaxs[2])
8131 {
8132 swap = sabermins[2];
8133 sabermins[2] = sabermaxs[2];
8134 sabermaxs[2] = swap;
8135 }
8136
8137 // Now the loc is halfway between the (absolute) mins and maxs
8138 VectorAdd(sabermins, sabermaxs, saberloc);
8139 VectorScale(saberloc, 0.5, saberloc);
8140
8141 // Finally, turn the mins and maxs, which are absolute, into relative mins and maxs.
8142 VectorSubtract(sabermins, saberloc, saberent->mins);
8143 VectorSubtract(sabermaxs, saberloc, saberent->maxs);
8144
8145 saberent->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
8146
8147 G_SetOrigin( saberent, saberloc);
8148 }
8149 */
8150 else
8151 { // Otherwise there is no blocking possible.
8152 //VectorClear(saberent->mins);
8153 //VectorClear(saberent->maxs);
8154 G_SetOrigin(saberent, self->currentOrigin);
8155 }
8156 saberent->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
8157 gi.linkentity(saberent);
8158 }
8159 else
8160 {
8161 WP_SaberInFlightReflectCheck( self, ucmd );
8162 }
8163 #ifndef FINAL_BUILD
8164 if ( d_saberCombat->integer > 2 )
8165 {
8166 CG_CubeOutline( saberent->absmin, saberent->absmax, 50, WPDEBUG_SaberColor( self->client->ps.saber[0].blade[0].color ), 1 );
8167 }
8168 #endif
8169 }
8170
8171 #define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost
G_CheckEnemyPresence(gentity_t * ent,int dir,float radius,float tolerance)8172 qboolean G_CheckEnemyPresence( gentity_t *ent, int dir, float radius, float tolerance )
8173 {
8174 gentity_t *radiusEnts[ MAX_RADIUS_ENTS ];
8175 vec3_t mins, maxs;
8176 int numEnts;
8177 vec3_t checkDir, dir2checkEnt;
8178 float dist;
8179 int i;
8180
8181 switch( dir )
8182 {
8183 case DIR_RIGHT:
8184 AngleVectors( ent->currentAngles, NULL, checkDir, NULL );
8185 break;
8186 case DIR_LEFT:
8187 AngleVectors( ent->currentAngles, NULL, checkDir, NULL );
8188 VectorScale( checkDir, -1, checkDir );
8189 break;
8190 case DIR_FRONT:
8191 AngleVectors( ent->currentAngles, checkDir, NULL, NULL );
8192 break;
8193 case DIR_BACK:
8194 AngleVectors( ent->currentAngles, checkDir, NULL, NULL );
8195 VectorScale( checkDir, -1, checkDir );
8196 break;
8197 }
8198 //Get all ents in range, see if they're living clients and enemies, then check dot to them...
8199
8200 //Setup the bbox to search in
8201 for ( i = 0; i < 3; i++ )
8202 {
8203 mins[i] = ent->currentOrigin[i] - radius;
8204 maxs[i] = ent->currentOrigin[i] + radius;
8205 }
8206
8207 //Get a number of entities in a given space
8208 numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS );
8209
8210 for ( i = 0; i < numEnts; i++ )
8211 {
8212 //Don't consider self
8213 if ( radiusEnts[i] == ent )
8214 continue;
8215
8216 //Must be valid
8217 if ( G_ValidEnemy( ent, radiusEnts[i] ) == qfalse )
8218 continue;
8219
8220 VectorSubtract( radiusEnts[i]->currentOrigin, ent->currentOrigin, dir2checkEnt );
8221 dist = VectorNormalize( dir2checkEnt );
8222 if ( dist <= radius
8223 && DotProduct( dir2checkEnt, checkDir ) >= tolerance )
8224 {
8225 //stop on the first one
8226 return qtrue;
8227 }
8228 }
8229
8230 return qfalse;
8231 }
8232 //OTHER JEDI POWERS=========================================================================
8233 //OTHER JEDI POWERS=========================================================================
8234 //OTHER JEDI POWERS=========================================================================
8235 //OTHER JEDI POWERS=========================================================================
8236 //OTHER JEDI POWERS=========================================================================
8237 extern gentity_t *TossClientItems( gentity_t *self );
8238 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
WP_DropWeapon(gentity_t * dropper,vec3_t velocity)8239 void WP_DropWeapon( gentity_t *dropper, vec3_t velocity )
8240 {
8241 if ( !dropper || !dropper->client )
8242 {
8243 return;
8244 }
8245 int replaceWeap = WP_NONE;
8246 int oldWeap = dropper->s.weapon;
8247 gentity_t *weapon = TossClientItems( dropper );
8248 if ( oldWeap == WP_THERMAL && dropper->NPC )
8249 {//Hmm, maybe all NPCs should go into melee? Not too many, though, or they mob you and look silly
8250 replaceWeap = WP_MELEE;
8251 }
8252 if ( dropper->ghoul2.IsValid() )
8253 {
8254 if ( dropper->weaponModel[0] > 0 )
8255 {//NOTE: guess you never drop the left-hand weapon, eh?
8256 gi.G2API_RemoveGhoul2Model( dropper->ghoul2, dropper->weaponModel[0] );
8257 dropper->weaponModel[0] = -1;
8258 }
8259 }
8260 //FIXME: does this work on the player?
8261 dropper->client->ps.stats[STAT_WEAPONS] |= ( 1 << replaceWeap );
8262 if ( !dropper->s.number )
8263 {
8264 if ( oldWeap == WP_THERMAL )
8265 {
8266 dropper->client->ps.ammo[weaponData[oldWeap].ammoIndex] -= weaponData[oldWeap].energyPerShot;
8267 }
8268 else
8269 {
8270 dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
8271 }
8272 CG_ChangeWeapon( replaceWeap );
8273 }
8274 else
8275 {
8276 dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
8277 }
8278 ChangeWeapon( dropper, replaceWeap );
8279 dropper->s.weapon = replaceWeap;
8280 if ( dropper->NPC )
8281 {
8282 dropper->NPC->last_ucmd.weapon = replaceWeap;
8283 }
8284 if ( weapon != NULL && velocity && !VectorCompare( velocity, vec3_origin ) )
8285 {//weapon should have a direction to it's throw
8286 VectorScale( velocity, 3, weapon->s.pos.trDelta );//NOTE: Presumes it is moving already...?
8287 if ( weapon->s.pos.trDelta[2] < 150 )
8288 {//this is presuming you don't want them to drop the weapon down on you...
8289 weapon->s.pos.trDelta[2] = 150;
8290 }
8291 //FIXME: gets stuck inside it's former owner...
8292 weapon->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8293 }
8294 }
8295
WP_KnockdownTurret(gentity_t * self,gentity_t * pas)8296 void WP_KnockdownTurret( gentity_t *self, gentity_t *pas )
8297 {
8298 //knock it over
8299 VectorCopy( pas->currentOrigin, pas->s.pos.trBase );
8300 pas->s.pos.trType = TR_LINEAR_STOP;
8301 pas->s.pos.trDuration = 250;
8302 pas->s.pos.trTime = level.time;
8303 pas->s.pos.trDelta[2] = ( 12.0f / ( pas->s.pos.trDuration * 0.001f ) );
8304
8305 VectorCopy( pas->currentAngles, pas->s.apos.trBase );
8306 pas->s.apos.trType = TR_LINEAR_STOP;
8307 pas->s.apos.trDuration = 250;
8308 pas->s.apos.trTime = level.time;
8309 //FIXME: pick pitch/roll that always tilts it directly away from pusher
8310 pas->s.apos.trDelta[PITCH] = ( 100.0f / ( pas->s.apos.trDuration * 0.001f ) );
8311
8312 //kill it
8313 pas->count = 0;
8314 pas->nextthink = -1;
8315 G_Sound( pas, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
8316 //push effect?
8317 pas->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8318 }
8319
WP_ForceThrowHazardTrooper(gentity_t * self,gentity_t * trooper,qboolean pull)8320 void WP_ForceThrowHazardTrooper( gentity_t *self, gentity_t *trooper, qboolean pull )
8321 {
8322 if ( !self || !self->client )
8323 {
8324 return;
8325 }
8326 if ( !trooper || !trooper->client )
8327 {
8328 return;
8329 }
8330
8331 //all levels: see effect on them, they notice us
8332 trooper->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8333
8334 if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1)
8335 || (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1) )
8336 {//level 2: they stop for a couple seconds and make a sound
8337 trooper->painDebounceTime = level.time + Q_irand( 1500, 2500 );
8338 G_AddVoiceEvent( trooper, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 1000, 3000 ) );
8339 GEntity_PainFunc( trooper, self, self, trooper->currentOrigin, 0, MOD_MELEE );
8340
8341 if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_2)
8342 || (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_2) )
8343 {//level 3: they actually play a pushed anim and stumble a bit
8344 vec3_t hazAngles = {0,trooper->currentAngles[YAW],0};
8345 int anim = -1;
8346 if ( InFront( self->currentOrigin, trooper->currentOrigin, hazAngles ) )
8347 {//I'm on front of him
8348 if ( pull )
8349 {
8350 anim = BOTH_PAIN4;
8351 }
8352 else
8353 {
8354 anim = BOTH_PAIN1;
8355 }
8356 }
8357 else
8358 {//I'm behind him
8359 if ( pull )
8360 {
8361 anim = BOTH_PAIN1;
8362 }
8363 else
8364 {
8365 anim = BOTH_PAIN4;
8366 }
8367 }
8368 if ( anim != -1 )
8369 {
8370 if ( anim == BOTH_PAIN1 )
8371 {//make them take a couple steps back
8372 AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL );
8373 VectorScale( trooper->client->ps.velocity, -40.0f, trooper->client->ps.velocity );
8374 trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
8375 }
8376 else if ( anim == BOTH_PAIN4 )
8377 {//make them stumble forward
8378 AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL );
8379 VectorScale( trooper->client->ps.velocity, 80.0f, trooper->client->ps.velocity );
8380 trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
8381 }
8382 NPC_SetAnim( trooper, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8383 trooper->painDebounceTime += trooper->client->ps.torsoAnimTimer;
8384 trooper->client->ps.pm_time = trooper->client->ps.torsoAnimTimer;
8385 }
8386 }
8387 if ( trooper->NPC )
8388 {
8389 if ( trooper->NPC->shotTime < trooper->painDebounceTime )
8390 {
8391 trooper->NPC->shotTime = trooper->painDebounceTime;
8392 }
8393 }
8394 trooper->client->ps.weaponTime = trooper->painDebounceTime-level.time;
8395 }
8396 else
8397 {//level 1: no pain reaction, but they should still notice
8398 if ( trooper->enemy == NULL//not mad at anyone
8399 && trooper->client->playerTeam != self->client->playerTeam//not on our team
8400 && !(trooper->svFlags&SVF_LOCKEDENEMY)//not locked on an enemy
8401 && !(trooper->svFlags&SVF_IGNORE_ENEMIES)//not ignoring enemie
8402 && !(self->flags&FL_NOTARGET) )//I'm not in notarget
8403 {//not already mad at them and can get mad at them, do so
8404 G_SetEnemy( trooper, self );
8405 }
8406 }
8407 }
8408
WP_ResistForcePush(gentity_t * self,gentity_t * pusher,qboolean noPenalty)8409 void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
8410 {
8411 int parts;
8412 qboolean runningResist = qfalse;
8413
8414 if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
8415 {
8416 return;
8417 }
8418
8419 //NOTE: don't interrupt big anims with this!
8420 if ( !PM_SaberCanInterruptMove( self->client->ps.saberMove, self->client->ps.torsoAnim ) )
8421 {//can't interrupt my current torso anim/sabermove with this, so ignore it entirely!
8422 return;
8423 }
8424
8425 if ( (!self->s.number
8426 ||( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
8427 ||( self->client && self->client->NPC_class == CLASS_SHADOWTROOPER )
8428 /*
8429 || self->client->NPC_class == CLASS_DESANN
8430 || !Q_stricmp("Yoda",self->NPC_type)
8431 || self->client->NPC_class == CLASS_LUKE*/
8432 )
8433 && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
8434 {
8435 runningResist = qtrue;
8436 }
8437
8438 if ( !runningResist
8439 && self->client->ps.groundEntityNum != ENTITYNUM_NONE
8440 && !PM_SpinningSaberAnim( self->client->ps.legsAnim )
8441 && !PM_FlippingAnim( self->client->ps.legsAnim )
8442 && !PM_RollingAnim( self->client->ps.legsAnim )
8443 && !PM_InKnockDown( &self->client->ps )
8444 && !PM_CrouchAnim( self->client->ps.legsAnim ))
8445 {//if on a surface and not in a spin or flip, play full body resist
8446 parts = SETANIM_BOTH;
8447 }
8448 else
8449 {//play resist just in torso
8450 parts = SETANIM_TORSO;
8451 }
8452 NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8453
8454 if ( !noPenalty )
8455 {
8456 if ( !runningResist )
8457 {
8458 VectorClear( self->client->ps.velocity );
8459 //still stop them from attacking or moving for a bit, though
8460 //FIXME: maybe push just a little (like, slide)?
8461 self->client->ps.weaponTime = 1000;
8462 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
8463 {
8464 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
8465 }
8466 self->client->ps.pm_time = self->client->ps.weaponTime;
8467 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
8468 //play the full body push effect on me
8469 self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8470 }
8471 else
8472 {
8473 self->client->ps.weaponTime = 600;
8474 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
8475 {
8476 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
8477 }
8478 }
8479 }
8480 //play my force push effect on my hand
8481 //self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
8482
8483 //reset to 0 in case it's still > 0 from a previous push
8484 //self->client->pushEffectFadeTime = 0;
8485 if ( !pusher //???
8486 || pusher == self->enemy//my enemy tried to push me
8487 || (pusher->client && pusher->client->playerTeam != self->client->playerTeam) )//someone not on my team tried to push me
8488 {
8489 Jedi_PlayBlockedPushSound( self );
8490 }
8491 }
8492
8493 extern qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown );
8494 extern qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir );
WP_ForceKnockdown(gentity_t * self,gentity_t * pusher,qboolean pull,qboolean strongKnockdown,qboolean breakSaberLock)8495 void WP_ForceKnockdown( gentity_t *self, gentity_t *pusher, qboolean pull, qboolean strongKnockdown, qboolean breakSaberLock )
8496 {
8497 if ( !self || !self->client || !pusher || !pusher->client )
8498 {
8499 return;
8500 }
8501
8502 if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
8503 {
8504 return;
8505 }
8506 else if ( PM_LockedAnim( self->client->ps.legsAnim ) )
8507 {//stuck doing something else
8508 return;
8509 }
8510 else if ( Rosh_BeingHealed( self ) )
8511 {
8512 return;
8513 }
8514
8515 //break out of a saberLock?
8516 if ( self->client->ps.saberLockTime > level.time )
8517 {
8518 if ( breakSaberLock
8519 || (pusher && self->client->ps.saberLockEnemy == pusher->s.number) )
8520 {
8521 self->client->ps.saberLockTime = 0;
8522 self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
8523 }
8524 else
8525 {
8526 return;
8527 }
8528 }
8529
8530 if ( self->health > 0 )
8531 {
8532 if ( !self->s.number )
8533 {
8534 NPC_SetPainEvent( self );
8535 }
8536 else
8537 {
8538 GEntity_PainFunc( self, pusher, pusher, self->currentOrigin, 0, MOD_MELEE );
8539 }
8540
8541 vec3_t pushDir;
8542 if ( pull )
8543 {
8544 VectorSubtract( pusher->currentOrigin, self->currentOrigin, pushDir );
8545 }
8546 else
8547 {
8548 VectorSubtract( self->currentOrigin, pusher->currentOrigin, pushDir );
8549 }
8550
8551 //FIXME: sometimes do this for some NPC force-users, too!
8552 if ( Boba_StopKnockdown( self, pusher, pushDir, qtrue ) )
8553 {//He can backflip instead of be knocked down
8554 return;
8555 }
8556 else if ( Jedi_StopKnockdown( self, pusher, pushDir ) )
8557 {//They can backflip instead of be knocked down
8558 return;
8559 }
8560
8561 G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );
8562
8563 if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
8564 && !PM_FlippingAnim( self->client->ps.legsAnim )
8565 && !PM_RollingAnim( self->client->ps.legsAnim )
8566 && !PM_InKnockDown( &self->client->ps ) )
8567 {
8568 int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
8569 if ( pusher->client->NPC_class == CLASS_DESANN && self->client->NPC_class != CLASS_LUKE )
8570 {//desann always knocks down, unless you're Luke
8571 strongKnockdown = qtrue;
8572 }
8573 if ( !self->s.number
8574 && !strongKnockdown
8575 && ( (!pull&&(self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1||!g_spskill->integer)) || (pull&&(self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1||!g_spskill->integer)) ) )
8576 {//player only knocked down if pushed *hard*
8577 if ( self->s.weapon == WP_SABER )
8578 {//temp HACK: these are the only 2 pain anims that look good when holding a saber
8579 knockAnim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
8580 }
8581 else
8582 {
8583 knockAnim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 );
8584 }
8585 }
8586 else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
8587 {//crouched knockdown
8588 knockAnim = BOTH_KNOCKDOWN4;
8589 }
8590 else
8591 {//plain old knockdown
8592 vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
8593 vec3_t sFwd, sAngles = {0,pusher->client->ps.viewangles[YAW],0};
8594 AngleVectors( pLAngles, pLFwd, NULL, NULL );
8595 AngleVectors( sAngles, sFwd, NULL, NULL );
8596 if ( DotProduct( sFwd, pLFwd ) > 0.2f )
8597 {//pushing him from behind
8598 //FIXME: check to see if we're aiming below or above the waist?
8599 if ( pull )
8600 {
8601 knockAnim = BOTH_KNOCKDOWN1;
8602 }
8603 else
8604 {
8605 knockAnim = BOTH_KNOCKDOWN3;
8606 }
8607 }
8608 else
8609 {//pushing him from front
8610 if ( pull )
8611 {
8612 knockAnim = BOTH_KNOCKDOWN3;
8613 }
8614 else
8615 {
8616 knockAnim = BOTH_KNOCKDOWN1;
8617 }
8618 }
8619 }
8620 if ( knockAnim == BOTH_KNOCKDOWN1 && strongKnockdown )
8621 {//push *hard*
8622 knockAnim = BOTH_KNOCKDOWN2;
8623 }
8624 NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8625 if ( self->s.number >= MAX_CLIENTS )
8626 {//randomize getup times - but not for boba
8627 int addTime;
8628 if ( self->client->NPC_class == CLASS_BOBAFETT )
8629 {
8630 addTime = Q_irand( -500, 0 );
8631 }
8632 else
8633 {
8634 addTime = Q_irand( -300, 300 );
8635 }
8636 self->client->ps.legsAnimTimer += addTime;
8637 self->client->ps.torsoAnimTimer += addTime;
8638 }
8639 else
8640 {//player holds extra long so you have more time to decide to do the quick getup
8641 if ( PM_KnockDownAnim( self->client->ps.legsAnim ) )
8642 {
8643 self->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
8644 self->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
8645 }
8646 }
8647 //
8648 if ( pusher->NPC && pusher->enemy == self )
8649 {//pushed pushed down his enemy
8650 G_AddVoiceEvent( pusher, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
8651 pusher->NPC->blockedSpeechDebounceTime = level.time + 3000;
8652 }
8653 }
8654 }
8655 self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
8656 }
8657
WP_ForceThrowable(gentity_t * ent,gentity_t * forwardEnt,gentity_t * self,qboolean pull,float cone,float radius,vec3_t forward)8658 qboolean WP_ForceThrowable( gentity_t *ent, gentity_t *forwardEnt, gentity_t *self, qboolean pull, float cone, float radius, vec3_t forward )
8659 {
8660 if (ent == self)
8661 return qfalse;
8662 if ( ent->owner == self && ent->s.weapon != WP_THERMAL )//can push your own thermals
8663 return qfalse;
8664 if ( !(ent->inuse) )
8665 return qfalse;
8666 if ( ent->NPC && ent->NPC->scriptFlags & SCF_NO_FORCE )
8667 {
8668 if ( ent->s.weapon == WP_SABER )
8669 {//Hmm, should jedi do the resist behavior? If this is on, perhaps it's because of a cinematic?
8670 WP_ResistForcePush( ent, self, qtrue );
8671 }
8672 return qfalse;
8673 }
8674 if ( (ent->flags&FL_FORCE_PULLABLE_ONLY) && !pull )
8675 {//simple HACK: cannot force-push ammo rack items (because they may start in solid)
8676 return qfalse;
8677 }
8678 //FIXME: don't push it if I already pushed it a little while ago
8679 if ( ent->s.eType != ET_MISSILE )
8680 {
8681 if ( ent->client )
8682 {
8683 if ( ent->client->ps.pullAttackTime > level.time )
8684 {
8685 return qfalse;
8686 }
8687 }
8688 if ( cone >= 1.0f )
8689 {//must be pointing right at them
8690 if ( ent != forwardEnt )
8691 {//must be the person I'm looking right at
8692 if ( ent->client && !pull
8693 && ent->client->ps.forceGripEntityNum == self->s.number
8694 && (self->s.eFlags&EF_FORCE_GRIPPED) )
8695 {//this is the guy that's force-gripping me, use a wider cone regardless of force power level
8696 }
8697 else
8698 {
8699 if ( ent->client && !pull
8700 && ent->client->ps.forceDrainEntityNum == self->s.number
8701 && (self->s.eFlags&EF_FORCE_DRAINED) )
8702 {//this is the guy that's force-draining me, use a wider cone regardless of force power level
8703 }
8704 else
8705 {
8706 return qfalse;
8707 }
8708 }
8709 }
8710 }
8711 if ( ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )//|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items
8712 {
8713 //FIXME: need pushable objects
8714 if ( ent->s.eFlags & EF_NODRAW )
8715 {
8716 return qfalse;
8717 }
8718 if ( !ent->client )
8719 {
8720 if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
8721 {//not a lightsaber
8722 if ( !(ent->svFlags&SVF_GLASS_BRUSH) )
8723 {//and not glass
8724 if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) )
8725 {//not a force-usable door
8726 if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) || (ent->spawnflags&32/*SOLITARY*/) )
8727 {//not a force-usable func_static or, it is one, but it's solitary, so you only press it when looking right at it
8728 if ( Q_stricmp( "limb", ent->classname ) )
8729 {//not a limb
8730 if ( ent->s.weapon == WP_TURRET && !Q_stricmp( "PAS", ent->classname ) && ent->s.apos.trType == TR_STATIONARY )
8731 {//can knock over placed turrets
8732 if ( !self->s.number || self->enemy != ent )
8733 {//only NPCs who are actively mad at this turret can push it over
8734 return qfalse;
8735 }
8736 }
8737 else
8738 {
8739 return qfalse;
8740 }
8741 }
8742 }
8743 }
8744 else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 )
8745 {//not at rest
8746 return qfalse;
8747 }
8748 }
8749 }
8750 //return qfalse;
8751 }
8752 else if ( ent->client->NPC_class == CLASS_MARK1 )
8753 {//can't push Mark1 unless push 3
8754 if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
8755 {
8756 return qfalse;
8757 }
8758 }
8759 else if ( ent->client->NPC_class == CLASS_GALAKMECH
8760 || ent->client->NPC_class == CLASS_ATST
8761 || ent->client->NPC_class == CLASS_RANCOR
8762 || ent->client->NPC_class == CLASS_WAMPA
8763 || ent->client->NPC_class == CLASS_SAND_CREATURE )
8764 {//can't push ATST or Galak or Rancor or Wampa
8765 return qfalse;
8766 }
8767 else if ( ent->s.weapon == WP_EMPLACED_GUN )
8768 {//FIXME: maybe can pull them out?
8769 return qfalse;
8770 }
8771 else if ( ent->client->playerTeam == self->client->playerTeam && self->enemy && self->enemy != ent )
8772 {//can't accidently push a teammate while in combat
8773 return qfalse;
8774 }
8775 else if ( G_IsRidingVehicle( ent )
8776 && (ent->s.eFlags&EF_NODRAW) )
8777 {//can't push/pull anyone riding *inside* vehicle
8778 return qfalse;
8779 }
8780 }
8781 else if ( ent->s.eType == ET_ITEM )
8782 {
8783 if ( (ent->flags&FL_NO_KNOCKBACK) )
8784 {
8785 return qfalse;
8786 }
8787 if ( ent->item
8788 && ent->item->giType == IT_HOLDABLE
8789 && ent->item->giTag == INV_SECURITY_KEY )
8790 //&& (ent->flags&FL_DROPPED_ITEM) ???
8791 {//dropped security keys can't be pushed? But placed ones can...? does this make any sense?
8792 if ( !pull || self->s.number )
8793 {//can't push, NPC's can't do anything to it
8794 return qfalse;
8795 }
8796 else
8797 {
8798 if ( g_crosshairEntNum != ent->s.number )
8799 {//player can pull it if looking *right* at it
8800 if ( cone >= 1.0f )
8801 {//we did a forwardEnt trace
8802 if ( forwardEnt != ent )
8803 {//must be pointing right at them
8804 return qfalse;
8805 }
8806 }
8807 else if ( forward )
8808 {//do a forwardEnt trace
8809 trace_t tr;
8810 vec3_t end;
8811 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
8812 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE, (EG2_Collision)0, 0 );//was MASK_SHOT, changed to match crosshair trace
8813 if ( tr.entityNum != ent->s.number )
8814 {//last chance
8815 return qfalse;
8816 }
8817 }
8818 }
8819 }
8820 }
8821 }
8822 }
8823 else
8824 {
8825 switch ( ent->s.weapon )
8826 {//only missiles with mass are force-pushable
8827 case WP_SABER:
8828 case WP_FLECHETTE:
8829 case WP_ROCKET_LAUNCHER:
8830 case WP_CONCUSSION:
8831 case WP_THERMAL:
8832 case WP_TRIP_MINE:
8833 case WP_DET_PACK:
8834 break;
8835 //only alt-fire of this weapon is force-pushable
8836 case WP_REPEATER:
8837 if ( ent->methodOfDeath != MOD_REPEATER_ALT )
8838 {//not an alt-fire missile
8839 return qfalse;
8840 }
8841 break;
8842 //everything else cannot be pushed
8843 case WP_ATST_SIDE:
8844 if ( ent->methodOfDeath != MOD_EXPLOSIVE )
8845 {//not a rocket
8846 return qfalse;
8847 }
8848 break;
8849 default:
8850 return qfalse;
8851 break;
8852 }
8853
8854 if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
8855 {//can't force-push/pull stuck missiles (detpacks, tripmines)
8856 return qfalse;
8857 }
8858 if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL )
8859 {//only thermal detonators can be pushed once stopped
8860 return qfalse;
8861 }
8862 }
8863 return qtrue;
8864 }
8865
ShouldPlayerResistForceThrow(gentity_t * player,gentity_t * attacker,qboolean pull)8866 static qboolean ShouldPlayerResistForceThrow( gentity_t *player, gentity_t *attacker, qboolean pull )
8867 {
8868 if ( player->health <= 0 )
8869 {
8870 return qfalse;
8871 }
8872
8873 if ( !player->client )
8874 {
8875 return qfalse;
8876 }
8877
8878 if ( player->client->ps.forceRageRecoveryTime >= level.time )
8879 {
8880 return qfalse;
8881 }
8882
8883 //wasn't trying to grip/drain anyone
8884 if ( player->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD ||
8885 player->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START ||
8886 player->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD )
8887 {
8888 return qfalse;
8889 }
8890
8891 //only 30% chance of resisting a Desann or yoda push
8892 if ( (attacker->client->NPC_class == CLASS_DESANN || Q_stricmp("Yoda",attacker->NPC_type) == 0) && Q_irand( 0, 2 ) > 0 )
8893 {
8894 return qfalse;
8895 }
8896
8897 //on the ground
8898 if ( player->client->ps.groundEntityNum == ENTITYNUM_NONE )
8899 {
8900 return qfalse;
8901 }
8902
8903 //not knocked down already
8904 if ( PM_InKnockDown( &player->client->ps ) )
8905 {
8906 return qfalse;
8907 }
8908
8909 //not involved in a saberLock
8910 if ( player->client->ps.saberLockTime >= level.time )
8911 {
8912 return qfalse;
8913 }
8914
8915 //not attacking or otherwise busy
8916 if ( player->client->ps.weaponTime >= level.time )
8917 {
8918 return qfalse;
8919 }
8920
8921 //using saber or fists
8922 if ( player->client->ps.weapon != WP_SABER && player->client->ps.weapon != WP_MELEE )
8923 {
8924 return qfalse;
8925 }
8926
8927 forcePowers_t forcePower = (pull ? FP_PULL : FP_PUSH);
8928 int attackingForceLevel = attacker->client->ps.forcePowerLevel[forcePower];
8929 int defendingForceLevel = player->client->ps.forcePowerLevel[forcePower];
8930
8931 if ( player->client->ps.powerups[PW_FORCE_PUSH] > level.time ||
8932 Q_irand( 0, Q_max(0, defendingForceLevel - attackingForceLevel)*2 + 1 ) > 0 )
8933 {
8934 // player was pushing, or player's force push/pull is high enough to try to stop me
8935 if ( InFront( attacker->currentOrigin, player->client->renderInfo.eyePoint, player->client->ps.viewangles, 0.3f ) )
8936 {
8937 //I'm in front of player
8938 return qtrue;
8939 }
8940 }
8941
8942 return qfalse;
8943 }
8944
ForceThrow(gentity_t * self,qboolean pull,qboolean fake)8945 void ForceThrow( gentity_t *self, qboolean pull, qboolean fake )
8946 {//FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent.
8947 //shove things in front of you away
8948 float dist;
8949 gentity_t *ent, *forwardEnt = NULL;
8950 gentity_t *entityList[MAX_GENTITIES];
8951 gentity_t *push_list[MAX_GENTITIES];
8952 int numListedEntities = 0;
8953 vec3_t mins, maxs;
8954 vec3_t v;
8955 int i, e;
8956 int ent_count = 0;
8957 int radius;
8958 vec3_t center, ent_org, size, forward, right, end, dir, fwdangles = {0};
8959 float dot1, cone;
8960 trace_t tr;
8961 int anim, hold, soundIndex, cost, actualCost;
8962 qboolean noResist = qfalse;
8963
8964 if ( self->health <= 0 )
8965 {
8966 return;
8967 }
8968 if ( self->client->ps.leanofs )
8969 {//can't force-throw while leaning
8970 return;
8971 }
8972 if ( self->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
8973 {//already pushing- now you can't haul someone across the room, sorry
8974 return;
8975 }
8976 if ( self->client->ps.forcePowerDebounce[FP_PULL] > level.time )
8977 {//already pulling- now you can't haul someone across the room, sorry
8978 return;
8979 }
8980 if ( self->client->ps.pullAttackTime > level.time )
8981 {//already pull-attacking
8982 return;
8983 }
8984 if ( !self->s.number && (cg.zoomMode || in_camera) )
8985 {//can't force throw/pull when zoomed in or in cinematic
8986 return;
8987 }
8988 if ( self->client->ps.saberLockTime > level.time )
8989 {
8990 if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
8991 {//this can be a way to break out
8992 return;
8993 }
8994 //else, I'm breaking my half of the saberlock
8995 self->client->ps.saberLockTime = 0;
8996 self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
8997 }
8998
8999 if ( self->client->ps.legsAnim == BOTH_KNOCKDOWN3
9000 || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F1 && self->client->ps.torsoAnimTimer > 400)
9001 || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F2 && self->client->ps.torsoAnimTimer > 900)
9002 || (self->client->ps.torsoAnim == BOTH_GETUP3 && self->client->ps.torsoAnimTimer > 500)
9003 || (self->client->ps.torsoAnim == BOTH_GETUP4 && self->client->ps.torsoAnimTimer > 300)
9004 || (self->client->ps.torsoAnim == BOTH_GETUP5 && self->client->ps.torsoAnimTimer > 500) )
9005 {//we're face-down, so we'd only be force-push/pulling the floor
9006 return;
9007 }
9008 if ( pull )
9009 {
9010 radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PULL]];
9011 }
9012 else
9013 {
9014 radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PUSH]];
9015 }
9016
9017 if ( !radius )
9018 {//no ability to do this yet
9019 return;
9020 }
9021
9022 if ( pull )
9023 {
9024 cost = forcePowerNeeded[FP_PULL];
9025 if ( !WP_ForcePowerUsable( self, FP_PULL, cost ) )
9026 {
9027 return;
9028 }
9029 //make sure this plays and that you cannot press fire for about 200ms after this
9030 anim = BOTH_FORCEPULL;
9031 soundIndex = G_SoundIndex( "sound/weapons/force/pull.wav" );
9032 hold = 200;
9033 }
9034 else
9035 {
9036 cost = forcePowerNeeded[FP_PUSH];
9037 if ( !WP_ForcePowerUsable( self, FP_PUSH, cost ) )
9038 {
9039 return;
9040 }
9041 //make sure this plays and that you cannot press fire for about 1 second after this
9042 anim = BOTH_FORCEPUSH;
9043 soundIndex = G_SoundIndex( "sound/weapons/force/push.wav" );
9044 hold = 650;
9045 }
9046
9047 int parts = SETANIM_TORSO;
9048 if ( !PM_InKnockDown( &self->client->ps ) )
9049 {
9050 if ( self->client->ps.saberLockTime > level.time )
9051 {
9052 self->client->ps.saberLockTime = 0;
9053 self->painDebounceTime = level.time + 2000;
9054 hold += 1000;
9055 parts = SETANIM_BOTH;
9056 }
9057 else if ( !VectorLengthSquared( self->client->ps.velocity ) && !(self->client->ps.pm_flags&PMF_DUCKED))
9058 {
9059 parts = SETANIM_BOTH;
9060 }
9061 }
9062 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
9063 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
9064 self->client->ps.saberBlocked = BLOCKED_NONE;
9065 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
9066 {
9067 hold = floor( hold*g_timescale->value );
9068 }
9069 self->client->ps.weaponTime = hold;//was 1000, but want to swing sooner
9070 //do effect... FIXME: build-up or delay this until in proper part of anim
9071 self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
9072 //reset to 0 in case it's still > 0 from a previous push
9073 self->client->pushEffectFadeTime = 0;
9074
9075 G_Sound( self, soundIndex );
9076
9077 if ( (!pull && self->client->ps.forcePowersForced&(1<<FP_PUSH))
9078 || (pull && self->client->ps.forcePowersForced&(1<<FP_PULL))
9079 || (pull&&self->client->NPC_class==CLASS_KYLE&&(self->spawnflags&1)&&TIMER_Done( self, "kyleTakesSaber" )) )
9080 {
9081 noResist = qtrue;
9082 }
9083
9084 VectorCopy( self->client->ps.viewangles, fwdangles );
9085 //fwdangles[1] = self->client->ps.viewangles[1];
9086 AngleVectors( fwdangles, forward, right, NULL );
9087 VectorCopy( self->currentOrigin, center );
9088
9089 if ( pull )
9090 {
9091 cone = forcePullCone[self->client->ps.forcePowerLevel[FP_PULL]];
9092 }
9093 else
9094 {
9095 cone = forcePushCone[self->client->ps.forcePowerLevel[FP_PUSH]];
9096 }
9097
9098 // if ( cone >= 1.0f )
9099 {//must be pointing right at them
9100 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
9101 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE, (EG2_Collision)0, 0 );//was MASK_SHOT, changed to match crosshair trace
9102 if ( tr.entityNum < ENTITYNUM_WORLD )
9103 {//found something right in front of self,
9104 forwardEnt = &g_entities[tr.entityNum];
9105 if ( !forwardEnt->client && !Q_stricmp( "func_static", forwardEnt->classname ) )
9106 {
9107 if ( (forwardEnt->spawnflags&1/*F_PUSH*/)||(forwardEnt->spawnflags&2/*F_PULL*/) )
9108 {//push/pullable
9109 if ( (forwardEnt->spawnflags&32/*SOLITARY*/) )
9110 {//can only push/pull ME, ignore all others
9111 if ( forwardEnt->NPC_targetname == NULL
9112 || (self->targetname&&Q_stricmp( forwardEnt->NPC_targetname, self->targetname ) == 0) )
9113 {//anyone can push it or only 1 person can push it and it's me
9114 push_list[0] = forwardEnt;
9115 ent_count = numListedEntities = 1;
9116 }
9117 }
9118 }
9119 }
9120 }
9121 }
9122
9123 if ( forwardEnt )
9124 {
9125 if ( G_TryingPullAttack( self, &self->client->usercmd, qtrue ) )
9126 {//we're going to try to do a pull attack on our forwardEnt
9127 if ( WP_ForceThrowable( forwardEnt, forwardEnt, self, pull, cone, radius, forward ) )
9128 {//we will actually pull-attack him, so don't pull him or anything else here
9129 //activate the power, here, though, so the later check that actually does the pull attack knows we tried to pull
9130 self->client->ps.forcePowersActive |= (1<<FP_PULL);
9131 self->client->ps.forcePowerDebounce[FP_PULL] = level.time + 100; //force-pulling
9132 return;
9133 }
9134 }
9135 }
9136
9137 if ( !numListedEntities )
9138 {
9139 for ( i = 0 ; i < 3 ; i++ )
9140 {
9141 mins[i] = center[i] - radius;
9142 maxs[i] = center[i] + radius;
9143 }
9144
9145 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
9146
9147 for ( e = 0 ; e < numListedEntities ; e++ )
9148 {
9149 ent = entityList[ e ];
9150
9151 if ( !WP_ForceThrowable( ent, forwardEnt, self, pull, cone, radius, forward ) )
9152 {
9153 continue;
9154 }
9155
9156 //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
9157 // find the distance from the edge of the bounding box
9158 for ( i = 0 ; i < 3 ; i++ )
9159 {
9160 if ( center[i] < ent->absmin[i] )
9161 {
9162 v[i] = ent->absmin[i] - center[i];
9163 } else if ( center[i] > ent->absmax[i] )
9164 {
9165 v[i] = center[i] - ent->absmax[i];
9166 } else
9167 {
9168 v[i] = 0;
9169 }
9170 }
9171
9172 VectorSubtract( ent->absmax, ent->absmin, size );
9173 VectorMA( ent->absmin, 0.5, size, ent_org );
9174
9175 //see if they're in front of me
9176 VectorSubtract( ent_org, center, dir );
9177 VectorNormalize( dir );
9178 if ( cone < 1.0f )
9179 {//must be within the forward cone
9180 if ( ent->client && !pull
9181 && ent->client->ps.forceGripEntityNum == self->s.number
9182 && (self->s.eFlags&EF_FORCE_GRIPPED) )
9183 {//this is the guy that's force-gripping me, use a wider cone regardless of force power level
9184 if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
9185 continue;
9186 }
9187 else if ( ent->client && !pull
9188 && ent->client->ps.forceDrainEntityNum == self->s.number
9189 && (self->s.eFlags&EF_FORCE_DRAINED) )
9190 {//this is the guy that's force-draining me, use a wider cone regardless of force power level
9191 if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
9192 continue;
9193 }
9194 else if ( ent->s.eType == ET_MISSILE )//&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )
9195 {//missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects)
9196 if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
9197 continue;
9198 }
9199 else if ( (dot1 = DotProduct( dir, forward )) < cone )
9200 {
9201 continue;
9202 }
9203 }
9204 else if ( ent->s.eType == ET_MISSILE )
9205 {//a missile and we're at force level 1... just use a small cone, but not ridiculously small
9206 if ( (dot1 = DotProduct( dir, forward )) < 0.75f )
9207 {
9208 continue;
9209 }
9210 }//else is an NPC or brush entity that our forward trace would have to hit
9211
9212 dist = VectorLength( v );
9213
9214 //Now check and see if we can actually deflect it
9215 //method1
9216 //if within a certain range, deflect it
9217 if ( ent->s.eType == ET_MISSILE && cone >= 1.0f )
9218 {//smaller radius on missile checks at force push 1
9219 if ( dist >= 192 )
9220 {
9221 continue;
9222 }
9223 }
9224 else if ( dist >= radius )
9225 {
9226 continue;
9227 }
9228
9229 //in PVS?
9230 if ( !ent->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.eyePoint ) )
9231 {//must be in PVS
9232 continue;
9233 }
9234
9235 if ( ent != forwardEnt )
9236 {//don't need to trace against forwardEnt again
9237 //really should have a clear LOS to this thing...
9238 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_FORCE_PUSH, (EG2_Collision)0, 0 );//was MASK_SHOT, but changed to match above trace and crosshair trace
9239 if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
9240 {//must have clear LOS
9241 continue;
9242 }
9243 }
9244
9245 // ok, we are within the radius, add us to the incoming list
9246 push_list[ent_count] = ent;
9247 ent_count++;
9248 }
9249 }
9250
9251 if ( ent_count )
9252 {
9253 for ( int x = 0; x < ent_count; x++ )
9254 {
9255 if ( push_list[x]->client )
9256 {
9257 vec3_t pushDir;
9258 float knockback = pull?0:200;
9259
9260 //SIGH band-aid...
9261 if ( push_list[x]->s.number >= MAX_CLIENTS
9262 && self->s.number < MAX_CLIENTS )
9263 {
9264 if ( (push_list[x]->client->ps.forcePowersActive&(1<<FP_GRIP))
9265 //&& push_list[x]->client->ps.forcePowerDebounce[FP_GRIP] < level.time
9266 && push_list[x]->client->ps.forceGripEntityNum == self->s.number )
9267 {
9268 WP_ForcePowerStop( push_list[x], FP_GRIP );
9269 }
9270 if ( (push_list[x]->client->ps.forcePowersActive&(1<<FP_DRAIN))
9271 //&& push_list[x]->client->ps.forcePowerDebounce[FP_DRAIN] < level.time
9272 && push_list[x]->client->ps.forceDrainEntityNum == self->s.number )
9273 {
9274 WP_ForcePowerStop( push_list[x], FP_DRAIN );
9275 }
9276 }
9277
9278 if ( Rosh_BeingHealed( push_list[x] ) )
9279 {
9280 continue;
9281 }
9282 if ( push_list[x]->client->NPC_class == CLASS_HAZARD_TROOPER
9283 && push_list[x]->health > 0 )
9284 {//living hazard troopers resist push/pull
9285 WP_ForceThrowHazardTrooper( self, push_list[x], pull );
9286 continue;
9287 }
9288 if ( fake )
9289 {//always resist
9290 WP_ResistForcePush( push_list[x], self, qfalse );
9291 continue;
9292 }
9293 //FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!!
9294 int powerLevel, powerUse;
9295 if (pull)
9296 {
9297 powerLevel = self->client->ps.forcePowerLevel[FP_PULL];
9298 powerUse = FP_PULL;
9299 }
9300 else
9301 {
9302 powerLevel = self->client->ps.forcePowerLevel[FP_PUSH];
9303 powerUse = FP_PUSH;
9304 }
9305 int modPowerLevel = WP_AbsorbConversion( push_list[x], push_list[x]->client->ps.forcePowerLevel[FP_ABSORB], self, powerUse, powerLevel, forcePowerNeeded[self->client->ps.forcePowerLevel[powerUse]] );
9306 if (push_list[x]->client->NPC_class==CLASS_ASSASSIN_DROID ||
9307 push_list[x]->client->NPC_class==CLASS_HAZARD_TROOPER)
9308 {
9309 modPowerLevel = 0; // devides throw by 10
9310 }
9311
9312 //First, if this is the player we're push/pulling, see if he can counter it
9313 if ( modPowerLevel != -1
9314 && !noResist
9315 && InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) )
9316 {//absorbed and I'm in front of them
9317 //counter it
9318 if ( push_list[x]->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 )
9319 {//no reaction at all
9320 }
9321 else
9322 {
9323 WP_ResistForcePush( push_list[x], self, qfalse );
9324 push_list[x]->client->ps.saberMove = push_list[x]->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
9325 push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
9326 }
9327 continue;
9328 }
9329 else if ( !push_list[x]->s.number )
9330 {//player
9331 if ( !noResist && ShouldPlayerResistForceThrow(push_list[x], self, pull) )
9332 {
9333 WP_ResistForcePush( push_list[x], self, qfalse );
9334 push_list[x]->client->ps.saberMove = push_list[x]->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
9335 push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
9336 continue;
9337 }
9338 }
9339 else if ( push_list[x]->client && Jedi_WaitingAmbush( push_list[x] ) )
9340 {
9341 WP_ForceKnockdown( push_list[x], self, pull, qtrue, qfalse );
9342 continue;
9343 }
9344
9345 G_KnockOffVehicle( push_list[x], self, pull );
9346
9347 if ( !pull
9348 && push_list[x]->client->ps.forceDrainEntityNum == self->s.number
9349 && (self->s.eFlags&EF_FORCE_DRAINED) )
9350 {//stop them from draining me now, dammit!
9351 WP_ForcePowerStop( push_list[x], FP_DRAIN );
9352 }
9353
9354 //okay, everyone else (or player who couldn't resist it)...
9355 if ( ((self->s.number == 0 && Q_irand( 0, 2 ) ) || Q_irand( 0, 2 ) ) && push_list[x]->client && push_list[x]->health > 0 //a living client
9356 && push_list[x]->client->ps.weapon == WP_SABER //Jedi
9357 && push_list[x]->health > 0 //alive
9358 && push_list[x]->client->ps.forceRageRecoveryTime < level.time //not recobering from rage
9359 && ((self->client->NPC_class != CLASS_DESANN&&Q_stricmp("Yoda",self->NPC_type)) || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
9360 && push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE //on the ground
9361 && InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.3f ) //I'm in front of him
9362 && ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time ||//he's pushing too
9363 (push_list[x]->s.number != 0 && push_list[x]->client->ps.weaponTime < level.time)//not the player and not attacking (NPC jedi auto-defend against pushes)
9364 )
9365 )
9366 {//Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground
9367 if ( push_list[x]->client->ps.saberLockTime > level.time )
9368 {//they're in a lock
9369 if ( push_list[x]->client->ps.saberLockEnemy != self->s.number )
9370 {//they're not in a lock with me
9371 continue;
9372 }
9373 else if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ||
9374 push_list[x]->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
9375 {//they're in a lock with me, but my push is too weak
9376 continue;
9377 }
9378 else
9379 {//we will knock them down
9380 self->painDebounceTime = 0;
9381 self->client->ps.weaponTime = 500;
9382 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
9383 {
9384 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
9385 }
9386 }
9387 }
9388 int resistChance = Q_irand(0, 2);
9389 if ( push_list[x]->s.number >= MAX_CLIENTS )
9390 {//NPC
9391 if ( g_spskill->integer == 1 )
9392 {//stupid tweak for graham
9393 resistChance = Q_irand(0, 3);
9394 }
9395 }
9396 if ( noResist ||
9397 ( !pull
9398 && modPowerLevel == -1
9399 && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2
9400 && !resistChance
9401 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
9402 )
9403 {//a level 3 push can even knock down a jedi
9404 if ( PM_InKnockDown( &push_list[x]->client->ps ) )
9405 {//can't knock them down again
9406 continue;
9407 }
9408 WP_ForceKnockdown( push_list[x], self, pull, qfalse, qtrue );
9409 }
9410 else
9411 {
9412 WP_ResistForcePush( push_list[x], self, qfalse );
9413 }
9414 }
9415 else
9416 {
9417 //UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)!
9418 //shove them
9419 if ( push_list[x]->NPC
9420 && push_list[x]->NPC->jumpState == JS_JUMPING )
9421 {//don't interrupt a scripted jump
9422 //WP_ResistForcePush( push_list[x], self, qfalse );
9423 push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9424 continue;
9425 }
9426
9427 if ( push_list[x]->s.number
9428 && (push_list[x]->message || (push_list[x]->flags&FL_NO_KNOCKBACK)) )
9429 {//an NPC who has a key
9430 //don't push me... FIXME: maybe can pull the key off me?
9431 WP_ForceKnockdown( push_list[x], self, pull, qfalse, qfalse );
9432 continue;
9433 }
9434 if ( pull )
9435 {
9436 VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
9437 if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3
9438 && self->client->NPC_class == CLASS_KYLE
9439 && (self->spawnflags&1)
9440 && TIMER_Done( self, "kyleTakesSaber" )
9441 && push_list[x]->client
9442 && push_list[x]->client->ps.weapon == WP_SABER
9443 && !push_list[x]->client->ps.saberInFlight
9444 && push_list[x]->client->ps.saberEntityNum < ENTITYNUM_WORLD
9445 && !PM_InOnGroundAnim( &push_list[x]->client->ps ) )
9446 {
9447 vec3_t throwVec;
9448 VectorScale( pushDir, 10.0f, throwVec );
9449 WP_SaberLose( push_list[x], throwVec );
9450 NPC_SetAnim( push_list[x], SETANIM_BOTH, BOTH_LOSE_SABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
9451 push_list[x]->client->ps.torsoAnimTimer += 500;
9452 push_list[x]->client->ps.pm_time = push_list[x]->client->ps.weaponTime = push_list[x]->client->ps.torsoAnimTimer;
9453 push_list[x]->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
9454 push_list[x]->client->ps.saberMove = LS_NONE;
9455 push_list[x]->aimDebounceTime = level.time + push_list[x]->client->ps.torsoAnimTimer;
9456 VectorClear( push_list[x]->client->ps.velocity );
9457 VectorClear( push_list[x]->client->ps.moveDir );
9458 //Kyle will stand around for a bit, too...
9459 self->client->ps.pm_time = self->client->ps.weaponTime = 2000;
9460 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
9461 self->painDebounceTime = level.time + self->client->ps.weaponTime;
9462 TIMER_Set( self, "kyleTakesSaber", Q_irand( 60000, 180000 ) );//don't do this again for a while
9463 G_AddVoiceEvent( self, Q_irand(EV_TAUNT1,EV_TAUNT3), Q_irand( 4000, 6000 ) );
9464 VectorClear( self->client->ps.velocity );
9465 VectorClear( self->client->ps.moveDir );
9466 continue;
9467 }
9468 else if ( push_list[x]->NPC
9469 && (push_list[x]->NPC->scriptFlags&SCF_DONT_FLEE) )
9470 {//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it
9471 }
9472 else if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_1
9473 && push_list[x]->client->NPC_class != CLASS_ROCKETTROOPER//rockettroopers never drop their weapon
9474 && push_list[x]->client->NPC_class != CLASS_VEHICLE
9475 && push_list[x]->client->NPC_class != CLASS_BOBAFETT
9476 && push_list[x]->client->NPC_class != CLASS_TUSKEN
9477 && push_list[x]->client->NPC_class != CLASS_HAZARD_TROOPER
9478 && push_list[x]->client->NPC_class != CLASS_ASSASSIN_DROID
9479 && push_list[x]->s.weapon != WP_SABER
9480 && push_list[x]->s.weapon != WP_MELEE
9481 && push_list[x]->s.weapon != WP_THERMAL
9482 && push_list[x]->s.weapon != WP_CONCUSSION // so rax can't drop his
9483 )
9484 {//yank the weapon - NOTE: level 1 just knocks them down, not take weapon
9485 //FIXME: weapon yank anim if not a knockdown?
9486 if ( InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.0f ) )
9487 {//enemy has to be facing me, too...
9488 WP_DropWeapon( push_list[x], pushDir );
9489 }
9490 }
9491 knockback += VectorNormalize( pushDir );
9492 if ( knockback > 200 )
9493 {
9494 knockback = 200;
9495 }
9496 if ( self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_3 )
9497 {//maybe just knock them down
9498 knockback /= 3;
9499 }
9500 }
9501 else
9502 {
9503 VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
9504 knockback -= VectorNormalize( pushDir );
9505 if ( knockback < 100 )
9506 {
9507 knockback = 100;
9508 }
9509 //scale for push level
9510 if ( self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 )
9511 {//maybe just knock them down
9512 knockback /= 3;
9513 }
9514 else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
9515 {//super-hard push
9516 //Hmm, maybe in this case can even nudge/knockdown a jedi? Especially if close?
9517 //knockback *= 5;
9518 }
9519 }
9520
9521 if ( modPowerLevel != -1 )
9522 {
9523 if ( !modPowerLevel )
9524 {
9525 knockback /= 10.0f;
9526 }
9527 else if ( modPowerLevel == 1 )
9528 {
9529 knockback /= 6.0f;
9530 }
9531 else// if ( modPowerLevel == 2 )
9532 {
9533 knockback /= 2.0f;
9534 }
9535 }
9536 //actually push/pull the enemy
9537 G_Throw( push_list[x], pushDir, knockback );
9538 //make it so they don't actually hurt me when pulled at me...
9539 push_list[x]->forcePuller = self->s.number;
9540
9541 if ( push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE )
9542 {//if on the ground, make sure they get shoved up some
9543 if ( push_list[x]->client->ps.velocity[2] < knockback )
9544 {
9545 push_list[x]->client->ps.velocity[2] = knockback;
9546 }
9547 }
9548
9549 if ( push_list[x]->health > 0 )
9550 {//target is still alive
9551 if ( (push_list[x]->s.number||(cg.renderingThirdPerson&&!cg.zoomMode)) //NPC or 3rd person player
9552 && ((!pull&&self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1) //level 1 push
9553 || (pull && self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_1)) )//level 1 pull
9554 {//NPC or third person player (without force push/pull skill), and force push/pull level is at 1
9555 WP_ForceKnockdown( push_list[x], self, pull, (qboolean)(!pull&&knockback>150), qfalse );
9556 }
9557 else if ( !push_list[x]->s.number )
9558 {//player, have to force an anim on him
9559 WP_ForceKnockdown( push_list[x], self, pull, (qboolean)(!pull&&knockback>150), qfalse );
9560 }
9561 else
9562 {//NPC and force-push/pull at level 2 or higher
9563 WP_ForceKnockdown( push_list[x], self, pull, (qboolean)(!pull&&knockback>100), qfalse );
9564 }
9565 }
9566 push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9567 }
9568 }
9569 else if ( !fake )
9570 {//not a fake push/pull
9571 if ( push_list[x]->s.weapon == WP_SABER && (push_list[x]->contents&CONTENTS_LIGHTSABER) )
9572 {//a thrown saber, just send it back
9573 /*
9574 if ( pull )
9575 {//steal it?
9576 }
9577 else */if ( push_list[x]->owner && push_list[x]->owner->client && push_list[x]->owner->client->ps.SaberActive() && push_list[x]->s.pos.trType == TR_LINEAR && push_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
9578 {//it's on and being controlled
9579 //FIXME: prevent it from damaging me?
9580 if ( self->s.number == 0 || Q_irand( 0, 2 ) )
9581 {//certain chance of throwing it aside and turning it off?
9582 //give it some velocity away from me
9583 //FIXME: maybe actually push or pull it?
9584 if ( Q_irand( 0, 1 ) )
9585 {
9586 VectorScale( right, -1, right );
9587 }
9588 G_ReflectMissile( self, push_list[x], right );
9589 //FIXME: isn't turning off!!!
9590 WP_SaberDrop( push_list[x]->owner, push_list[x] );
9591 }
9592 else
9593 {
9594 WP_SaberReturn( push_list[x]->owner, push_list[x] );
9595 }
9596 //different effect?
9597 }
9598 }
9599 else if ( push_list[x]->s.eType == ET_MISSILE
9600 && push_list[x]->s.pos.trType != TR_STATIONARY
9601 && (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below
9602 {
9603 vec3_t dir2Me;
9604 VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, dir2Me );
9605 float dot = DotProduct( push_list[x]->s.pos.trDelta, dir2Me );
9606 if ( pull )
9607 {//deflect rather than reflect?
9608 }
9609 else
9610 {
9611 if ( push_list[x]->s.eFlags&EF_MISSILE_STICK )
9612 {//caught a sticky in-air
9613 push_list[x]->s.eType = ET_MISSILE;
9614 push_list[x]->s.eFlags &= ~EF_MISSILE_STICK;
9615 push_list[x]->s.eFlags |= EF_BOUNCE_HALF;
9616 push_list[x]->splashDamage /= 3;
9617 push_list[x]->splashRadius /= 3;
9618 push_list[x]->e_ThinkFunc = thinkF_WP_Explode;
9619 push_list[x]->nextthink = level.time + Q_irand( 500, 3000 );
9620 }
9621 if ( dot >= 0 )
9622 {//it's heading towards me
9623 G_ReflectMissile( self, push_list[x], forward );
9624 }
9625 else
9626 {
9627 VectorScale( push_list[x]->s.pos.trDelta, 1.25f, push_list[x]->s.pos.trDelta );
9628 }
9629 //deflect sound
9630 //G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) );
9631 //push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9632 }
9633 if ( push_list[x]->s.eType == ET_MISSILE
9634 && push_list[x]->s.weapon == WP_ROCKET_LAUNCHER
9635 && push_list[x]->damage < 60 )
9636 {//pushing away a rocket raises it's damage to the max for NPCs
9637 push_list[x]->damage = 60;
9638 }
9639 }
9640 else if ( push_list[x]->svFlags & SVF_GLASS_BRUSH )
9641 {//break the glass
9642 trace_t tr;
9643 vec3_t pushDir;
9644 float damage = 800;
9645
9646 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
9647 VectorNormalize( forward );
9648 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
9649 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
9650 if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
9651 {//must be pointing right at it
9652 continue;
9653 }
9654
9655 if ( pull )
9656 {
9657 VectorSubtract( self->client->renderInfo.eyePoint, tr.endpos, pushDir );
9658 }
9659 else
9660 {
9661 VectorSubtract( tr.endpos, self->client->renderInfo.eyePoint, pushDir );
9662 }
9663 /*
9664 VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
9665 VectorMA( push_list[x]->absmin, 0.5, size, center );
9666 if ( pull )
9667 {
9668 VectorSubtract( self->client->renderInfo.eyePoint, center, pushDir );
9669 }
9670 else
9671 {
9672 VectorSubtract( center, self->client->renderInfo.eyePoint, pushDir );
9673 }
9674 */
9675 damage -= VectorNormalize( pushDir );
9676 if ( damage < 200 )
9677 {
9678 damage = 200;
9679 }
9680 VectorScale( pushDir, damage, pushDir );
9681
9682 G_Damage( push_list[x], self, self, pushDir, tr.endpos, damage, 0, MOD_UNKNOWN );
9683 }
9684 else if ( !Q_stricmp( "func_static", push_list[x]->classname ) )
9685 {//force-usable func_static
9686 if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) )
9687 {
9688 if ( push_list[x]->NPC_targetname == NULL
9689 || (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->targetname ) == 0) )
9690 {//anyone can pull it or only 1 person can push it and it's me
9691 GEntity_UseFunc( push_list[x], self, self );
9692 }
9693 }
9694 else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) )
9695 {
9696 if ( push_list[x]->NPC_targetname == NULL
9697 || (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->NPC_targetname ) == 0) )
9698 {//anyone can push it or only 1 person can push it and it's me
9699 GEntity_UseFunc( push_list[x], self, self );
9700 }
9701 }
9702 }
9703 else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2/*MOVER_FORCE_ACTIVATE*/) )
9704 {//push/pull the door
9705 vec3_t pos1, pos2;
9706
9707 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
9708 VectorNormalize( forward );
9709 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
9710 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
9711 if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
9712 {//must be pointing right at it
9713 continue;
9714 }
9715
9716 if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) )
9717 {//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
9718 VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
9719 VectorMA( push_list[x]->absmin, 0.5, size, center );
9720 if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 )
9721 {//if at pos1 and started open, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
9722 VectorSubtract( center, push_list[x]->pos1, center );
9723 }
9724 else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 )
9725 {//if at pos2, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2
9726 VectorSubtract( center, push_list[x]->pos2, center );
9727 }
9728 VectorAdd( center, push_list[x]->pos1, pos1 );
9729 VectorAdd( center, push_list[x]->pos2, pos2 );
9730 }
9731 else
9732 {//actually has an origin, pos1 and pos2 are absolute
9733 VectorCopy( push_list[x]->currentOrigin, center );
9734 VectorCopy( push_list[x]->pos1, pos1 );
9735 VectorCopy( push_list[x]->pos2, pos2 );
9736 }
9737
9738 if ( Distance( pos1, self->client->renderInfo.eyePoint ) < Distance( pos2, self->client->renderInfo.eyePoint ) )
9739 {//pos1 is closer
9740 if ( push_list[x]->moverState == MOVER_POS1 )
9741 {//at the closest pos
9742 if ( pull )
9743 {//trying to pull, but already at closest point, so screw it
9744 continue;
9745 }
9746 }
9747 else if ( push_list[x]->moverState == MOVER_POS2 )
9748 {//at farthest pos
9749 if ( !pull )
9750 {//trying to push, but already at farthest point, so screw it
9751 continue;
9752 }
9753 }
9754 }
9755 else
9756 {//pos2 is closer
9757 if ( push_list[x]->moverState == MOVER_POS1 )
9758 {//at the farthest pos
9759 if ( !pull )
9760 {//trying to push, but already at farthest point, so screw it
9761 continue;
9762 }
9763 }
9764 else if ( push_list[x]->moverState == MOVER_POS2 )
9765 {//at closest pos
9766 if ( pull )
9767 {//trying to pull, but already at closest point, so screw it
9768 continue;
9769 }
9770 }
9771 }
9772 GEntity_UseFunc( push_list[x], self, self );
9773 }
9774 else if ( push_list[x]->s.eType == ET_MISSILE/*thermal resting on ground*/
9775 || push_list[x]->s.eType == ET_ITEM
9776 || push_list[x]->e_ThinkFunc == thinkF_G_RunObject || Q_stricmp( "limb", push_list[x]->classname ) == 0 )
9777 {//general object, toss it
9778 vec3_t pushDir, kvel;
9779 float knockback = pull?0:200;
9780 float mass = 200;
9781
9782 if ( pull )
9783 {
9784 if ( push_list[x]->s.eType == ET_ITEM )
9785 {//pull it to a little higher point
9786 vec3_t adjustedOrg;
9787 VectorCopy( self->currentOrigin, adjustedOrg );
9788 adjustedOrg[2] += self->maxs[2]/3;
9789 VectorSubtract( adjustedOrg, push_list[x]->currentOrigin, pushDir );
9790 }
9791 else if ( self->enemy //I have an enemy
9792 //&& push_list[x]->s.eType != ET_ITEM //not an item
9793 && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater
9794 && InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me
9795 && InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me
9796 && !InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, -0.25f)//object is generally behind enemy
9797 //FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy?
9798 && ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)<self->NPC->rank) )//NPC with enough skill
9799 ||( self->s.number<MAX_CLIENTS ) )
9800 )
9801 {//if I have an auto-enemy & he's in front of me, push it toward him!
9802 /*
9803 if ( targetedObjectMassTotal + push_list[x]->mass > TARGETED_OBJECT_PUSH_MASS_MAX )
9804 {//already pushed too many things
9805 //FIXME: pick closest?
9806 continue;
9807 }
9808 targetedObjectMassTotal += push_list[x]->mass;
9809 */
9810 VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir );
9811 }
9812 else
9813 {
9814 VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
9815 }
9816 knockback += VectorNormalize( pushDir );
9817 if ( knockback > 200 )
9818 {
9819 knockback = 200;
9820 }
9821 if ( push_list[x]->s.eType == ET_ITEM
9822 && push_list[x]->item
9823 && push_list[x]->item->giType == IT_HOLDABLE
9824 && push_list[x]->item->giTag == INV_SECURITY_KEY )
9825 {//security keys are pulled with less enthusiasm
9826 if ( knockback > 100 )
9827 {
9828 knockback = 100;
9829 }
9830 }
9831 else if ( knockback > 200 )
9832 {
9833 knockback = 200;
9834 }
9835 }
9836 else
9837 {
9838 if ( self->enemy //I have an enemy
9839 && push_list[x]->s.eType != ET_ITEM //not an item
9840 && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater
9841 && InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me
9842 && InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me
9843 && InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, 0.25f)//object is generally in front of enemy
9844 //FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy?
9845 && ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)<self->NPC->rank) )//NPC with enough skill
9846 ||( self->s.number<MAX_CLIENTS ) )
9847 )
9848 {//if I have an auto-enemy & he's in front of me, push it toward him!
9849 /*
9850 if ( targetedObjectMassTotal + push_list[x]->mass > TARGETED_OBJECT_PUSH_MASS_MAX )
9851 {//already pushed too many things
9852 //FIXME: pick closest?
9853 continue;
9854 }
9855 targetedObjectMassTotal += push_list[x]->mass;
9856 */
9857 VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir );
9858 }
9859 else
9860 {
9861 VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
9862 }
9863 knockback -= VectorNormalize( pushDir );
9864 if ( knockback < 100 )
9865 {
9866 knockback = 100;
9867 }
9868 }
9869 //FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid? or check?
9870 VectorCopy( push_list[x]->currentOrigin, push_list[x]->s.pos.trBase );
9871 push_list[x]->s.pos.trTime = level.time; // move a bit on the very first frame
9872 if ( push_list[x]->s.pos.trType != TR_INTERPOLATE )
9873 {//don't do this to rolling missiles
9874 push_list[x]->s.pos.trType = TR_GRAVITY;
9875 }
9876
9877 if ( push_list[x]->e_ThinkFunc == thinkF_G_RunObject && push_list[x]->physicsBounce )
9878 {//it's a pushable misc_model_breakable, use it's mass instead of our one-size-fits-all mass
9879 mass = push_list[x]->physicsBounce;//same as push_list[x]->mass, right?
9880 }
9881 if ( mass < 50 )
9882 {//???
9883 mass = 50;
9884 }
9885 if ( g_gravity->value > 0 )
9886 {
9887 VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
9888 kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
9889 }
9890 else
9891 {
9892 VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
9893 }
9894 VectorAdd( push_list[x]->s.pos.trDelta, kvel, push_list[x]->s.pos.trDelta );
9895 if ( g_gravity->value > 0 )
9896 {
9897 if ( push_list[x]->s.pos.trDelta[2] < knockback )
9898 {
9899 push_list[x]->s.pos.trDelta[2] = knockback;
9900 }
9901 }
9902 //no trDuration?
9903 if ( push_list[x]->e_ThinkFunc != thinkF_G_RunObject )
9904 {//objects spin themselves?
9905 //spin it
9906 //FIXME: messing with roll ruins the rotational center???
9907 push_list[x]->s.apos.trTime = level.time;
9908 push_list[x]->s.apos.trType = TR_LINEAR;
9909 VectorClear( push_list[x]->s.apos.trDelta );
9910 push_list[x]->s.apos.trDelta[1] = Q_irand( -800, 800 );
9911 }
9912
9913 if ( Q_stricmp( "limb", push_list[x]->classname ) == 0 )
9914 {//make sure it runs it's physics
9915 push_list[x]->e_ThinkFunc = thinkF_LimbThink;
9916 push_list[x]->nextthink = level.time + FRAMETIME;
9917 }
9918 push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
9919 push_list[x]->forcePuller = self->s.number;//remember this regardless
9920 if ( push_list[x]->item && push_list[x]->item->giTag == INV_SECURITY_KEY )
9921 {
9922 AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
9923 }
9924 else
9925 {
9926 AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
9927 }
9928 }
9929 else if ( push_list[x]->s.weapon == WP_TURRET
9930 && !Q_stricmp( "PAS", push_list[x]->classname )
9931 && push_list[x]->s.apos.trType == TR_STATIONARY )
9932 {//a portable turret
9933 WP_KnockdownTurret( self, push_list[x] );
9934 }
9935 }
9936 }
9937 if ( pull )
9938 {
9939 if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_2 )
9940 {//at level 3, can pull multiple, so it costs more
9941 actualCost = forcePowerNeeded[FP_PULL]*ent_count;
9942 if ( actualCost > 50 )
9943 {
9944 actualCost = 50;
9945 }
9946 else if ( actualCost < cost )
9947 {
9948 actualCost = cost;
9949 }
9950 }
9951 else
9952 {
9953 actualCost = cost;
9954 }
9955 WP_ForcePowerStart( self, FP_PULL, actualCost );
9956 }
9957 else
9958 {
9959 if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
9960 {//at level 3, can push multiple, so costs more
9961 actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
9962 if ( actualCost > 50 )
9963 {
9964 actualCost = 50;
9965 }
9966 else if ( actualCost < cost )
9967 {
9968 actualCost = cost;
9969 }
9970 }
9971 else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_1 )
9972 {//at level 2, can push multiple, so costs more
9973 actualCost = floor(forcePowerNeeded[FP_PUSH]*ent_count/1.5f);
9974 if ( actualCost > 50 )
9975 {
9976 actualCost = 50;
9977 }
9978 else if ( actualCost < cost )
9979 {
9980 actualCost = cost;
9981 }
9982 }
9983 else
9984 {
9985 actualCost = cost;
9986 }
9987 WP_ForcePowerStart( self, FP_PUSH, actualCost );
9988 }
9989 }
9990 else
9991 {//didn't push or pull anything? don't penalize them too much
9992 if ( pull )
9993 {
9994 WP_ForcePowerStart( self, FP_PULL, 5 );
9995 }
9996 else
9997 {
9998 WP_ForcePowerStart( self, FP_PUSH, 5 );
9999 }
10000 }
10001 if ( pull )
10002 {
10003 if ( self->NPC )
10004 {//NPCs can push more often
10005 //FIXME: vary by rank and game skill?
10006 self->client->ps.forcePowerDebounce[FP_PULL] = level.time + 200;
10007 }
10008 else
10009 {
10010 self->client->ps.forcePowerDebounce[FP_PULL] = level.time + self->client->ps.torsoAnimTimer + 500;
10011 }
10012 }
10013 else
10014 {
10015 if ( self->NPC )
10016 {//NPCs can push more often
10017 //FIXME: vary by rank and game skill?
10018 self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + 200;
10019 }
10020 else
10021 {
10022 self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
10023 }
10024 }
10025 }
10026
WP_DebounceForceDeactivateTime(gentity_t * self)10027 void WP_DebounceForceDeactivateTime( gentity_t *self )
10028 {
10029 //FIXME: if these are interruptable, should they also drain power at a constant rate
10030 // rather than just taking one lump sum of force power upfront?
10031 if ( self && self->client )
10032 {
10033 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED)
10034 || self->client->ps.forcePowersActive&(1<<FP_PROTECT)
10035 || self->client->ps.forcePowersActive&(1<<FP_ABSORB)
10036 || self->client->ps.forcePowersActive&(1<<FP_RAGE)
10037 || self->client->ps.forcePowersActive&(1<<FP_SEE) )
10038 {//already running another power that can be manually, stopped don't debounce so long
10039 self->client->ps.forceAllowDeactivateTime = level.time + 500;
10040 }
10041 else
10042 {//not running one of the interruptable powers
10043 //FIXME: this should be shorter for force speed and rage (because of timescaling)
10044 self->client->ps.forceAllowDeactivateTime = level.time + 1500;
10045 }
10046 }
10047 }
10048
ForceSpeed(gentity_t * self,int duration)10049 void ForceSpeed( gentity_t *self, int duration )
10050 {
10051 if ( self->health <= 0 )
10052 {
10053 return;
10054 }
10055 if (self->client->ps.forceAllowDeactivateTime < level.time &&
10056 (self->client->ps.forcePowersActive & (1 << FP_SPEED)) )
10057 {//stop using it
10058 WP_ForcePowerStop( self, FP_SPEED );
10059 return;
10060 }
10061 if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) )
10062 {
10063 return;
10064 }
10065 if ( self->client->ps.saberLockTime > level.time )
10066 {//FIXME: can this be a way to break out?
10067 return;
10068 }
10069
10070 WP_DebounceForceDeactivateTime( self );
10071
10072 WP_ForcePowerStart( self, FP_SPEED, 0 );
10073 if ( duration )
10074 {
10075 self->client->ps.forcePowerDuration[FP_SPEED] = level.time + duration;
10076 }
10077 G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) );
10078 }
10079
WP_StartForceHealEffects(gentity_t * self)10080 void WP_StartForceHealEffects( gentity_t *self )
10081 {
10082 if ( self->ghoul2.size() )
10083 {
10084 if ( self->chestBolt != -1 )
10085 {
10086 G_PlayEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10087 }
10088 /*
10089 if ( self->headBolt != -1 )
10090 {
10091 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10092 }
10093 if ( self->cervicalBolt != -1 )
10094 {
10095 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10096 }
10097 if ( self->chestBolt != -1 )
10098 {
10099 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10100 }
10101 if ( self->gutBolt != -1 )
10102 {
10103 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10104 }
10105 if ( self->kneeLBolt != -1 )
10106 {
10107 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10108 }
10109 if ( self->kneeRBolt != -1 )
10110 {
10111 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10112 }
10113 if ( self->elbowLBolt != -1 )
10114 {
10115 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10116 }
10117 if ( self->elbowRBolt != -1 )
10118 {
10119 G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number, self->currentOrigin, 3000, qtrue );
10120 }
10121 */
10122 }
10123 }
10124
WP_StopForceHealEffects(gentity_t * self)10125 void WP_StopForceHealEffects( gentity_t *self )
10126 {
10127 if ( self->ghoul2.size() )
10128 {
10129 if ( self->chestBolt != -1 )
10130 {
10131 G_StopEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number );
10132 }
10133 /*
10134 if ( self->headBolt != -1 )
10135 {
10136 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number );
10137 }
10138 if ( self->cervicalBolt != -1 )
10139 {
10140 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number );
10141 }
10142 if ( self->chestBolt != -1 )
10143 {
10144 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number );
10145 }
10146 if ( self->gutBolt != -1 )
10147 {
10148 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number );
10149 }
10150 if ( self->kneeLBolt != -1 )
10151 {
10152 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number );
10153 }
10154 if ( self->kneeRBolt != -1 )
10155 {
10156 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number );
10157 }
10158 if ( self->elbowLBolt != -1 )
10159 {
10160 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number );
10161 }
10162 if ( self->elbowRBolt != -1 )
10163 {
10164 G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number );
10165 }
10166 */
10167 }
10168 }
10169
FP_MaxForceHeal(gentity_t * self)10170 int FP_MaxForceHeal( gentity_t *self )
10171 {
10172 if ( self->s.number >= MAX_CLIENTS )
10173 {
10174 return MAX_FORCE_HEAL_HARD;
10175 }
10176 switch ( g_spskill->integer )
10177 {
10178 case 0://easy
10179 return MAX_FORCE_HEAL_EASY;
10180 break;
10181 case 1://medium
10182 return MAX_FORCE_HEAL_MEDIUM;
10183 break;
10184 case 2://hard
10185 default:
10186 return MAX_FORCE_HEAL_HARD;
10187 break;
10188 }
10189 }
10190
FP_ForceHealInterval(gentity_t * self)10191 int FP_ForceHealInterval( gentity_t *self )
10192 {
10193 return (self->client->ps.forcePowerLevel[FP_HEAL]>FORCE_LEVEL_2)?50:FORCE_HEAL_INTERVAL;
10194 }
10195
ForceHeal(gentity_t * self)10196 void ForceHeal( gentity_t *self )
10197 {
10198 if ( self->health <= 0 || self->client->ps.stats[STAT_MAX_HEALTH] <= self->health )
10199 {
10200 return;
10201 }
10202
10203 if ( !WP_ForcePowerUsable( self, FP_HEAL, 20 ) )
10204 {//must have enough force power for at least 5 points of health
10205 return;
10206 }
10207
10208 if ( self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) )
10209 {//can't initiate a heal while taking pain or attacking
10210 return;
10211 }
10212
10213 if ( self->client->ps.saberLockTime > level.time )
10214 {//FIXME: can this be a way to break out?
10215 return;
10216 }
10217 /*
10218 if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
10219 {//instant heal
10220 //no more than available force power
10221 int max = self->client->ps.forcePower;
10222 if ( max > MAX_FORCE_HEAL )
10223 {//no more than max allowed
10224 max = MAX_FORCE_HEAL;
10225 }
10226 if ( max > self->client->ps.stats[STAT_MAX_HEALTH] - self->health )
10227 {//no more than what's missing
10228 max = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
10229 }
10230 self->health += max;
10231 WP_ForcePowerDrain( self, FP_HEAL, max );
10232 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
10233 }
10234 else
10235 */
10236 {
10237 //start health going up
10238 //NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE );
10239 WP_ForcePowerStart( self, FP_HEAL, 0 );
10240 if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
10241 {//must meditate
10242 //FIXME: holster weapon (select WP_NONE?)
10243 //FIXME: BOTH_FORCEHEAL_START
10244 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10245 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10246 self->client->ps.saberBlocked = BLOCKED_NONE;
10247 self->client->ps.torsoAnimTimer = self->client->ps.legsAnimTimer = FP_ForceHealInterval(self)*FP_MaxForceHeal(self) + 2000;//???
10248 WP_DeactivateSaber( self );//turn off saber when meditating
10249 }
10250 else
10251 {//just a quick gesture
10252 /*
10253 //Can't get an anim that looks good...
10254 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_QUICK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10255 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10256 self->client->ps.saberBlocked = BLOCKED_NONE;
10257 */
10258 }
10259 }
10260
10261 //FIXME: always play healing effect
10262 G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/heal.mp3" );
10263 }
10264
10265 extern void NPC_PlayConfusionSound( gentity_t *self );
10266 extern void NPC_Jedi_PlayConfusionSound( gentity_t *self );
WP_CheckBreakControl(gentity_t * self)10267 qboolean WP_CheckBreakControl( gentity_t *self )
10268 {
10269 if ( !self )
10270 {
10271 return qfalse;
10272 }
10273 if ( !self->s.number )
10274 {//player
10275 if ( self->client && self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
10276 {//control-level
10277 if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
10278 {//we are in a viewentity
10279 gentity_t *controlled = &g_entities[self->client->ps.viewEntity];
10280 if ( controlled->NPC && controlled->NPC->controlledTime > level.time )
10281 {//it is an NPC we controlled
10282 //clear it and return
10283 G_ClearViewEntity( self );
10284 return qtrue;
10285 }
10286 }
10287 }
10288 }
10289 else
10290 {//NPC
10291 if ( self->NPC && self->NPC->controlledTime > level.time )
10292 {//being controlled
10293 gentity_t *controller = &g_entities[0];
10294 if ( controller->client && controller->client->ps.viewEntity == self->s.number )
10295 {//we are being controlled by player
10296 if ( controller->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
10297 {//control-level mind trick
10298 //clear the control and return
10299 G_ClearViewEntity( controller );
10300 return qtrue;
10301 }
10302 }
10303 }
10304 }
10305 return qfalse;
10306 }
10307 extern bool Pilot_AnyVehiclesRegistered();
10308
ForceTelepathy(gentity_t * self)10309 void ForceTelepathy( gentity_t *self )
10310 {
10311 trace_t tr;
10312 vec3_t end, forward;
10313 gentity_t *traceEnt;
10314 qboolean targetLive = qfalse;
10315
10316 if ( WP_CheckBreakControl( self ) )
10317 {
10318 return;
10319 }
10320 if ( self->health <= 0 )
10321 {
10322 return;
10323 }
10324 //FIXME: if mind trick 3 and aiming at an enemy need more force power
10325 if ( !WP_ForcePowerUsable( self, FP_TELEPATHY, 0 ) )
10326 {
10327 return;
10328 }
10329
10330 if ( self->client->ps.weaponTime >= 800 )
10331 {//just did one!
10332 return;
10333 }
10334 if ( self->client->ps.saberLockTime > level.time )
10335 {//FIXME: can this be a way to break out?
10336 return;
10337 }
10338
10339 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
10340 VectorNormalize( forward );
10341 VectorMA( self->client->renderInfo.eyePoint, 2048, forward, end );
10342
10343 //Cause a distraction if enemy is not fighting
10344 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_BODY, (EG2_Collision)0, 0 );
10345 if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
10346 {
10347 return;
10348 }
10349
10350 traceEnt = &g_entities[tr.entityNum];
10351
10352 if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
10353 {
10354 return;
10355 }
10356
10357 if ( traceEnt && traceEnt->client )
10358 {
10359 switch ( traceEnt->client->NPC_class )
10360 {
10361 case CLASS_GALAKMECH://cant grip him, he's in armor
10362 case CLASS_ATST://much too big to grip!
10363 //no droids either
10364 case CLASS_PROBE:
10365 case CLASS_GONK:
10366 case CLASS_R2D2:
10367 case CLASS_R5D2:
10368 case CLASS_MARK1:
10369 case CLASS_MARK2:
10370 case CLASS_MOUSE:
10371 case CLASS_SEEKER:
10372 case CLASS_REMOTE:
10373 case CLASS_PROTOCOL:
10374 case CLASS_ASSASSIN_DROID:
10375 case CLASS_SABER_DROID:
10376 case CLASS_BOBAFETT:
10377 break;
10378 case CLASS_RANCOR:
10379 if ( !(traceEnt->spawnflags&1) )
10380 {
10381 targetLive = qtrue;
10382 }
10383 break;
10384 default:
10385 targetLive = qtrue;
10386 break;
10387 }
10388 }
10389 if ( targetLive
10390 && traceEnt->NPC
10391 && traceEnt->health > 0 )
10392 {//hit an organic non-player
10393 if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) )
10394 {//activated a script on him
10395 //FIXME: do the visual sparkles effect on their heads, still?
10396 WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
10397 }
10398 else if ( traceEnt->client->playerTeam != self->client->playerTeam )
10399 {//an enemy
10400 int override = 0;
10401 if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) )
10402 {
10403 if ( traceEnt->client->NPC_class == CLASS_GALAKMECH )
10404 {
10405 G_AddVoiceEvent( traceEnt, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), Q_irand( 3000, 5000 ) );
10406 }
10407 }
10408 else if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
10409 {//control them, even jedi
10410 G_SetViewEntity( self, traceEnt );
10411 traceEnt->NPC->controlledTime = level.time + 30000;
10412 }
10413 else if ( traceEnt->s.weapon != WP_SABER
10414 && traceEnt->client->NPC_class != CLASS_REBORN )
10415 {//haha! Jedi aren't easily confused!
10416 if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2
10417 && traceEnt->s.weapon != WP_NONE //don't charm people who aren't capable of fighting... like ugnaughts and droids, just confuse them
10418 && traceEnt->client->NPC_class != CLASS_TUSKEN//don't charm them, just confuse them
10419 && traceEnt->client->NPC_class != CLASS_NOGHRI//don't charm them, just confuse them
10420 && !Pilot_AnyVehiclesRegistered() //also, don't charm guys when bikes are near
10421 )
10422 {//turn them to our side
10423 //if mind trick 3 and aiming at an enemy need more force power
10424 override = 50;
10425 if ( self->client->ps.forcePower < 50 )
10426 {
10427 return;
10428 }
10429 if ( traceEnt->enemy )
10430 {
10431 G_ClearEnemy( traceEnt );
10432 }
10433 if ( traceEnt->NPC )
10434 {
10435 //traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
10436 traceEnt->client->leader = self;
10437 }
10438 //FIXME: maybe pick an enemy right here?
10439 //FIXME: does nothing to TEAM_FREE and TEAM_NEUTRALs!!!
10440 team_t saveTeam = traceEnt->client->enemyTeam;
10441 traceEnt->client->enemyTeam = traceEnt->client->playerTeam;
10442 traceEnt->client->playerTeam = saveTeam;
10443 //FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
10444 traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];
10445 if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 )
10446 {//FIXME: what if already playing effect?
10447 G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue );
10448 }
10449 }
10450 else
10451 {//just confuse them
10452 //somehow confuse them? Set don't fire to true for a while? Drop their aggression? Maybe just take their enemy away and don't let them pick one up for a while unless shot?
10453 traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds
10454 if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 )
10455 {//FIXME: what if already playing effect?
10456 G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue );
10457 }
10458 NPC_PlayConfusionSound( traceEnt );
10459 if ( traceEnt->enemy )
10460 {
10461 G_ClearEnemy( traceEnt );
10462 }
10463 }
10464 }
10465 else
10466 {
10467 NPC_Jedi_PlayConfusionSound( traceEnt );
10468 }
10469 WP_ForcePowerStart( self, FP_TELEPATHY, override );
10470 }
10471 else if ( traceEnt->client->playerTeam == self->client->playerTeam )
10472 {//an ally
10473 //maybe just have him look at you? Respond? Take your enemy?
10474 if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) )
10475 {
10476 NPC_UseResponse( traceEnt, self, qfalse );
10477 WP_ForcePowerStart( self, FP_TELEPATHY, 1 );
10478 }
10479 }//NOTE: no effect on TEAM_NEUTRAL?
10480 vec3_t eyeDir;
10481 AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL );
10482 VectorNormalize( eyeDir );
10483 G_PlayEffect( "force/force_touch", traceEnt->client->renderInfo.eyePoint, eyeDir );
10484
10485 //make sure this plays and that you cannot press fire for about 1 second after this
10486 //FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
10487 NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
10488 //FIXME: build-up or delay this until in proper part of anim
10489 }
10490 else
10491 {
10492 if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr.fraction * 2048 > 64 )
10493 {//don't create a diversion less than 64 from you of if at power level 1
10494 //use distraction anim instead
10495 G_PlayEffect( G_EffectIndex( "force/force_touch" ), tr.endpos, tr.plane.normal );
10496 //FIXME: these events don't seem to always be picked up...?
10497 AddSoundEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue, qtrue );
10498 AddSightEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, 50 );
10499 WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
10500 }
10501 NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
10502 }
10503 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10504 self->client->ps.saberBlocked = BLOCKED_NONE;
10505 self->client->ps.weaponTime = 1000;
10506 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
10507 {
10508 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
10509 }
10510 }
10511
10512 //rww - RAGDOLL_BEGIN
10513 //#define JK2_RAGDOLL_GRIPNOHEALTH
10514 //rww - RAGDOLL_END
10515
ForceGrip(gentity_t * self)10516 void ForceGrip( gentity_t *self )
10517 {//FIXME: make enemy Jedi able to use this
10518 trace_t tr;
10519 vec3_t end, forward;
10520 gentity_t *traceEnt = NULL;
10521
10522 if ( self->health <= 0 )
10523 {
10524 return;
10525 }
10526 if ( !self->s.number && (cg.zoomMode || in_camera) )
10527 {//can't force grip when zoomed in or in cinematic
10528 return;
10529 }
10530 if ( self->client->ps.leanofs )
10531 {//can't force-grip while leaning
10532 return;
10533 }
10534
10535 if ( self->client->ps.forceGripEntityNum <= ENTITYNUM_WORLD )
10536 {//already gripping
10537 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
10538 {
10539 self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 100;
10540 self->client->ps.weaponTime = 1000;
10541 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
10542 {
10543 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
10544 }
10545 }
10546 return;
10547 }
10548
10549 if ( !WP_ForcePowerUsable( self, FP_GRIP, 0 ) )
10550 {//can't use it right now
10551 return;
10552 }
10553
10554 if ( self->client->ps.forcePower < 26 )
10555 {//need 20 to start, 6 to hold it for any decent amount of time...
10556 return;
10557 }
10558
10559 if ( self->client->ps.weaponTime )
10560 {//busy
10561 return;
10562 }
10563
10564 if ( self->client->ps.saberLockTime > level.time )
10565 {//FIXME: can this be a way to break out?
10566 return;
10567 }
10568 //Cause choking anim + health drain in ent in front of me
10569 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10570 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10571 self->client->ps.saberBlocked = BLOCKED_NONE;
10572
10573 self->client->ps.weaponTime = 1000;
10574 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
10575 {
10576 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
10577 }
10578
10579 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
10580 VectorNormalize( forward );
10581 VectorMA( self->client->renderInfo.handLPoint, FORCE_GRIP_DIST, forward, end );
10582
10583 if ( self->enemy )
10584 {//I have an enemy
10585 if ( !self->enemy->message
10586 && !(self->flags&FL_NO_KNOCKBACK) )
10587 {//don't auto-pickup guys with keys
10588 if ( DistanceSquared( self->enemy->currentOrigin, self->currentOrigin ) < FORCE_GRIP_DIST_SQUARED )
10589 {//close enough to grab
10590 float minDot = 0.5f;
10591 if ( self->s.number < MAX_CLIENTS )
10592 {//player needs to be facing more directly
10593 minDot = 0.2f;
10594 }
10595 if ( InFront( self->enemy->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, minDot ) ) //self->s.number || //NPCs can always lift enemy since we assume they're looking at them...?
10596 {//need to be facing the enemy
10597 if ( gi.inPVS( self->enemy->currentOrigin, self->client->renderInfo.eyePoint ) )
10598 {//must be in PVS
10599 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, self->enemy->currentOrigin, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
10600 if ( tr.fraction == 1.0f || tr.entityNum == self->enemy->s.number )
10601 {//must have clear LOS
10602 traceEnt = self->enemy;
10603 }
10604 }
10605 }
10606 }
10607 }
10608 }
10609 if ( !traceEnt )
10610 {//okay, trace straight ahead and see what's there
10611 gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
10612 if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
10613 {
10614 return;
10615 }
10616
10617 traceEnt = &g_entities[tr.entityNum];
10618 }
10619 //rww - RAGDOLL_BEGIN
10620 #ifdef JK2_RAGDOLL_GRIPNOHEALTH
10621 if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
10622 {
10623 return;
10624 }
10625 #else
10626 //rww - RAGDOLL_END
10627 if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
10628 {
10629 return;
10630 }
10631 //rww - RAGDOLL_BEGIN
10632 #endif
10633 //rww - RAGDOLL_END
10634
10635 if ( traceEnt->m_pVehicle != NULL )
10636 {//is it a vehicle
10637 //grab pilot if there is one
10638 if ( traceEnt->m_pVehicle->m_pPilot != NULL
10639 && traceEnt->m_pVehicle->m_pPilot->client != NULL )
10640 {//grip the pilot
10641 traceEnt = traceEnt->m_pVehicle->m_pPilot;
10642 }
10643 else
10644 {//can't grip a vehicle
10645 return;
10646 }
10647 }
10648 if ( traceEnt->client )
10649 {
10650 if ( traceEnt->client->ps.forceJumpZStart )
10651 {//can't catch them in mid force jump - FIXME: maybe base it on velocity?
10652 return;
10653 }
10654 if ( traceEnt->client->ps.pullAttackTime > level.time )
10655 {//can't grip someone who is being pull-attacked or is pull-attacking
10656 return;
10657 }
10658 if ( !Q_stricmp("Yoda",traceEnt->NPC_type) )
10659 {
10660 Jedi_PlayDeflectSound( traceEnt );
10661 ForceThrow( traceEnt, qfalse );
10662 return;
10663 }
10664
10665 if ( G_IsRidingVehicle( traceEnt )
10666 && (traceEnt->s.eFlags&EF_NODRAW) )
10667 {//riding *inside* vehicle
10668 return;
10669 }
10670
10671 switch ( traceEnt->client->NPC_class )
10672 {
10673 case CLASS_GALAKMECH://cant grip him, he's in armor
10674 G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
10675 return;
10676 break;
10677 case CLASS_HAZARD_TROOPER://cant grip him, he's in armor
10678 return;
10679 break;
10680 case CLASS_ATST://much too big to grip!
10681 case CLASS_RANCOR://much too big to grip!
10682 case CLASS_WAMPA://much too big to grip!
10683 case CLASS_SAND_CREATURE://much too big to grip!
10684 return;
10685 break;
10686 //no droids either...?
10687 case CLASS_GONK:
10688 case CLASS_R2D2:
10689 case CLASS_R5D2:
10690 case CLASS_MARK1:
10691 case CLASS_MARK2:
10692 case CLASS_MOUSE://?
10693 case CLASS_PROTOCOL:
10694 //*sigh*... in JK3, you'll be able to grab and move *anything*...
10695 return;
10696 break;
10697 //not even combat droids? (No animation for being gripped...)
10698 case CLASS_SABER_DROID:
10699 case CLASS_ASSASSIN_DROID:
10700 //*sigh*... in JK3, you'll be able to grab and move *anything*...
10701 return;
10702 break;
10703 case CLASS_PROBE:
10704 case CLASS_SEEKER:
10705 case CLASS_REMOTE:
10706 case CLASS_SENTRY:
10707 case CLASS_INTERROGATOR:
10708 //*sigh*... in JK3, you'll be able to grab and move *anything*...
10709 return;
10710 break;
10711 case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
10712 case CLASS_KYLE:
10713 case CLASS_TAVION:
10714 case CLASS_LUKE:
10715 Jedi_PlayDeflectSound( traceEnt );
10716 ForceThrow( traceEnt, qfalse );
10717 return;
10718 break;
10719 case CLASS_REBORN:
10720 case CLASS_SHADOWTROOPER:
10721 case CLASS_ALORA:
10722 case CLASS_JEDI:
10723 if ( traceEnt->NPC && traceEnt->NPC->rank > RANK_CIVILIAN && self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
10724 {
10725 Jedi_PlayDeflectSound( traceEnt );
10726 ForceThrow( traceEnt, qfalse );
10727 return;
10728 }
10729 break;
10730 default:
10731 break;
10732 }
10733 if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
10734 {//FIXME: maybe can pull them out?
10735 return;
10736 }
10737 if ( self->enemy && traceEnt != self->enemy && traceEnt->client->playerTeam == self->client->playerTeam )
10738 {//can't accidently grip your teammate in combat
10739 return;
10740 }
10741 //=CHECKABSORB===
10742 if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_GRIP]]) )
10743 {
10744 //WP_ForcePowerStop( self, FP_GRIP );
10745 return;
10746 }
10747 //===============
10748 }
10749 else
10750 {//can't grip non-clients... right?
10751 //FIXME: Make it so objects flagged as "grabbable" are let through
10752 //if ( Q_stricmp( "misc_model_breakable", traceEnt->classname ) || !(traceEnt->s.eFlags&EF_BOUNCE_HALF) || !traceEnt->physicsBounce )
10753 {
10754 return;
10755 }
10756 }
10757
10758 // Make sure to turn off Force Protection and Force Absorb.
10759 if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
10760 {
10761 WP_ForcePowerStop( self, FP_PROTECT );
10762 }
10763 if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
10764 {
10765 WP_ForcePowerStop( self, FP_ABSORB );
10766 }
10767
10768 WP_ForcePowerStart( self, FP_GRIP, 20 );
10769 //FIXME: rule out other things?
10770 //FIXME: Jedi resist, like the push and pull?
10771 self->client->ps.forceGripEntityNum = traceEnt->s.number;
10772 if ( traceEnt->client )
10773 {
10774 Vehicle_t *pVeh;
10775 if ( ( pVeh = G_IsRidingVehicle( traceEnt ) ) != NULL )
10776 {//riding vehicle? pull him off!
10777 //FIXME: if in an AT-ST or X-Wing, shouldn't do this... :)
10778 //pull him off of it
10779 //((CVehicleNPC *)traceEnt->NPC)->Eject( traceEnt );
10780 pVeh->m_pVehicleInfo->Eject( pVeh, traceEnt, qtrue );
10781 //G_DriveVehicle( traceEnt, NULL, NULL );
10782 }
10783 G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
10784 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 || traceEnt->s.weapon == WP_SABER )
10785 {//if we pick up & carry, drop their weap
10786 if ( traceEnt->s.weapon
10787 && traceEnt->client->NPC_class != CLASS_ROCKETTROOPER
10788 && traceEnt->client->NPC_class != CLASS_VEHICLE
10789 && traceEnt->client->NPC_class != CLASS_HAZARD_TROOPER
10790 && traceEnt->client->NPC_class != CLASS_TUSKEN
10791 && traceEnt->client->NPC_class != CLASS_BOBAFETT
10792 && traceEnt->client->NPC_class != CLASS_ASSASSIN_DROID
10793 && traceEnt->s.weapon != WP_CONCUSSION // so rax can't drop his
10794 )
10795 {
10796 if ( traceEnt->client->NPC_class == CLASS_BOBAFETT )
10797 {//he doesn't drop them, just puts it away
10798 ChangeWeapon( traceEnt, WP_MELEE );
10799 }
10800 else if ( traceEnt->s.weapon == WP_MELEE )
10801 {//they can't take that away from me, oh no...
10802 }
10803 else if ( traceEnt->NPC
10804 && (traceEnt->NPC->scriptFlags&SCF_DONT_FLEE) )
10805 {//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it
10806 }
10807 else if ( traceEnt->s.weapon != WP_SABER )
10808 {
10809 WP_DropWeapon( traceEnt, NULL );
10810 }
10811 else
10812 {
10813 //turn it off?
10814 traceEnt->client->ps.SaberDeactivate();
10815 G_SoundOnEnt( traceEnt, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" );
10816 }
10817 }
10818 }
10819 //else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun
10820 VectorCopy( traceEnt->client->renderInfo.headPoint, self->client->ps.forceGripOrg );
10821 }
10822 else
10823 {
10824 VectorCopy( traceEnt->currentOrigin, self->client->ps.forceGripOrg );
10825 }
10826 self->client->ps.forceGripOrg[2] += 48;//FIXME: define?
10827 if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
10828 {//just a duration
10829 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
10830 self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 5000;
10831
10832 if ( self->m_pVehicle && self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) )
10833 {//empty vehicles don't make gripped noise
10834 traceEnt->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
10835 }
10836 }
10837 else
10838 {
10839 if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 )
10840 {//lifting sound? or always?
10841 }
10842 //if ( traceEnt->s.number )
10843 {//picks them up for a second first
10844 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 1000;
10845 }
10846 /*
10847 else
10848 {//player should take damage right away
10849 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
10850 }
10851 */
10852 // force grip sound should only play when the target is alive?
10853 // if (traceEnt->health>0)
10854 // {
10855 self->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
10856 // }
10857 }
10858 }
10859
ForceLightningCheck2Handed(gentity_t * self)10860 qboolean ForceLightningCheck2Handed( gentity_t *self )
10861 {
10862 if ( self && self->client )
10863 {
10864 if ( self->s.weapon == WP_NONE
10865 || self->s.weapon == WP_MELEE
10866 || (self->s.weapon == WP_SABER && !self->client->ps.SaberActive()) )
10867 {
10868 return qtrue;
10869 }
10870 }
10871 return qfalse;
10872 }
10873
ForceLightningAnim(gentity_t * self)10874 void ForceLightningAnim( gentity_t *self )
10875 {
10876 if ( !self || !self->client )
10877 {
10878 return;
10879 }
10880
10881 //one-handed lightning 2 and above
10882 int startAnim = BOTH_FORCELIGHTNING_START;
10883 int holdAnim = BOTH_FORCELIGHTNING_HOLD;
10884
10885 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] >= FORCE_LEVEL_3
10886 && ForceLightningCheck2Handed( self ) )
10887 {//empty handed lightning 3
10888 startAnim = BOTH_FORCE_2HANDEDLIGHTNING_START;
10889 holdAnim = BOTH_FORCE_2HANDEDLIGHTNING_HOLD;
10890 }
10891
10892 //FIXME: if standing still, play on whole body? Especially 2-handed version
10893 if ( self->client->ps.torsoAnim == startAnim )
10894 {
10895 if ( !self->client->ps.torsoAnimTimer )
10896 {
10897 NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10898 }
10899 else
10900 {
10901 NPC_SetAnim( self, SETANIM_TORSO, startAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10902 }
10903 }
10904 else
10905 {
10906 NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10907 }
10908 }
10909
ForceLightning(gentity_t * self)10910 void ForceLightning( gentity_t *self )
10911 {
10912 if ( self->health <= 0 )
10913 {
10914 return;
10915 }
10916 if ( !self->s.number && (cg.zoomMode || in_camera) )
10917 {//can't force lightning when zoomed in or in cinematic
10918 return;
10919 }
10920 if ( self->client->ps.leanofs )
10921 {//can't force-lightning while leaning
10922 return;
10923 }
10924 if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING, 0 ) )
10925 {
10926 return;
10927 }
10928 if ( self->client->ps.forcePowerDebounce[FP_LIGHTNING] > level.time )
10929 {//stops it while using it and also after using it, up to 3 second delay
10930 return;
10931 }
10932 if ( self->client->ps.saberLockTime > level.time )
10933 {//FIXME: can this be a way to break out?
10934 return;
10935 }
10936 // Make sure to turn off Force Protection and Force Absorb.
10937 if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
10938 {
10939 WP_ForcePowerStop( self, FP_PROTECT );
10940 }
10941 if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
10942 {
10943 WP_ForcePowerStop( self, FP_ABSORB );
10944 }
10945 //Shoot lightning from hand
10946 //make sure this plays and that you cannot press fire for about 1 second after this
10947 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
10948 {
10949 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10950 }
10951 else
10952 {
10953 ForceLightningAnim( self );
10954 /*
10955 if ( ForceLightningCheck2Handed( self ) )
10956 {//empty handed lightning 3
10957 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10958 }
10959 else
10960 {//one-handed lightning 3
10961 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
10962 }
10963 */
10964 }
10965 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
10966 self->client->ps.saberBlocked = BLOCKED_NONE;
10967
10968 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
10969 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
10970 {//short burst
10971 //G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
10972 }
10973 else
10974 {//holding it
10975 self->s.loopSound = G_SoundIndex( "sound/weapons/force/lightning2.wav" );
10976 }
10977
10978 //FIXME: build-up or delay this until in proper part of anim
10979 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
10980 WP_ForcePowerStart( self, FP_LIGHTNING, self->client->ps.torsoAnimTimer );
10981 }
10982
ForceLightningDamage(gentity_t * self,gentity_t * traceEnt,vec3_t dir,float dist,float dot,vec3_t impactPoint)10983 void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, float dist, float dot, vec3_t impactPoint )
10984 {
10985 if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
10986 {
10987 return;
10988 }
10989
10990 if ( traceEnt && traceEnt->takedamage )
10991 {
10992 if ( !traceEnt->client || traceEnt->client->playerTeam != self->client->playerTeam || self->enemy == traceEnt || traceEnt->enemy == self )
10993 {//an enemy or object
10994 int dmg;
10995 //FIXME: check for client using FP_ABSORB
10996 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
10997 {//more damage if closer and more in front
10998 dmg = 1;
10999 if ( self->client->NPC_class == CLASS_REBORN
11000 && self->client->ps.weapon == WP_NONE )
11001 {//Cultist: looks fancy, but does less damage
11002 }
11003 else
11004 {
11005 if ( dist < 100 )
11006 {
11007 dmg += 2;
11008 }
11009 else if ( dist < 200 )
11010 {
11011 dmg += 1;
11012 }
11013 if ( dot > 0.9f )
11014 {
11015 dmg += 2;
11016 }
11017 else if ( dot > 0.7f )
11018 {
11019 dmg += 1;
11020 }
11021 }
11022 if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING
11023 || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START
11024 || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
11025 || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE )
11026 {//jackin' 'em up, Palpatine-style
11027 dmg *= 2;
11028 }
11029 }
11030 else
11031 {
11032 dmg = Q_irand( 1, 3 );//*self->client->ps.forcePowerLevel[FP_LIGHTNING];
11033 }
11034
11035 if ( traceEnt->client
11036 && traceEnt->health > 0
11037 && traceEnt->NPC
11038 && (traceEnt->NPC->aiFlags&NPCAI_BOSS_CHARACTER) )
11039 {//Luke, Desann Tavion and Kyle can shield themselves from the attack
11040 //FIXME: shield effect or something?
11041 int parts;
11042 if ( traceEnt->client->ps.groundEntityNum != ENTITYNUM_NONE && !PM_SpinningSaberAnim( traceEnt->client->ps.legsAnim ) && !PM_FlippingAnim( traceEnt->client->ps.legsAnim ) && !PM_RollingAnim( traceEnt->client->ps.legsAnim ) )
11043 {//if on a surface and not in a spin or flip, play full body resist
11044 parts = SETANIM_BOTH;
11045 }
11046 else
11047 {//play resist just in torso
11048 parts = SETANIM_TORSO;
11049 }
11050 //FIXME: don't interrupt big anims with this!
11051 NPC_SetAnim( traceEnt, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11052 Jedi_PlayDeflectSound( traceEnt );
11053 dmg = Q_irand(0,1);
11054 }
11055 else if ( traceEnt->s.weapon == WP_SABER )
11056 {//saber can block lightning
11057 if ( traceEnt->client //a client
11058 && !traceEnt->client->ps.saberInFlight//saber in hand
11059 && ( traceEnt->client->ps.saberMove == LS_READY || PM_SaberInParry( traceEnt->client->ps.saberMove ) || PM_SaberInReturn( traceEnt->client->ps.saberMove ) )//not attacking with saber
11060 && InFOV( self->currentOrigin, traceEnt->currentOrigin, traceEnt->client->ps.viewangles, 20, 35 ) //I'm in front of them
11061 && !PM_InKnockDown( &traceEnt->client->ps ) //they're not in a knockdown
11062 && !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim )
11063 && !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim )
11064 && !PM_SaberInSpecialAttack( traceEnt->client->ps.torsoAnim )
11065 && !PM_InSpecialJump( traceEnt->client->ps.torsoAnim )
11066 && (!traceEnt->s.number||(traceEnt->NPC&&traceEnt->NPC->rank>=RANK_LT_COMM)) )//the player or a tough jedi/reborn
11067 {
11068 if ( Q_irand( 0, traceEnt->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*3 ) > 0 )//more of a chance of defending if saber defense is high
11069 {
11070 dmg = 0;
11071 }
11072 if ( (traceEnt->client->ps.forcePowersActive&(1<<FP_ABSORB))
11073 && traceEnt->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 )
11074 {//no parry, just absorb
11075 }
11076 else
11077 {
11078 //make them do a parry
11079 traceEnt->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
11080 int parryReCalcTime = Jedi_ReCalcParryTime( traceEnt, EVASION_PARRY );
11081 if ( traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
11082 {
11083 traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
11084 }
11085 traceEnt->client->ps.weaponTime = Q_irand( 100, 300 );//hold this move - can't attack! - FIXME: unless dual sabers?
11086 }
11087 }
11088 else if ( Q_irand( 0, 1 ) )
11089 {//jedi less likely to be damaged
11090 dmg = 0;
11091 }
11092 else
11093 {
11094 dmg = 1;
11095 }
11096 }
11097 if ( traceEnt && traceEnt->client && traceEnt->client->ps.powerups[PW_GALAK_SHIELD] )
11098 {
11099 //has shield up
11100 dmg = 0;
11101 }
11102 int modPowerLevel = -1;
11103
11104 if (traceEnt->client)
11105 {
11106 modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_LIGHTNING, self->client->ps.forcePowerLevel[FP_LIGHTNING], 1);
11107 }
11108
11109 if (modPowerLevel != -1)
11110 {
11111 if ( !modPowerLevel )
11112 {
11113 dmg = 0;
11114 }
11115 else if ( modPowerLevel == 1 )
11116 {
11117 dmg = floor((float)dmg/4.0f);
11118 }
11119 else if ( modPowerLevel == 2 )
11120 {
11121 dmg = floor((float)dmg/2.0f);
11122 }
11123 }
11124 //FIXME: if ForceDrain, sap force power and add health to self, use different sound & effects
11125 if ( dmg )
11126 {
11127 G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_LIGHTNING );
11128 }
11129 if ( traceEnt->client )
11130 {
11131 if ( !Q_irand( 0, 2 ) )
11132 {
11133 G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/force/lightninghit%d.wav", Q_irand( 1, 3 ) ) ) );
11134 }
11135 traceEnt->s.powerups |= ( 1 << PW_SHOCKED );
11136
11137 // If we are dead or we are a bot, we can do the full version
11138 class_t npc_class = traceEnt->client->NPC_class;
11139 if ( traceEnt->health <= 0 || ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE ||
11140 npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_REMOTE ||
11141 npc_class == CLASS_R5D2 || npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 ||
11142 npc_class == CLASS_MARK2 || npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST ) ||
11143 npc_class == CLASS_SENTRY )
11144 {
11145 traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 4000;
11146 }
11147 else //short version
11148 {
11149 traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 500;
11150 }
11151 }
11152 }
11153 }
11154 }
11155
ForceShootLightning(gentity_t * self)11156 void ForceShootLightning( gentity_t *self )
11157 {
11158 trace_t tr;
11159 vec3_t end, forward;
11160 gentity_t *traceEnt;
11161
11162 if ( self->health <= 0 )
11163 {
11164 return;
11165 }
11166 if ( !self->s.number && cg.zoomMode )
11167 {//can't force lightning when zoomed in
11168 return;
11169 }
11170
11171 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
11172 VectorNormalize( forward );
11173
11174 //FIXME: if lightning hits water, do water-only-flagged radius damage from that point
11175 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
11176 {//arc
11177 vec3_t center, mins, maxs, dir, ent_org, size, v;
11178 float radius = 512, dot, dist;
11179 gentity_t *entityList[MAX_GENTITIES];
11180 int e, numListedEntities, i;
11181
11182 VectorCopy( self->currentOrigin, center );
11183 for ( i = 0 ; i < 3 ; i++ )
11184 {
11185 mins[i] = center[i] - radius;
11186 maxs[i] = center[i] + radius;
11187 }
11188 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
11189
11190 for ( e = 0 ; e < numListedEntities ; e++ )
11191 {
11192 traceEnt = entityList[e];
11193
11194 if ( !traceEnt )
11195 continue;
11196 if ( traceEnt == self )
11197 continue;
11198 if ( traceEnt->owner == self && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals
11199 continue;
11200 if ( !traceEnt->inuse )
11201 continue;
11202 if ( !traceEnt->takedamage )
11203 continue;
11204 /*
11205 if ( traceEnt->health <= 0 )//no torturing corpses
11206 continue;
11207 */
11208 //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
11209 // find the distance from the edge of the bounding box
11210 for ( i = 0 ; i < 3 ; i++ )
11211 {
11212 if ( center[i] < traceEnt->absmin[i] )
11213 {
11214 v[i] = traceEnt->absmin[i] - center[i];
11215 } else if ( center[i] > traceEnt->absmax[i] )
11216 {
11217 v[i] = center[i] - traceEnt->absmax[i];
11218 } else
11219 {
11220 v[i] = 0;
11221 }
11222 }
11223
11224 VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
11225 VectorMA( traceEnt->absmin, 0.5, size, ent_org );
11226
11227 //see if they're in front of me
11228 //must be within the forward cone
11229 VectorSubtract( ent_org, center, dir );
11230 VectorNormalize( dir );
11231 if ( (dot = DotProduct( dir, forward )) < 0.5 )
11232 continue;
11233
11234 //must be close enough
11235 dist = VectorLength( v );
11236 if ( dist >= radius )
11237 {
11238 continue;
11239 }
11240
11241 //in PVS?
11242 if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
11243 {//must be in PVS
11244 continue;
11245 }
11246
11247 //Now check and see if we can actually hit it
11248 gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
11249 if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
11250 {//must have clear LOS
11251 continue;
11252 }
11253
11254 // ok, we are within the radius, add us to the incoming list
11255 //FIXME: maybe add up the ents and do more damage the less ents there are
11256 // as if we're spreading out the damage?
11257 ForceLightningDamage( self, traceEnt, dir, dist, dot, ent_org );
11258 }
11259
11260 }
11261 else
11262 {//trace-line
11263 int ignore = self->s.number;
11264 int traces = 0;
11265 vec3_t start;
11266
11267 VectorCopy( self->client->renderInfo.handLPoint, start );
11268 VectorMA( self->client->renderInfo.handLPoint, 2048, forward, end );
11269
11270 while ( traces < 10 )
11271 {//need to loop this in case we hit a Jedi who dodges the shot
11272 gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 );
11273 if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
11274 {
11275 return;
11276 }
11277
11278 traceEnt = &g_entities[tr.entityNum];
11279 //NOTE: only NPCs do this auto-dodge
11280 if ( traceEnt
11281 && traceEnt->s.number >= MAX_CLIENTS
11282 && traceEnt->client
11283 && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
11284 {//FIXME: need a more reliable way to know we hit a jedi?
11285 if ( !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
11286 {//act like we didn't even hit him
11287 VectorCopy( tr.endpos, start );
11288 ignore = tr.entityNum;
11289 traces++;
11290 continue;
11291 }
11292 }
11293 //a Jedi is not dodging this shot
11294 break;
11295 }
11296
11297 traceEnt = &g_entities[tr.entityNum];
11298 ForceLightningDamage( self, traceEnt, forward, 0, 0, tr.endpos );
11299 }
11300 }
11301
WP_DeactivateSaber(gentity_t * self,qboolean clearLength)11302 void WP_DeactivateSaber( gentity_t *self, qboolean clearLength )
11303 {
11304 if ( !self || !self->client )
11305 {
11306 return;
11307 }
11308 //keep my saber off!
11309 if ( self->client->ps.SaberActive() )
11310 {
11311 self->client->ps.SaberDeactivate();
11312 if ( clearLength )
11313 {
11314 self->client->ps.SetSaberLength( 0 );
11315 }
11316 G_SoundIndexOnEnt( self, CHAN_WEAPON, self->client->ps.saber[0].soundOff );
11317 }
11318 }
11319
11320 static void ForceShootDrain( gentity_t *self );
11321
ForceDrainGrabStart(gentity_t * self)11322 void ForceDrainGrabStart( gentity_t *self )
11323 {
11324 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11325 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
11326 self->client->ps.saberBlocked = BLOCKED_NONE;
11327
11328 self->client->ps.weaponTime = 1000;
11329 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
11330 {
11331 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
11332 }
11333 //actually grabbing someone, so turn off the saber!
11334 WP_DeactivateSaber( self, qtrue );
11335 }
11336
ForceDrain2(gentity_t * self)11337 qboolean ForceDrain2( gentity_t *self )
11338 {//FIXME: make enemy Jedi able to use this
11339 trace_t tr;
11340 vec3_t end, forward;
11341 gentity_t *traceEnt = NULL;
11342
11343 if ( self->health <= 0 )
11344 {
11345 return qtrue;
11346 }
11347
11348 if ( !self->s.number && (cg.zoomMode || in_camera) )
11349 {//can't force grip when zoomed in or in cinematic
11350 return qtrue;
11351 }
11352
11353 if ( self->client->ps.leanofs )
11354 {//can't force-drain while leaning
11355 return qtrue;
11356 }
11357
11358 /*
11359 if ( self->client->ps.SaberLength() > 0 )
11360 {//can't do this if saber is on!
11361 return qfalse;
11362 }
11363 */
11364
11365 if ( self->client->ps.forceDrainEntityNum <= ENTITYNUM_WORLD )
11366 {//already draining
11367 //keep my saber off!
11368 WP_DeactivateSaber( self, qtrue );
11369 if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
11370 {
11371 self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 100;
11372 self->client->ps.weaponTime = 1000;
11373 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
11374 {
11375 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
11376 }
11377 }
11378 return qtrue;
11379 }
11380
11381 if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time )
11382 {//stops it while using it and also after using it, up to 3 second delay
11383 return qtrue;
11384 }
11385
11386 if ( self->client->ps.weaponTime > 0 )
11387 {//busy
11388 return qtrue;
11389 }
11390
11391 if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) )
11392 {
11393 return qtrue;
11394 }
11395
11396 if ( self->client->ps.saberLockTime > level.time )
11397 {//in saberlock
11398 return qtrue;
11399 }
11400
11401 //NOTE: from here on, if it fails, it's okay to try a normal drain, so return qfalse
11402 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
11403 {//in air
11404 return qfalse;
11405 }
11406
11407 //Cause choking anim + health drain in ent in front of me
11408 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
11409 VectorNormalize( forward );
11410 VectorMA( self->client->renderInfo.eyePoint, FORCE_DRAIN_DIST, forward, end );
11411
11412 //okay, trace straight ahead and see what's there
11413 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
11414 if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
11415 {
11416 return qfalse;
11417 }
11418 traceEnt = &g_entities[tr.entityNum];
11419 if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
11420 {
11421 return qfalse;
11422 }
11423
11424 if ( traceEnt->client )
11425 {
11426 if ( traceEnt->client->ps.forceJumpZStart )
11427 {//can't catch them in mid force jump - FIXME: maybe base it on velocity?
11428 return qfalse;
11429 }
11430 if ( traceEnt->client->ps.groundEntityNum == ENTITYNUM_NONE )
11431 {//can't catch them in mid air
11432 return qfalse;
11433 }
11434 if ( !Q_stricmp("Yoda",traceEnt->NPC_type) )
11435 {
11436 Jedi_PlayDeflectSound( traceEnt );
11437 ForceThrow( traceEnt, qfalse );
11438 return qtrue;
11439 }
11440 switch ( traceEnt->client->NPC_class )
11441 {
11442 case CLASS_GALAKMECH://cant grab him, he's in armor
11443 G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
11444 return qfalse;
11445 break;
11446 case CLASS_ROCKETTROOPER://cant grab him, he's in armor
11447 case CLASS_HAZARD_TROOPER://cant grab him, he's in armor
11448 return qfalse;
11449 break;
11450 case CLASS_ATST://much too big to grab!
11451 return qfalse;
11452 break;
11453 //no droids either
11454 case CLASS_GONK:
11455 case CLASS_R2D2:
11456 case CLASS_R5D2:
11457 case CLASS_MARK1:
11458 case CLASS_MARK2:
11459 case CLASS_MOUSE:
11460 case CLASS_PROTOCOL:
11461 case CLASS_SABER_DROID:
11462 case CLASS_ASSASSIN_DROID:
11463 return qfalse;
11464 break;
11465 case CLASS_PROBE:
11466 case CLASS_SEEKER:
11467 case CLASS_REMOTE:
11468 case CLASS_SENTRY:
11469 case CLASS_INTERROGATOR:
11470 return qfalse;
11471 break;
11472 case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
11473 case CLASS_KYLE:
11474 case CLASS_TAVION:
11475 case CLASS_LUKE:
11476 Jedi_PlayDeflectSound( traceEnt );
11477 ForceThrow( traceEnt, qfalse );
11478 return qtrue;
11479 break;
11480 case CLASS_REBORN:
11481 case CLASS_SHADOWTROOPER:
11482 //case CLASS_ALORA:
11483 case CLASS_JEDI:
11484 if ( traceEnt->NPC
11485 && traceEnt->NPC->rank > RANK_CIVILIAN
11486 && self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2
11487 && traceEnt->client->ps.weaponTime <= 0 )
11488 {
11489 ForceDrainGrabStart( self );
11490 Jedi_PlayDeflectSound( traceEnt );
11491 ForceThrow( traceEnt, qfalse );
11492 return qtrue;
11493 }
11494 break;
11495 default:
11496 break;
11497 }
11498 if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
11499 {//FIXME: maybe can pull them out?
11500 return qfalse;
11501 }
11502 if ( traceEnt != self->enemy && OnSameTeam(self, traceEnt) )
11503 {//can't accidently grip-drain your teammate
11504 return qfalse;
11505 }
11506 //=CHECKABSORB===
11507 /*
11508 if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]]) )
11509 {
11510 //WP_ForcePowerStop( self, FP_DRAIN );
11511 return;
11512 }
11513 */
11514 //===============
11515 if ( !FP_ForceDrainGrippableEnt( traceEnt ) )
11516 {
11517 return qfalse;
11518 }
11519 }
11520 else
11521 {//can't drain non-clients
11522 return qfalse;
11523 }
11524
11525 ForceDrainGrabStart( self );
11526
11527 WP_ForcePowerStart( self, FP_DRAIN, 10 );
11528 self->client->ps.forceDrainEntityNum = traceEnt->s.number;
11529
11530 // G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
11531 G_AddVoiceEvent( traceEnt, Q_irand(EV_CHOKE1, EV_CHOKE3), 2000 );
11532 if ( /*self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 ||*/ traceEnt->s.weapon == WP_SABER )
11533 {//if we pick up, turn off their weapon
11534 WP_DeactivateSaber( traceEnt, qtrue );
11535 }
11536
11537 /*
11538 if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 )
11539 {//just a duration
11540 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 250;
11541 self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 5000;
11542 }
11543 */
11544
11545 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" );
11546
11547 // NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11548 NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
11549
11550 WP_SabersCheckLock2( self, traceEnt, LOCK_FORCE_DRAIN );
11551
11552 return qtrue;
11553 }
11554
ForceDrain(gentity_t * self,qboolean triedDrain2)11555 void ForceDrain( gentity_t *self, qboolean triedDrain2 )
11556 {
11557 if ( self->health <= 0 )
11558 {
11559 return;
11560 }
11561
11562 if ( !triedDrain2 && self->client->ps.weaponTime > 0 )
11563 {
11564 return;
11565 }
11566
11567 if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) )
11568 {
11569 return;
11570 }
11571 if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time )
11572 {//stops it while using it and also after using it, up to 3 second delay
11573 return;
11574 }
11575
11576 if ( self->client->ps.saberLockTime > level.time )
11577 {//FIXME: can this be a way to break out?
11578 return;
11579 }
11580
11581 // Make sure to turn off Force Protection and Force Absorb.
11582 if ( self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
11583 {
11584 WP_ForcePowerStop( self, FP_PROTECT );
11585 }
11586 if ( self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
11587 {
11588 WP_ForcePowerStop( self, FP_ABSORB );
11589 }
11590
11591 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" );
11592
11593 WP_ForcePowerStart( self, FP_DRAIN, 0 );
11594 }
11595
11596
FP_ForceDrainableEnt(gentity_t * victim)11597 qboolean FP_ForceDrainableEnt( gentity_t *victim )
11598 {
11599 if ( !victim || !victim->client )
11600 {
11601 return qfalse;
11602 }
11603 switch ( victim->client->NPC_class )
11604 {
11605 case CLASS_SAND_CREATURE://??
11606 case CLASS_ATST: // technically droid...
11607 case CLASS_GONK: // droid
11608 case CLASS_INTERROGATOR: // droid
11609 case CLASS_MARK1: // droid
11610 case CLASS_MARK2: // droid
11611 case CLASS_GALAKMECH: // droid
11612 case CLASS_MINEMONSTER:
11613 case CLASS_MOUSE: // droid
11614 case CLASS_PROBE: // droid
11615 case CLASS_PROTOCOL: // droid
11616 case CLASS_R2D2: // droid
11617 case CLASS_R5D2: // droid
11618 case CLASS_REMOTE:
11619 case CLASS_SEEKER: // droid
11620 case CLASS_SENTRY:
11621 case CLASS_SABER_DROID:
11622 case CLASS_ASSASSIN_DROID:
11623 case CLASS_VEHICLE:
11624 return qfalse;
11625 default:
11626 break;
11627 }
11628 return qtrue;
11629 }
11630
FP_ForceDrainGrippableEnt(gentity_t * victim)11631 qboolean FP_ForceDrainGrippableEnt( gentity_t *victim )
11632 {
11633 if ( !victim || !victim->client )
11634 {
11635 return qfalse;
11636 }
11637 if ( !FP_ForceDrainableEnt( victim ) )
11638 {
11639 return qfalse;
11640 }
11641 switch ( victim->client->NPC_class )
11642 {
11643 case CLASS_RANCOR:
11644 case CLASS_SAND_CREATURE:
11645 case CLASS_WAMPA:
11646 case CLASS_LIZARD:
11647 case CLASS_MINEMONSTER:
11648 case CLASS_MURJJ:
11649 case CLASS_SWAMP:
11650 case CLASS_ROCKETTROOPER:
11651 case CLASS_HAZARD_TROOPER:
11652 return qfalse;
11653 default:
11654 break;
11655 }
11656 return qtrue;
11657 }
11658
ForceDrainDamage(gentity_t * self,gentity_t * traceEnt,vec3_t dir,vec3_t impactPoint)11659 void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint )
11660 {
11661 if ( traceEnt
11662 && traceEnt->health > 0
11663 && traceEnt->takedamage
11664 && FP_ForceDrainableEnt( traceEnt ) )
11665 {
11666 if ( traceEnt->client
11667 && (!OnSameTeam(self, traceEnt)||self->enemy==traceEnt)//don't drain an ally unless that is actually my current enemy
11668 && self->client->ps.forceDrainTime < level.time )
11669 {//an enemy or object
11670 int modPowerLevel = -1;
11671 int dmg = self->client->ps.forcePowerLevel[FP_DRAIN] + 1;
11672 int dflags = (DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC);//|DAMAGE_NO_KILL);
11673 if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum )
11674 {//grabbing hold of them does more damage/drains more, and can actually kill them
11675 dmg += 3;
11676 dflags |= DAMAGE_IGNORE_TEAM;
11677 //dflags &= ~DAMAGE_NO_KILL;
11678 }
11679
11680 if (traceEnt->client)
11681 {
11682 //check for client using FP_ABSORB
11683 modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], 0);
11684 //Since this is drain, don't absorb any power, but nullify the affect it has
11685 }
11686
11687 if ( modPowerLevel != -1 )
11688 {
11689 if ( !modPowerLevel )
11690 {
11691 dmg = 0;
11692 }
11693 else if ( modPowerLevel == 1 )
11694 {
11695 dmg = 1;
11696 }
11697 else if ( modPowerLevel == 2 )
11698 {
11699 dmg = 2;
11700 }
11701 }
11702
11703 if ( dmg )
11704 {
11705 int drain = 0;
11706 if ( traceEnt->client->ps.forcePower )
11707 {
11708 if ( dmg > traceEnt->client->ps.forcePower )
11709 {
11710 drain = traceEnt->client->ps.forcePower;
11711 dmg -= drain;
11712 traceEnt->client->ps.forcePower = 0;
11713 }
11714 else
11715 {
11716 drain = dmg;
11717 traceEnt->client->ps.forcePower -= (dmg);
11718 dmg = 0;
11719 }
11720 }
11721
11722 /*
11723 if ( (dflags&DAMAGE_NO_KILL) )
11724 {//must cap damage
11725 if ( traceEnt->health <= 1 )
11726 {//can't drain more than they have
11727 dmg = 0;
11728 }
11729 else if ( dmg >= traceEnt->health )
11730 {//no more than they have, leaving one for them
11731 dmg = traceEnt->health-1;
11732 }
11733 }
11734 */
11735
11736 int maxHealth = self->client->ps.stats[STAT_MAX_HEALTH];
11737 if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
11738 {//overcharge health
11739 maxHealth = floor( (float)self->client->ps.stats[STAT_MAX_HEALTH] * 1.25f );
11740 }
11741 if (self->client->ps.stats[STAT_HEALTH] < maxHealth &&
11742 self->health > 0 && self->client->ps.stats[STAT_HEALTH] > 0)
11743 {
11744 self->health += (drain+dmg);
11745 if (self->health > maxHealth )
11746 {
11747 self->health = maxHealth;
11748 }
11749 self->client->ps.stats[STAT_HEALTH] = self->health;
11750 if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH] )
11751 {
11752 self->flags |= FL_OVERCHARGED_HEALTH;
11753 }
11754 }
11755
11756 if ( dmg )
11757 {//do damage, too
11758 G_Damage( traceEnt, self, self, dir, impactPoint, dmg, dflags, MOD_FORCE_DRAIN );
11759 }
11760 else if ( drain )
11761 {
11762 /*
11763 if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum
11764 || traceEnt->s.number < MAX_CLIENTS )
11765 {//grip-draining (or player - only does sound)
11766 */
11767 NPC_SetPainEvent( traceEnt );
11768 /*
11769 }
11770 else
11771 {
11772 GEntity_PainFunc( traceEnt, self, self, impactPoint, 0, MOD_FORCE_DRAIN );
11773 }
11774 */
11775 }
11776
11777 if ( !Q_irand( 0, 2 ) )
11778 {
11779 G_Sound( traceEnt, G_SoundIndex( "sound/weapons/force/drained.mp3" ) );
11780 }
11781
11782 traceEnt->client->ps.forcePowerRegenDebounceTime = level.time + 800; //don't let the client being drained get force power back right away
11783 }
11784 }
11785 }
11786 }
11787
WP_CheckForceDraineeStopMe(gentity_t * self,gentity_t * drainee)11788 qboolean WP_CheckForceDraineeStopMe( gentity_t *self, gentity_t *drainee )
11789 {
11790 if ( drainee->NPC
11791 && drainee->client
11792 && (drainee->client->ps.forcePowersKnown&(1<<FP_PUSH))
11793 && level.time-(self->client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms
11794 && !Q_irand( 0, 100-(drainee->NPC->stats.evasion*10)-(g_spskill->integer*12) ) )
11795 {//a jedi who broke free
11796 ForceThrow( drainee, qfalse );
11797 //FIXME: I need to go into some pushed back anim...
11798 WP_ForcePowerStop( self, FP_DRAIN );
11799 //can't drain again for 2 seconds
11800 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000;
11801 return qtrue;
11802 }
11803 return qfalse;
11804 }
11805
ForceShootDrain(gentity_t * self)11806 void ForceShootDrain( gentity_t *self )
11807 {
11808 trace_t tr;
11809 vec3_t end, forward;
11810 gentity_t *traceEnt;
11811 int numDrained = 0;
11812
11813 if ( self->health <= 0 )
11814 {
11815 return;
11816 }
11817
11818 if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time )
11819 {
11820 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
11821 VectorNormalize( forward );
11822
11823 if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
11824 {//arc
11825 vec3_t center, mins, maxs, dir, ent_org, size, v;
11826 float radius = MAX_DRAIN_DISTANCE, dot, dist;
11827 gentity_t *entityList[MAX_GENTITIES];
11828 int e, numListedEntities, i;
11829
11830 VectorCopy( self->client->ps.origin, center );
11831 for ( i = 0 ; i < 3 ; i++ )
11832 {
11833 mins[i] = center[i] - radius;
11834 maxs[i] = center[i] + radius;
11835 }
11836 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
11837
11838 for ( e = 0 ; e < numListedEntities ; e++ )
11839 {
11840 traceEnt = entityList[e];
11841
11842 if ( !traceEnt )
11843 continue;
11844 if ( traceEnt == self )
11845 continue;
11846 if ( !traceEnt->inuse )
11847 continue;
11848 if ( !traceEnt->takedamage )
11849 continue;
11850 if ( traceEnt->health <= 0 )//no torturing corpses
11851 continue;
11852 if ( !traceEnt->client )
11853 continue;
11854 /*
11855 if ( !traceEnt->client->ps.forcePower )
11856 continue;
11857 */
11858 // if (traceEnt->client->ps.forceSide == FORCE_DARKSIDE) // We no longer care if the victim is dark or light
11859 // continue;
11860 if (self->enemy != traceEnt//not my enemy
11861 && OnSameTeam(self, traceEnt))//on my team
11862 continue;
11863 //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
11864 // find the distance from the edge of the bounding box
11865 for ( i = 0 ; i < 3 ; i++ )
11866 {
11867 if ( center[i] < traceEnt->absmin[i] )
11868 {
11869 v[i] = traceEnt->absmin[i] - center[i];
11870 } else if ( center[i] > traceEnt->absmax[i] )
11871 {
11872 v[i] = center[i] - traceEnt->absmax[i];
11873 } else
11874 {
11875 v[i] = 0;
11876 }
11877 }
11878
11879 VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
11880 VectorMA( traceEnt->absmin, 0.5, size, ent_org );
11881
11882 //see if they're in front of me
11883 //must be within the forward cone
11884 VectorSubtract( ent_org, center, dir );
11885 VectorNormalize( dir );
11886 if ( (dot = DotProduct( dir, forward )) < 0.5 )
11887 continue;
11888
11889 //must be close enough
11890 dist = VectorLength( v );
11891 if ( dist >= radius )
11892 {
11893 continue;
11894 }
11895
11896 //in PVS?
11897 if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
11898 {//must be in PVS
11899 continue;
11900 }
11901
11902 //Now check and see if we can actually hit it
11903 gi.trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
11904 if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
11905 {//must have clear LOS
11906 continue;
11907 }
11908
11909 if ( traceEnt
11910 && traceEnt->s.number >= MAX_CLIENTS
11911 && traceEnt->client
11912 && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
11913 {
11914 if ( !Q_irand( 0, 4 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
11915 {//act like we didn't even hit him
11916 continue;
11917 }
11918 }
11919
11920 // ok, we are within the radius, add us to the incoming list
11921 if ( WP_CheckForceDraineeStopMe( self, traceEnt ) )
11922 {
11923 continue;
11924 }
11925 ForceDrainDamage( self, traceEnt, dir, ent_org );
11926 numDrained++;
11927 }
11928
11929 }
11930 else
11931 {//trace-line
11932 int ignore = self->s.number;
11933 int traces = 0;
11934 vec3_t start;
11935
11936 VectorCopy( self->client->renderInfo.handLPoint, start );
11937 VectorMA( start, MAX_DRAIN_DISTANCE, forward, end );
11938
11939 while ( traces < 10 )
11940 {//need to loop this in case we hit a Jedi who dodges the shot
11941 gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 );
11942 if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
11943 {
11944 //always take 1 force point per frame that we're shooting this
11945 WP_ForcePowerDrain( self, FP_DRAIN, 1 );
11946 return;
11947 }
11948
11949 traceEnt = &g_entities[tr.entityNum];
11950 //NOTE: only NPCs do this auto-dodge
11951 if ( traceEnt
11952 && traceEnt->s.number >= MAX_CLIENTS
11953 && traceEnt->client
11954 && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC
11955 {
11956 if ( !Q_irand( 0, 2 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
11957 {//act like we didn't even hit him
11958 VectorCopy( tr.endpos, start );
11959 ignore = tr.entityNum;
11960 traces++;
11961 continue;
11962 }
11963 }
11964 //a Jedi is not dodging this shot
11965 break;
11966 }
11967 traceEnt = &g_entities[tr.entityNum];
11968 if ( !WP_CheckForceDraineeStopMe( self, traceEnt ) )
11969 {
11970 ForceDrainDamage( self, traceEnt, forward, tr.endpos );
11971 }
11972 numDrained = 1;
11973 }
11974
11975 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 200;//so we don't drain so damn fast!
11976 }
11977 self->client->ps.forcePowerRegenDebounceTime = level.time + 500;
11978
11979 if ( !numDrained )
11980 {//always take 1 force point per frame that we're shooting this
11981 WP_ForcePowerDrain( self, FP_DRAIN, 1 );
11982 }
11983 else
11984 {
11985 WP_ForcePowerDrain( self, FP_DRAIN, numDrained );//was 2, but...
11986 }
11987
11988 return;
11989 }
11990
ForceDrainEnt(gentity_t * self,gentity_t * drainEnt)11991 void ForceDrainEnt( gentity_t *self, gentity_t *drainEnt )
11992 {
11993 if ( self->health <= 0 )
11994 {
11995 return;
11996 }
11997
11998 if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time )
11999 {
12000 if ( !drainEnt )
12001 return;
12002 if ( drainEnt == self )
12003 return;
12004 if ( !drainEnt->inuse )
12005 return;
12006 if ( !drainEnt->takedamage )
12007 return;
12008 if ( drainEnt->health <= 0 )//no torturing corpses
12009 return;
12010 if ( !drainEnt->client )
12011 return;
12012 if (OnSameTeam(self, drainEnt))
12013 return;
12014
12015 vec3_t fwd;
12016 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
12017
12018 drainEnt->painDebounceTime = 0;
12019 ForceDrainDamage( self, drainEnt, fwd, drainEnt->currentOrigin );
12020 drainEnt->painDebounceTime = level.time + 2000;
12021
12022 if ( drainEnt->s.number )
12023 {
12024 if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 )
12025 {//do damage faster at level 3
12026 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 );
12027 }
12028 else
12029 {
12030 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 200, 800 );
12031 }
12032 }
12033 else
12034 {//player takes damage faster
12035 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 );
12036 }
12037 }
12038
12039 self->client->ps.forcePowerRegenDebounceTime = level.time + 500;
12040 }
12041
ForceSeeing(gentity_t * self)12042 void ForceSeeing( gentity_t *self )
12043 {
12044 if ( self->health <= 0 )
12045 {
12046 return;
12047 }
12048
12049 if (self->client->ps.forceAllowDeactivateTime < level.time &&
12050 (self->client->ps.forcePowersActive & (1 << FP_SEE)) )
12051 {
12052 WP_ForcePowerStop( self, FP_SEE );
12053 return;
12054 }
12055
12056 if ( !WP_ForcePowerUsable( self, FP_SEE, 0 ) )
12057 {
12058 return;
12059 }
12060
12061 WP_DebounceForceDeactivateTime( self );
12062
12063 WP_ForcePowerStart( self, FP_SEE, 0 );
12064
12065 G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.wav" );
12066 }
12067
ForceProtect(gentity_t * self)12068 void ForceProtect( gentity_t *self )
12069 {
12070 if ( self->health <= 0 )
12071 {
12072 return;
12073 }
12074
12075 if (self->client->ps.forceAllowDeactivateTime < level.time &&
12076 (self->client->ps.forcePowersActive & (1 << FP_PROTECT)) )
12077 {
12078 WP_ForcePowerStop( self, FP_PROTECT );
12079 return;
12080 }
12081
12082 if ( !WP_ForcePowerUsable( self, FP_PROTECT, 0 ) )
12083 {
12084 return;
12085 }
12086
12087 // Make sure to turn off Force Rage and Force Absorb.
12088 if (self->client->ps.forcePowersActive & (1 << FP_RAGE) )
12089 {
12090 WP_ForcePowerStop( self, FP_RAGE );
12091 }
12092
12093 WP_DebounceForceDeactivateTime( self );
12094
12095 WP_ForcePowerStart( self, FP_PROTECT, 0 );
12096
12097 if ( self->client->ps.saberLockTime < level.time )
12098 {
12099 if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_3 )
12100 {//animate
12101 int parts = SETANIM_BOTH;
12102 int anim = BOTH_FORCE_PROTECT;
12103 if ( self->client->ps.forcePowerLevel[FP_PROTECT] > FORCE_LEVEL_1 )
12104 {//level 2 only does it on torso (can keep running)
12105 parts = SETANIM_TORSO;
12106 anim = BOTH_FORCE_PROTECT_FAST;
12107 }
12108 else
12109 {
12110 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
12111 {
12112 VectorClear( self->client->ps.velocity );
12113 }
12114 if ( self->NPC )
12115 {
12116 VectorClear( self->client->ps.moveDir );
12117 self->client->ps.speed = 0;
12118 }
12119 //FIXME: what if in air?
12120 }
12121 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12122 //don't move or attack during this anim
12123 if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_2 )
12124 {
12125 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
12126 self->client->ps.pm_time = self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12127 if ( self->s.number )
12128 {//NPC
12129 self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12130 }
12131 else
12132 {//player
12133 self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12134 }
12135 }
12136 else
12137 {
12138 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12139 }
12140 }
12141 }
12142 }
12143
ForceAbsorb(gentity_t * self)12144 void ForceAbsorb( gentity_t *self )
12145 {
12146 if ( self->health <= 0 )
12147 {
12148 return;
12149 }
12150
12151 if (self->client->ps.forceAllowDeactivateTime < level.time &&
12152 (self->client->ps.forcePowersActive & (1 << FP_ABSORB)) )
12153 {
12154 WP_ForcePowerStop( self, FP_ABSORB );
12155 return;
12156 }
12157
12158 if ( !WP_ForcePowerUsable( self, FP_ABSORB, 0 ) )
12159 {
12160 return;
12161 }
12162
12163 // Make sure to turn off Force Rage and Force Protection.
12164 if (self->client->ps.forcePowersActive & (1 << FP_RAGE) )
12165 {
12166 WP_ForcePowerStop( self, FP_RAGE );
12167 }
12168
12169 WP_DebounceForceDeactivateTime( self );
12170
12171 WP_ForcePowerStart( self, FP_ABSORB, 0 );
12172
12173 if ( self->client->ps.saberLockTime < level.time )
12174 {
12175 if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_3 )
12176 {//must animate
12177 int parts = SETANIM_BOTH;
12178 if ( self->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_1 )
12179 {//level 2 only does it on torso (can keep running)
12180 parts = SETANIM_TORSO;
12181 }
12182 else
12183 {
12184 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE )
12185 {
12186 VectorClear( self->client->ps.velocity );
12187 }
12188 if ( self->NPC )
12189 {
12190 VectorClear( self->client->ps.moveDir );
12191 self->client->ps.speed = 0;
12192 }
12193 //FIXME: what if in air?
12194 }
12195 /*
12196 //if in air, only do on torso - NOTE: or moving?
12197 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )//|| !VectorCompare( self->client->ps.velocity, vec3_origin ) )
12198 {
12199 parts = SETANIM_TORSO;
12200 }
12201 */
12202 NPC_SetAnim( self, parts, BOTH_FORCE_ABSORB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12203 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12204 if ( parts == SETANIM_BOTH )
12205 {//can't move
12206 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
12207 self->client->ps.pm_time = self->client->ps.legsAnimTimer = self->client->ps.torsoAnimTimer;// = self->client->ps.forcePowerDuration[FP_ABSORB];
12208 if ( self->s.number )
12209 {//NPC
12210 self->painDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB];
12211 }
12212 else
12213 {//player
12214 self->aimDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB];
12215 }
12216 }
12217 //stop saber
12218 //WP_DeactivateSaber( self );//turn off saber when meditating
12219 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
12220 self->client->ps.saberBlocked = BLOCKED_NONE;
12221 }
12222 }
12223 }
12224
ForceRage(gentity_t * self)12225 void ForceRage( gentity_t *self )
12226 {
12227 if ( self->health <= 0 )
12228 {
12229 return;
12230 }
12231
12232 //FIXME: prevent them from using any other force powers when raging?
12233
12234 if (self->client->ps.forceAllowDeactivateTime < level.time &&
12235 (self->client->ps.forcePowersActive & (1 << FP_RAGE)) )
12236 {
12237 WP_ForcePowerStop( self, FP_RAGE );
12238 return;
12239 }
12240
12241 if ( !WP_ForcePowerUsable( self, FP_RAGE, 0 ) )
12242 {
12243 return;
12244 }
12245
12246 if (self->client->ps.forceRageRecoveryTime >= level.time)
12247 {
12248 return;
12249 }
12250
12251 if ( self->s.number < MAX_CLIENTS
12252 && self->health < 25 )
12253 {//have to have at least 25 health to start it
12254 return;
12255 }
12256
12257 if (self->health < 10)
12258 {
12259 return;
12260 }
12261
12262 // Make sure to turn off Force Protection and Force Absorb.
12263 if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) )
12264 {
12265 WP_ForcePowerStop( self, FP_PROTECT );
12266 }
12267 if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) )
12268 {
12269 WP_ForcePowerStop( self, FP_ABSORB );
12270 }
12271
12272 WP_DebounceForceDeactivateTime( self );
12273
12274 WP_ForcePowerStart( self, FP_RAGE, 0 );
12275
12276 if ( self->client->ps.saberLockTime < level.time )
12277 {
12278 if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_3 )
12279 {//must animate
12280 if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 )
12281 {//have to stand still for whole length of anim
12282 //FIXME: if in air, only do on torso?
12283 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12284 //don't attack during this anim
12285 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12286 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
12287 self->client->ps.pm_time = self->client->ps.torsoAnimTimer;
12288 if ( self->s.number )
12289 {//NPC
12290 self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12291 }
12292 else
12293 {//player
12294 self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
12295 }
12296 }
12297 else
12298 {
12299 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12300 //don't attack during this anim
12301 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12302 }
12303 //stop saber
12304 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
12305 self->client->ps.saberBlocked = BLOCKED_NONE;
12306 }
12307 }
12308 }
12309
ForceJumpCharge(gentity_t * self,usercmd_t * ucmd)12310 void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd )
12311 {
12312 float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME);
12313
12314 if ( self->health <= 0 )
12315 {
12316 return;
12317 }
12318 if ( !self->s.number && cg.zoomMode )
12319 {//can't force jump when zoomed in
12320 return;
12321 }
12322
12323 //need to play sound
12324 if ( !self->client->ps.forceJumpCharge )
12325 {//FIXME: this should last only as long as the actual charge-up
12326 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jumpbuild.wav" );
12327 }
12328 //Increment
12329 self->client->ps.forceJumpCharge += forceJumpChargeInterval;
12330
12331 //clamp to max strength for current level
12332 if ( self->client->ps.forceJumpCharge > forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]] )
12333 {
12334 self->client->ps.forceJumpCharge = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]];
12335 }
12336
12337 //clamp to max available force power
12338 if ( self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] > self->client->ps.forcePower )
12339 {//can't use more than you have
12340 self->client->ps.forceJumpCharge = self->client->ps.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
12341 }
12342 //FIXME: a simple tap should always do at least a normal height's jump?
12343 }
12344
WP_GetVelocityForForceJump(gentity_t * self,vec3_t jumpVel,usercmd_t * ucmd)12345 int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd )
12346 {
12347 float pushFwd = 0, pushRt = 0;
12348 vec3_t view, forward, right;
12349 VectorCopy( self->client->ps.viewangles, view );
12350 view[0] = 0;
12351 AngleVectors( view, forward, right, NULL );
12352 if ( ucmd->forwardmove && ucmd->rightmove )
12353 {
12354 if ( ucmd->forwardmove > 0 )
12355 {
12356 pushFwd = 50;
12357 }
12358 else
12359 {
12360 pushFwd = -50;
12361 }
12362 if ( ucmd->rightmove > 0 )
12363 {
12364 pushRt = 50;
12365 }
12366 else
12367 {
12368 pushRt = -50;
12369 }
12370 }
12371 else if ( ucmd->forwardmove || ucmd->rightmove )
12372 {
12373 if ( ucmd->forwardmove > 0 )
12374 {
12375 pushFwd = 100;
12376 }
12377 else if ( ucmd->forwardmove < 0 )
12378 {
12379 pushFwd = -100;
12380 }
12381 else if ( ucmd->rightmove > 0 )
12382 {
12383 pushRt = 100;
12384 }
12385 else if ( ucmd->rightmove < 0 )
12386 {
12387 pushRt = -100;
12388 }
12389 }
12390 VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel );
12391 VectorMA( self->client->ps.velocity, pushRt, right, jumpVel );
12392 jumpVel[2] += self->client->ps.forceJumpCharge;//forceJumpStrength;
12393 if ( pushFwd > 0 && self->client->ps.forceJumpCharge > 200 )
12394 {
12395 return FJ_FORWARD;
12396 }
12397 else if ( pushFwd < 0 && self->client->ps.forceJumpCharge > 200 )
12398 {
12399 return FJ_BACKWARD;
12400 }
12401 else if ( pushRt > 0 && self->client->ps.forceJumpCharge > 200 )
12402 {
12403 return FJ_RIGHT;
12404 }
12405 else if ( pushRt < 0 && self->client->ps.forceJumpCharge > 200 )
12406 {
12407 return FJ_LEFT;
12408 }
12409 else
12410 {//FIXME: jump straight up anim
12411 return FJ_UP;
12412 }
12413 }
12414
ForceJump(gentity_t * self,usercmd_t * ucmd)12415 void ForceJump( gentity_t *self, usercmd_t *ucmd )
12416 {
12417 if ( self->client->ps.forcePowerDuration[FP_LEVITATION] > level.time )
12418 {
12419 return;
12420 }
12421 if ( !WP_ForcePowerUsable( self, FP_LEVITATION, 0 ) )
12422 {
12423 return;
12424 }
12425 if ( self->s.groundEntityNum == ENTITYNUM_NONE )
12426 {
12427 return;
12428 }
12429 if ( self->client->ps.pm_flags&PMF_JUMP_HELD )
12430 {
12431 return;
12432 }
12433 if ( self->health <= 0 )
12434 {
12435 return;
12436 }
12437 if ( !self->s.number && (cg.zoomMode || in_camera) )
12438 {//can't force jump when zoomed in or in cinematic
12439 return;
12440 }
12441 if ( self->client->ps.saberLockTime > level.time )
12442 {//FIXME: can this be a way to break out?
12443 return;
12444 }
12445
12446 if ( self->client->NPC_class == CLASS_BOBAFETT
12447 || self->client->NPC_class == CLASS_ROCKETTROOPER )
12448 {
12449 if ( self->client->ps.forceJumpCharge > 300 )
12450 {
12451 JET_FlyStart(NPC);
12452 }
12453 else
12454 {
12455 G_AddEvent( self, EV_JUMP, 0 );
12456 }
12457 }
12458 else
12459 {
12460 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
12461 }
12462
12463 float forceJumpChargeInterval = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
12464
12465 int anim;
12466 vec3_t jumpVel;
12467
12468 switch( WP_GetVelocityForForceJump( self, jumpVel, ucmd ) )
12469 {
12470 case FJ_FORWARD:
12471 if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12472 || (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12473 || (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12474 || ( self->NPC &&
12475 self->NPC->rank != RANK_CREWMAN &&
12476 self->NPC->rank <= RANK_LT_JG ) )
12477 {//can't do acrobatics
12478 anim = BOTH_FORCEJUMP1;
12479 }
12480 else
12481 {
12482 if ( self->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) )
12483 {
12484 anim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 );
12485 }
12486 else
12487 {
12488 anim = BOTH_FLIP_F;
12489 }
12490 }
12491 break;
12492 case FJ_BACKWARD:
12493 if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12494 || (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12495 || (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12496 || ( self->NPC &&
12497 self->NPC->rank != RANK_CREWMAN &&
12498 self->NPC->rank <= RANK_LT_JG ) )
12499 {//can't do acrobatics
12500 anim = BOTH_FORCEJUMPBACK1;
12501 }
12502 else
12503 {
12504 anim = BOTH_FLIP_B;
12505 }
12506 break;
12507 case FJ_RIGHT:
12508 if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12509 || (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12510 || (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12511 || ( self->NPC &&
12512 self->NPC->rank != RANK_CREWMAN &&
12513 self->NPC->rank <= RANK_LT_JG ) )
12514 {//can't do acrobatics
12515 anim = BOTH_FORCEJUMPRIGHT1;
12516 }
12517 else
12518 {
12519 anim = BOTH_FLIP_R;
12520 }
12521 break;
12522 case FJ_LEFT:
12523 if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 )
12524 || (self->client->ps.saber[0].saberFlags&SFL_NO_FLIPS)
12525 || (self->client->ps.dualSabers && (self->client->ps.saber[1].saberFlags&SFL_NO_FLIPS) )
12526 || ( self->NPC &&
12527 self->NPC->rank != RANK_CREWMAN &&
12528 self->NPC->rank <= RANK_LT_JG ) )
12529 {//can't do acrobatics
12530 anim = BOTH_FORCEJUMPLEFT1;
12531 }
12532 else
12533 {
12534 anim = BOTH_FLIP_L;
12535 }
12536 break;
12537 default:
12538 case FJ_UP:
12539 anim = BOTH_JUMP1;
12540 break;
12541 }
12542
12543 int parts = SETANIM_BOTH;
12544 if ( self->client->ps.weaponTime )
12545 {//FIXME: really only care if we're in a saber attack anim.. maybe trail length?
12546 parts = SETANIM_LEGS;
12547 }
12548
12549 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12550
12551 //FIXME: sound effect
12552 self->client->ps.forceJumpZStart = self->currentOrigin[2];//remember this for when we land
12553 VectorCopy( jumpVel, self->client->ps.velocity );
12554 //wasn't allowing them to attack when jumping, but that was annoying
12555 //self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
12556
12557 WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] );
12558 //self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
12559 self->client->ps.forceJumpCharge = 0;
12560 }
12561
WP_AbsorbConversion(gentity_t * attacked,int atdAbsLevel,gentity_t * attacker,int atPower,int atPowerLevel,int atForceSpent)12562 int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent)
12563 {
12564 int getLevel = 0;
12565 int addTot = 0;
12566
12567 if (atPower != FP_LIGHTNING &&
12568 atPower != FP_DRAIN &&
12569 atPower != FP_GRIP &&
12570 atPower != FP_PUSH &&
12571 atPower != FP_PULL)
12572 { //Only these powers can be absorbed
12573 return -1;
12574 }
12575
12576 if (!atdAbsLevel)
12577 { //looks like attacker doesn't have any absorb power
12578 return -1;
12579 }
12580
12581 if (!(attacked->client->ps.forcePowersActive & (1 << FP_ABSORB)))
12582 { //absorb is not active
12583 return -1;
12584 }
12585
12586 //Subtract absorb power level from the offensive force power
12587 getLevel = atPowerLevel;
12588 getLevel -= atdAbsLevel;
12589
12590 if (getLevel < 0)
12591 {
12592 getLevel = 0;
12593 }
12594
12595 //let the attacker absorb an amount of force used in this attack based on his level of absorb
12596 addTot = (atForceSpent/3)*attacked->client->ps.forcePowerLevel[FP_ABSORB];
12597
12598 if (addTot < 1 && atForceSpent >= 1)
12599 {
12600 addTot = 1;
12601 }
12602 attacked->client->ps.forcePower += addTot;
12603 if (attacked->client->ps.forcePower > attacked->client->ps.forcePowerMax)
12604 {
12605 attacked->client->ps.forcePower = attacked->client->ps.forcePowerMax;
12606 }
12607
12608 G_SoundOnEnt( attacked, CHAN_ITEM, "sound/weapons/force/absorbhit.wav" );
12609
12610 return getLevel;
12611 }
12612
WP_ForcePowerRegenerate(gentity_t * self,int overrideAmt)12613 void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt )
12614 {
12615 if ( !self->client )
12616 {
12617 return;
12618 }
12619
12620 if ( self->client->ps.forcePower < self->client->ps.forcePowerMax )
12621 {
12622 if ( overrideAmt )
12623 {
12624 self->client->ps.forcePower += overrideAmt;
12625 }
12626 else
12627 {
12628 self->client->ps.forcePower++;
12629 }
12630 if ( self->client->ps.forcePower > self->client->ps.forcePowerMax )
12631 {
12632 self->client->ps.forcePower = self->client->ps.forcePowerMax;
12633 }
12634 }
12635 }
12636
WP_ForcePowerDrain(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12637 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12638 {
12639 if ( self->NPC )
12640 {//For now, NPCs have infinite force power
12641 return;
12642 }
12643 //take away the power
12644 int drain = overrideAmt;
12645 if ( !drain )
12646 {
12647 drain = forcePowerNeeded[forcePower];
12648 }
12649 if ( !drain )
12650 {
12651 return;
12652 }
12653 self->client->ps.forcePower -= drain;
12654 if ( self->client->ps.forcePower < 0 )
12655 {
12656 self->client->ps.forcePower = 0;
12657 }
12658 }
12659
WP_ForcePowerStart(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12660 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12661 {
12662 int duration = 0;
12663
12664 //FIXME: debounce some of these?
12665 self->client->ps.forcePowerDebounce[forcePower] = 0;
12666
12667 //and it in
12668 //set up duration time
12669 switch( (int)forcePower )
12670 {
12671 case FP_HEAL:
12672 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12673 self->client->ps.forceHealCount = 0;
12674 WP_StartForceHealEffects( self );
12675 break;
12676 case FP_LEVITATION:
12677 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12678 break;
12679 case FP_SPEED:
12680 //duration is always 5 seconds, player time
12681 duration = ceil(FORCE_SPEED_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right...
12682 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12683 self->s.loopSound = G_SoundIndex( "sound/weapons/force/speedloop.wav" );
12684 if ( self->client->ps.forcePowerLevel[FP_SPEED] > FORCE_LEVEL_2 )
12685 {//HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
12686 self->client->ps.forcePowerDebounce[FP_SPEED] = level.time;
12687 }
12688 break;
12689 case FP_PUSH:
12690 break;
12691 case FP_PULL:
12692 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12693 break;
12694 case FP_TELEPATHY:
12695 break;
12696 case FP_GRIP:
12697 duration = 1000;
12698 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12699 //HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
12700 //self->client->ps.forcePowerDebounce[forcePower] = level.time;
12701 break;
12702 case FP_LIGHTNING:
12703 duration = overrideAmt;
12704 overrideAmt = 0;
12705 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12706 break;
12707 //new Jedi Academy force powers
12708 case FP_RAGE:
12709 //duration is always 5 seconds, player time
12710 duration = ceil(FORCE_RAGE_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right...
12711 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12712 G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/rage.mp3" );
12713 self->s.loopSound = G_SoundIndex( "sound/weapons/force/rageloop.wav" );
12714 if ( self->chestBolt != -1 )
12715 {
12716 G_PlayEffect( G_EffectIndex( "force/rage2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, duration, qtrue );
12717 }
12718 break;
12719 case FP_DRAIN:
12720 if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1
12721 && self->client->ps.forceDrainEntityNum >= ENTITYNUM_WORLD )
12722 {
12723 duration = overrideAmt;
12724 overrideAmt = 0;
12725 //HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else
12726 self->client->ps.forcePowerDebounce[forcePower] = level.time;
12727 }
12728 else
12729 {
12730 duration = 1000;
12731 }
12732 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12733 break;
12734 case FP_PROTECT:
12735 switch ( self->client->ps.forcePowerLevel[FP_PROTECT] )
12736 {
12737 case FORCE_LEVEL_3:
12738 duration = 20000;
12739 break;
12740 case FORCE_LEVEL_2:
12741 duration = 15000;
12742 break;
12743 case FORCE_LEVEL_1:
12744 default:
12745
12746 duration = 10000;
12747 break;
12748 }
12749 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12750 G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/protect.mp3" );
12751 self->s.loopSound = G_SoundIndex( "sound/weapons/force/protectloop.wav" );
12752 break;
12753 case FP_ABSORB:
12754 duration = 20000;
12755 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12756 G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/absorb.mp3" );
12757 self->s.loopSound = G_SoundIndex( "sound/weapons/force/absorbloop.wav" );
12758 break;
12759 case FP_SEE:
12760 if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_1 )
12761 {
12762 duration = 5000;
12763 }
12764 else if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_2 )
12765 {
12766 duration = 10000;
12767 }
12768 else// if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_3 )
12769 {
12770 duration = 20000;
12771 }
12772
12773 self->client->ps.forcePowersActive |= ( 1 << forcePower );
12774 G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.mp3" );
12775 self->s.loopSound = G_SoundIndex( "sound/weapons/force/seeloop.wav" );
12776 break;
12777 default:
12778 break;
12779 }
12780 if ( duration )
12781 {
12782 self->client->ps.forcePowerDuration[forcePower] = level.time + duration;
12783 }
12784 else
12785 {
12786 self->client->ps.forcePowerDuration[forcePower] = 0;
12787 }
12788
12789 WP_ForcePowerDrain( self, forcePower, overrideAmt );
12790
12791 if ( !self->s.number )
12792 {
12793 self->client->sess.missionStats.forceUsed[(int)forcePower]++;
12794 }
12795 }
12796
WP_ForcePowerAvailable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12797 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12798 {
12799 if ( forcePower == FP_LEVITATION )
12800 {
12801 return qtrue;
12802 }
12803 int drain = overrideAmt?overrideAmt:forcePowerNeeded[forcePower];
12804 if ( !drain )
12805 {
12806 return qtrue;
12807 }
12808 if ( self->client->ps.forcePower < drain )
12809 {
12810 //G_AddEvent( self, EV_NOAMMO, 0 );
12811 return qfalse;
12812 }
12813 return qtrue;
12814 }
12815
12816 extern void CG_PlayerLockedWeaponSpeech( int jumping );
12817 extern qboolean Rosh_TwinNearBy( gentity_t *self );
WP_ForcePowerUsable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)12818 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
12819 {
12820 if ( !(self->client->ps.forcePowersKnown & ( 1 << forcePower )) )
12821 {//don't know this power
12822 return qfalse;
12823 }
12824 else if ( self->NPC && (self->NPC->aiFlags&NPCAI_ROSH) )
12825 {
12826 if ( ((1<<forcePower)&FORCE_POWERS_ROSH_FROM_TWINS) )
12827 {//this is a force power we can only use when a twin is near us
12828 if ( !Rosh_TwinNearBy( self ) )
12829 {
12830 return qfalse;
12831 }
12832 }
12833 }
12834
12835 if ( self->client->ps.forcePowerLevel[forcePower] <= 0 )
12836 {//can't use this power
12837 return qfalse;
12838 }
12839
12840 if( (self->flags&FL_LOCK_PLAYER_WEAPONS) ) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one
12841 {
12842 if ( self->s.number < MAX_CLIENTS )
12843 {
12844 CG_PlayerLockedWeaponSpeech( qfalse );
12845 }
12846 return qfalse;
12847 }
12848
12849 if ( in_camera && self->s.number < MAX_CLIENTS )
12850 {//player can't turn on force powers duing cinematics
12851 return qfalse;
12852 }
12853
12854 if ( PM_LockedAnim( self->client->ps.torsoAnim ) && self->client->ps.torsoAnimTimer )
12855 {//no force powers during these special anims
12856 return qfalse;
12857 }
12858 if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim )
12859 || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) )
12860 {
12861 return qfalse;
12862 }
12863
12864 if ( (self->client->ps.forcePowersActive & ( 1 << forcePower )) )
12865 {//already using this power
12866 return qfalse;
12867 }
12868 /*
12869 if ( !self->client->ps.forcePowerLevel[(int)(forcePower)] )
12870 {
12871 return qfalse;
12872 }
12873 */
12874 if ( self->client->NPC_class == CLASS_ATST )
12875 {//Doh! No force powers in an AT-ST!
12876 return qfalse;
12877 }
12878 Vehicle_t *pVeh = NULL;
12879 if ( (pVeh = G_IsRidingVehicle( self )) != NULL )
12880 {//Doh! No force powers when flying a vehicle!
12881 if ( pVeh->m_pVehicleInfo->numHands > 1 )
12882 {//if in a two-handed vehicle
12883 return qfalse;
12884 }
12885 }
12886 if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
12887 {//Doh! No force powers when controlling an NPC
12888 return qfalse;
12889 }
12890 if ( self->client->ps.eFlags & EF_LOCKED_TO_WEAPON )
12891 {//Doh! No force powers when in an emplaced gun!
12892 return qfalse;
12893 }
12894
12895 if ( (self->client->ps.saber[0].saberFlags&SFL_SINGLE_BLADE_THROWABLE)//SaberStaff() //using staff
12896 && !self->client->ps.dualSabers //only 1, in right hand
12897 && !self->client->ps.saber[0].blade[1].active )//only first blade is on
12898 {//allow power
12899 //FIXME: externalize this condition seperately?
12900 }
12901 else
12902 {
12903 if ( forcePower == FP_SABERTHROW && (self->client->ps.saber[0].saberFlags&SFL_NOT_THROWABLE) )
12904 {//cannot throw this kind of saber
12905 return qfalse;
12906 }
12907
12908 if ( self->client->ps.saber[0].Active() )
12909 {
12910 if ( (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED) )
12911 {
12912 if ( g_saberRestrictForce->integer )
12913 {
12914 switch ( forcePower )
12915 {
12916 case FP_PUSH:
12917 case FP_PULL:
12918 case FP_TELEPATHY:
12919 case FP_GRIP:
12920 case FP_LIGHTNING:
12921 case FP_DRAIN:
12922 return qfalse;
12923 default:
12924 break;
12925 }
12926 }
12927 }
12928 if ( (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)
12929 || (self->client->ps.dualSabers && self->client->ps.saber[1].Active()) )
12930 {//this saber requires the use of two hands OR our other hand is using an active saber too
12931 if ( (self->client->ps.saber[0].forceRestrictions&(1<<forcePower)) )
12932 {//this power is verboten when using this saber
12933 return qfalse;
12934 }
12935 }
12936 }
12937 if ( self->client->ps.dualSabers && self->client->ps.saber[1].Active() )
12938 {
12939 if ( g_saberRestrictForce->integer )
12940 {
12941 switch ( forcePower )
12942 {
12943 case FP_PUSH:
12944 case FP_PULL:
12945 case FP_TELEPATHY:
12946 case FP_GRIP:
12947 case FP_LIGHTNING:
12948 case FP_DRAIN:
12949 return qfalse;
12950 default:
12951 break;
12952 }
12953 }
12954 if ( (self->client->ps.saber[1].forceRestrictions&(1<<forcePower)) )
12955 {//this power is verboten when using this saber
12956 return qfalse;
12957 }
12958 }
12959 }
12960
12961 return WP_ForcePowerAvailable( self, forcePower, overrideAmt );
12962 }
12963
WP_ForcePowerStop(gentity_t * self,forcePowers_t forcePower)12964 void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower )
12965 {
12966 gentity_t *gripEnt;
12967 gentity_t *drainEnt;
12968
12969 if ( !(self->client->ps.forcePowersActive&(1<<forcePower)) )
12970 {//umm, wasn't doing it, so...
12971 return;
12972 }
12973
12974 self->client->ps.forcePowersActive &= ~( 1 << forcePower );
12975
12976 switch( (int)forcePower )
12977 {
12978 case FP_HEAL:
12979 //if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 )
12980 {//wasn't an instant heal and heal is now done
12981 if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
12982 {//if in meditation pose, must come out of it
12983 //FIXME: BOTH_FORCEHEAL_STOP
12984 if ( self->client->ps.legsAnim == BOTH_FORCEHEAL_START )
12985 {
12986 NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12987 }
12988 if ( self->client->ps.torsoAnim == BOTH_FORCEHEAL_START )
12989 {
12990 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
12991 }
12992 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
12993 self->client->ps.saberBlocked = BLOCKED_NONE;
12994 }
12995 }
12996 WP_StopForceHealEffects( self );
12997 if (self->health >= self->client->ps.stats[STAT_MAX_HEALTH]/3)
12998 {
12999 gi.G2API_ClearSkinGore(self->ghoul2);
13000 }
13001 break;
13002 case FP_LEVITATION:
13003 self->client->ps.forcePowerDebounce[FP_LEVITATION] = 0;
13004 break;
13005 case FP_SPEED:
13006 if ( !self->s.number )
13007 {//player using force speed
13008 if ( g_timescale->value != 1.0 )
13009 {
13010 if ( !(self->client->ps.forcePowersActive&(1<<FP_RAGE))||self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 )
13011 {//not slowed down because of force rage
13012 gi.cvar_set("timescale", "1");
13013 }
13014 }
13015 }
13016 //FIXME: reset my current anim, keeping current frame, but with proper anim speed
13017 // otherwise, the anim will continue playing at high speed
13018 self->s.loopSound = 0;
13019 break;
13020 case FP_PUSH:
13021 break;
13022 case FP_PULL:
13023 break;
13024 case FP_TELEPATHY:
13025 break;
13026 case FP_GRIP:
13027 if ( self->NPC )
13028 {
13029 TIMER_Set( self, "gripping", -level.time );
13030 }
13031 if ( self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
13032 {
13033 gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
13034 if ( gripEnt )
13035 {
13036 gripEnt->s.loopSound = 0;
13037 if ( gripEnt->client )
13038 {
13039 gripEnt->client->ps.eFlags &= ~EF_FORCE_GRIPPED;
13040 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13041 {//sanity-cap the velocity
13042 float gripVel = VectorNormalize( gripEnt->client->ps.velocity );
13043 if ( gripVel > 500.0f )
13044 {
13045 gripVel = 500.0f;
13046 }
13047 VectorScale( gripEnt->client->ps.velocity, gripVel, gripEnt->client->ps.velocity );
13048 }
13049
13050 //FIXME: they probably dropped their weapon, should we make them flee? Or should AI handle no-weapon behavior?
13051 //rww - RAGDOLL_BEGIN
13052 #ifndef JK2_RAGDOLL_GRIPNOHEALTH
13053 //rww - RAGDOLL_END
13054 if ( gripEnt->health > 0 )
13055 //rww - RAGDOLL_BEGIN
13056 #endif
13057 //rww - RAGDOLL_END
13058 {
13059 int holdTime = 500;
13060 if ( gripEnt->health > 0 )
13061 {
13062 G_AddEvent( gripEnt, EV_WATER_CLEAR, 0 );
13063 }
13064 if ( gripEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
13065 {//they probably pushed out of it
13066 holdTime = 0;
13067 }
13068 else if ( gripEnt->s.weapon == WP_SABER )
13069 {//jedi recover faster
13070 holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*200;
13071 }
13072 else
13073 {
13074 holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*500;
13075 }
13076 //stop the anims soon, keep them locked in place for a bit
13077 if ( gripEnt->client->ps.torsoAnim == BOTH_CHOKE1 || gripEnt->client->ps.torsoAnim == BOTH_CHOKE3 )
13078 {//stop choking anim on torso
13079 if ( gripEnt->client->ps.torsoAnimTimer > holdTime )
13080 {
13081 gripEnt->client->ps.torsoAnimTimer = holdTime;
13082 }
13083 }
13084 if ( gripEnt->client->ps.legsAnim == BOTH_CHOKE1 || gripEnt->client->ps.legsAnim == BOTH_CHOKE3 )
13085 {//stop choking anim on legs
13086 gripEnt->client->ps.legsAnimTimer = 0;
13087 if ( holdTime )
13088 {
13089 //lock them in place for a bit
13090 gripEnt->client->ps.pm_time = gripEnt->client->ps.torsoAnimTimer;
13091 gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
13092 if ( gripEnt->s.number )
13093 {//NPC
13094 gripEnt->painDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
13095 }
13096 else
13097 {//player
13098 gripEnt->aimDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
13099 }
13100 }
13101 }
13102 if ( gripEnt->NPC )
13103 {
13104 if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
13105 {//not falling to their death
13106 gripEnt->NPC->nextBStateThink = level.time + holdTime;
13107 }
13108 //if still alive after stopped gripping, let them wake others up
13109 if ( gripEnt->health > 0 )
13110 {
13111 G_AngerAlert( gripEnt );
13112 }
13113 }
13114 }
13115 }
13116 else
13117 {
13118 gripEnt->s.eFlags &= ~EF_FORCE_GRIPPED;
13119 if ( gripEnt->s.eType == ET_MISSILE )
13120 {//continue normal movement
13121 if ( gripEnt->s.weapon == WP_THERMAL )
13122 {
13123 gripEnt->s.pos.trType = TR_INTERPOLATE;
13124 }
13125 else
13126 {
13127 gripEnt->s.pos.trType = TR_LINEAR;//FIXME: what about gravity-effected projectiles?
13128 }
13129 VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
13130 gripEnt->s.pos.trTime = level.time;
13131 }
13132 else
13133 {//drop it
13134 gripEnt->e_ThinkFunc = thinkF_G_RunObject;
13135 gripEnt->nextthink = level.time + FRAMETIME;
13136 gripEnt->s.pos.trType = TR_GRAVITY;
13137 VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
13138 gripEnt->s.pos.trTime = level.time;
13139 }
13140 }
13141 }
13142 self->s.loopSound = 0;
13143 self->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
13144 }
13145 if ( self->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD )
13146 {
13147 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEGRIP_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13148 }
13149 break;
13150 case FP_LIGHTNING:
13151 if ( self->NPC )
13152 {
13153 TIMER_Set( self, "holdLightning", -level.time );
13154 }
13155 if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_HOLD
13156 || self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
13157 {
13158 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13159 }
13160 else if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
13161 || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START )
13162 {
13163 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13164 }
13165 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
13166 {//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
13167 self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;//FIXME: define?
13168 }
13169 else
13170 {//stop the looping sound
13171 self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 1000;//FIXME: define?
13172 self->s.loopSound = 0;
13173 }
13174 break;
13175 case FP_RAGE:
13176 self->client->ps.forceRageRecoveryTime = level.time + 10000;//recover for 10 seconds
13177 if ( self->client->ps.forcePowerDuration[FP_RAGE] > level.time )
13178 {//still had time left, we cut it short
13179 self->client->ps.forceRageRecoveryTime -= (self->client->ps.forcePowerDuration[FP_RAGE] - level.time);//minus however much time you had left when you cut it short
13180 }
13181 if ( !self->s.number )
13182 {//player using force speed
13183 if ( g_timescale->value != 1.0 )
13184 {
13185 if ( !(self->client->ps.forcePowersActive&(1<<FP_SPEED)) )
13186 {//not slowed down because of force speed
13187 gi.cvar_set("timescale", "1");
13188 }
13189 }
13190 }
13191 //FIXME: reset my current anim, keeping current frame, but with proper anim speed
13192 // otherwise, the anim will continue playing at high speed
13193 self->s.loopSound = 0;
13194 if ( self->NPC )
13195 {
13196 Jedi_RageStop( self );
13197 }
13198 if ( self->chestBolt != -1 )
13199 {
13200 G_StopEffect("force/rage2", self->playerModel, self->chestBolt, self->s.number );
13201 }
13202 break;
13203 case FP_DRAIN:
13204 if ( self->NPC )
13205 {
13206 TIMER_Set( self, "draining", -level.time );
13207 }
13208 if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 )
13209 {//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
13210 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 3000;//FIXME: define?
13211 }
13212 else
13213 {//stop the looping sound
13214 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 1000;//FIXME: define?
13215 self->s.loopSound = 0;
13216 }
13217 //drop them
13218 if ( self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD )
13219 {
13220 drainEnt = &g_entities[self->client->ps.forceDrainEntityNum];
13221 if ( drainEnt )
13222 {
13223 if ( drainEnt->client )
13224 {
13225 drainEnt->client->ps.eFlags &= ~EF_FORCE_DRAINED;
13226 //VectorClear( drainEnt->client->ps.velocity );
13227 if ( drainEnt->health > 0 )
13228 {
13229 if ( drainEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
13230 {//they probably pushed out of it
13231 }
13232 else
13233 {
13234 //NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEESTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13235 if ( drainEnt->client->ps.torsoAnim != BOTH_FORCEPUSH )
13236 {//don't stop the push
13237 drainEnt->client->ps.torsoAnimTimer = 0;
13238 }
13239 drainEnt->client->ps.legsAnimTimer = 0;
13240 }
13241 if ( drainEnt->NPC )
13242 {//if still alive after stopped draining, let them wake others up
13243 G_AngerAlert( drainEnt );
13244 }
13245 }
13246 else
13247 {//leave the effect playing on them for a few seconds
13248 //drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED;
13249 drainEnt->s.powerups |= ( 1 << PW_DRAINED );
13250 drainEnt->client->ps.powerups[PW_DRAINED] = level.time + Q_irand( 1000, 4000 );
13251 }
13252 }
13253 }
13254 self->client->ps.forceDrainEntityNum = ENTITYNUM_NONE;
13255 }
13256 if ( self->client->ps.torsoAnim == BOTH_HUGGER1 )
13257 {//old anim
13258 NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGERSTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13259 }
13260 else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START
13261 || self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD )
13262 {//new anim
13263 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13264 }
13265 else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_HOLD
13266 || self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START )
13267 {
13268 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13269 }
13270 break;
13271 case FP_PROTECT:
13272 self->s.loopSound = 0;
13273 break;
13274 case FP_ABSORB:
13275 self->s.loopSound = 0;
13276 if ( self->client->ps.legsAnim == BOTH_FORCE_ABSORB_START )
13277 {
13278 NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13279 }
13280 if ( self->client->ps.torsoAnim == BOTH_FORCE_ABSORB_START )
13281 {
13282 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13283 }
13284 if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_2 )
13285 {//was stuck, free us in case we interrupted it or something
13286 self->client->ps.weaponTime = 0;
13287 self->client->ps.pm_flags &= ~PMF_TIME_KNOCKBACK;
13288 self->client->ps.pm_time = 0;
13289 if ( self->s.number )
13290 {//NPC
13291 self->painDebounceTime = 0;
13292 }
13293 else
13294 {//player
13295 self->aimDebounceTime = 0;
13296 }
13297 }
13298 break;
13299 case FP_SEE:
13300 self->s.loopSound = 0;
13301 break;
13302 default:
13303 break;
13304 }
13305 }
13306
WP_ForceForceThrow(gentity_t * thrower)13307 void WP_ForceForceThrow( gentity_t *thrower )
13308 {
13309 if ( !thrower || !thrower->client )
13310 {
13311 return;
13312 }
13313 qboolean relock = qfalse;
13314 if ( !(thrower->client->ps.forcePowersKnown&(1<<FP_PUSH)) )
13315 {//give them push just for this specific purpose
13316 thrower->client->ps.forcePowersKnown |= (1<<FP_PUSH);
13317 thrower->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
13318 }
13319
13320 if ( thrower->NPC
13321 && (thrower->NPC->aiFlags&NPCAI_HEAL_ROSH)
13322 && (thrower->flags&FL_LOCK_PLAYER_WEAPONS) )
13323 {
13324 thrower->flags &= ~FL_LOCK_PLAYER_WEAPONS;
13325 relock = qtrue;
13326 }
13327
13328 ForceThrow( thrower, qfalse );
13329
13330 if ( relock )
13331 {
13332 thrower->flags |= FL_LOCK_PLAYER_WEAPONS;
13333 }
13334
13335 if ( thrower )
13336 {//take it back off
13337 thrower->client->ps.forcePowersKnown &= ~(1<<FP_PUSH);
13338 thrower->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_0;
13339 }
13340 }
13341
13342 extern qboolean PM_ForceJumpingUp( gentity_t *gent );
WP_ForcePowerRun(gentity_t * self,forcePowers_t forcePower,usercmd_t * cmd)13343 static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd )
13344 {
13345 float speed, newSpeed;
13346 gentity_t *gripEnt, *drainEnt;
13347 vec3_t angles, dir, gripOrg, gripEntOrg;
13348 float dist;
13349 extern usercmd_t ucmd;
13350
13351 switch( (int)forcePower )
13352 {
13353 case FP_HEAL:
13354 if ( self->client->ps.forceHealCount >= FP_MaxForceHeal(self) || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] )
13355 {//fully healed or used up all 25
13356 if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
13357 {
13358 int index = Q_irand( 1, 4 );
13359 if ( self->s.number < MAX_CLIENTS )
13360 {
13361 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", index, g_sex->string[0] ) );
13362 }
13363 else if ( self->NPC )
13364 {
13365 if ( self->NPC->blockedSpeechDebounceTime <= level.time )
13366 {//enough time has passed since our last speech
13367 if ( Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
13368 {//not playing a scripted line
13369 //say "Ahhh...."
13370 if ( self->NPC->stats.sex == SEX_MALE
13371 || self->NPC->stats.sex == SEX_NEUTRAL )
13372 {
13373 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_m.mp3", index ) );
13374 }
13375 else//all other sexes use female sounds
13376 {
13377 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_f.mp3", index ) );
13378 }
13379 }
13380 }
13381 }
13382 }
13383 WP_ForcePowerStop( self, forcePower );
13384 }
13385 else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 && ( (cmd->buttons&BUTTON_ATTACK) || (cmd->buttons&BUTTON_ALT_ATTACK) || self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) ) )
13386 {//attacked or was hit while healing...
13387 //stop healing
13388 WP_ForcePowerStop( self, forcePower );
13389 }
13390 else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) )
13391 {//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used
13392 //stop healing
13393 WP_ForcePowerStop( self, forcePower );
13394 }
13395 else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time )
13396 {//time to heal again
13397 if ( WP_ForcePowerAvailable( self, forcePower, 4 ) )
13398 {//have available power
13399 int healInterval = FP_ForceHealInterval( self );
13400 int healAmount = 1;//hard, normal healing rate
13401 if ( self->s.number < MAX_CLIENTS )
13402 {
13403 if ( g_spskill->integer == 1 )
13404 {//medium, heal twice as fast
13405 healAmount *= 2;
13406 }
13407 else if ( g_spskill->integer == 0 )
13408 {//easy, heal 3 times as fast...
13409 healAmount *= 3;
13410 }
13411 }
13412 if ( self->health + healAmount > self->client->ps.stats[STAT_MAX_HEALTH] )
13413 {
13414 healAmount = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
13415 }
13416 self->health += healAmount;
13417 self->client->ps.forceHealCount += healAmount;
13418 self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + healInterval;
13419 WP_ForcePowerDrain( self, forcePower, 4 );
13420 }
13421 else
13422 {//stop
13423 WP_ForcePowerStop( self, forcePower );
13424 }
13425 }
13426 break;
13427 case FP_LEVITATION:
13428 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart )
13429 {//done with jump
13430 WP_ForcePowerStop( self, forcePower );
13431 }
13432 else
13433 {
13434 if ( PM_ForceJumpingUp( self ) )
13435 {//holding jump in air
13436 if ( cmd->upmove > 10 )
13437 {//still trying to go up
13438 if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) )
13439 {
13440 if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time )
13441 {
13442 WP_ForcePowerDrain( self, FP_LEVITATION, 5 );
13443 self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100;
13444 }
13445 self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION );
13446 self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands
13447 }
13448 else
13449 {//cut the jump short
13450 WP_ForcePowerStop( self, forcePower );
13451 }
13452 }
13453 else
13454 {//cut the jump short
13455 WP_ForcePowerStop( self, forcePower );
13456 }
13457 }
13458 else
13459 {
13460 WP_ForcePowerStop( self, forcePower );
13461 }
13462 }
13463 break;
13464 case FP_SPEED:
13465 speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]];
13466 if ( !self->s.number )
13467 {//player using force speed
13468 if ( !(self->client->ps.forcePowersActive&(1<<FP_RAGE))
13469 || self->client->ps.forcePowerLevel[FP_SPEED] >= self->client->ps.forcePowerLevel[FP_RAGE] )
13470 {//either not using rage or rage is at a lower level than speed
13471 gi.cvar_set("timescale", va("%4.2f", speed));
13472 if ( g_timescale->value > speed )
13473 {
13474 newSpeed = g_timescale->value - 0.05;
13475 if ( newSpeed < speed )
13476 {
13477 newSpeed = speed;
13478 }
13479 gi.cvar_set("timescale", va("%4.2f", newSpeed));
13480 }
13481 }
13482 }
13483 break;
13484 case FP_PUSH:
13485 break;
13486 case FP_PULL:
13487 break;
13488 case FP_TELEPATHY:
13489 break;
13490 case FP_GRIP:
13491 if ( !WP_ForcePowerAvailable( self, FP_GRIP, 0 )
13492 || (self->client->ps.forcePowerLevel[FP_GRIP]>FORCE_LEVEL_1&&!self->s.number&&!(cmd->buttons&BUTTON_FORCEGRIP)) )
13493 {
13494 WP_ForcePowerStop( self, FP_GRIP );
13495 return;
13496 }
13497 else if ( self->client->ps.forceGripEntityNum >= 0 && self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
13498 {
13499 gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
13500
13501 if ( !gripEnt || !gripEnt->inuse )
13502 {//invalid or freed ent
13503 WP_ForcePowerStop( self, FP_GRIP );
13504 return;
13505 }
13506 else
13507 //rww - RAGDOLL_BEGIN
13508 #ifndef JK2_RAGDOLL_GRIPNOHEALTH
13509 //rww - RAGDOLL_END
13510 if ( gripEnt->health <= 0 && gripEnt->takedamage )//FIXME: what about things that never had health or lose takedamage when they die?
13511 {//either invalid ent, or dead ent
13512 WP_ForcePowerStop( self, FP_GRIP );
13513 return;
13514 }
13515 else
13516 //rww - RAGDOLL_BEGIN
13517 #endif
13518 //rww - RAGDOLL_END
13519 if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_1
13520 && gripEnt->client
13521 && gripEnt->client->ps.groundEntityNum == ENTITYNUM_NONE
13522 && gripEnt->client->moveType != MT_FLYSWIM )
13523 {
13524 WP_ForcePowerStop( self, FP_GRIP );
13525 return;
13526 }
13527 else if ( gripEnt->client && gripEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( gripEnt->client->ps.velocity ) > (300*300) )
13528 {//flying creature broke free
13529 WP_ForcePowerStop( self, FP_GRIP );
13530 return;
13531 }
13532 else if ( gripEnt->client
13533 && gripEnt->health>0 //dead dudes don't fly
13534 && (gripEnt->client->NPC_class == CLASS_BOBAFETT || gripEnt->client->NPC_class == CLASS_ROCKETTROOPER)
13535 && self->client->ps.forcePowerDebounce[FP_GRIP] < level.time
13536 && !Q_irand( 0, 3 )
13537 )
13538 {//boba fett - fly away!
13539 gripEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim
13540 gripEnt->client->ps.velocity[2] = 250;
13541 gripEnt->client->ps.forceJumpZStart = gripEnt->currentOrigin[2];//so we don't take damage if we land at same height
13542 gripEnt->client->ps.pm_flags |= PMF_JUMPING;
13543 G_AddEvent( gripEnt, EV_JUMP, 0 );
13544 JET_FlyStart( gripEnt );
13545 WP_ForcePowerStop( self, FP_GRIP );
13546 return;
13547 }
13548 else if ( gripEnt->NPC
13549 && gripEnt->client
13550 && gripEnt->client->ps.forcePowersKnown
13551 && (gripEnt->client->NPC_class==CLASS_REBORN||gripEnt->client->ps.weapon==WP_SABER)
13552 && !Jedi_CultistDestroyer(gripEnt)
13553 && !Q_irand( 0, 100-(gripEnt->NPC->stats.evasion*8)-(g_spskill->integer*20) ) )
13554 {//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
13555 WP_ForceForceThrow( gripEnt );
13556 //FIXME: I need to go into some pushed back anim...
13557 WP_ForcePowerStop( self, FP_GRIP );
13558 return;
13559 }
13560 else if ( PM_SaberInAttack( self->client->ps.saberMove )
13561 || PM_SaberInStart( self->client->ps.saberMove ) )
13562 {//started an attack
13563 WP_ForcePowerStop( self, FP_GRIP );
13564 return;
13565 }
13566 else
13567 {
13568 int gripLevel = self->client->ps.forcePowerLevel[FP_GRIP];
13569 if ( gripEnt->client )
13570 {
13571 gripLevel = WP_AbsorbConversion( gripEnt, gripEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[gripLevel] );
13572 }
13573 if ( !gripLevel )
13574 {
13575 WP_ForcePowerStop( self, forcePower );
13576 return;
13577 }
13578
13579 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13580 {//holding it
13581 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13582 if ( self->client->ps.torsoAnimTimer < 100 ){//we were already playing this anim, we didn't want to restart it, but we want to hold it for at least 100ms, sooo....
13583
13584 self->client->ps.torsoAnimTimer = 100;
13585 }
13586 }
13587 //get their org
13588 VectorCopy( self->client->ps.viewangles, angles );
13589 angles[0] -= 10;
13590 AngleVectors( angles, dir, NULL, NULL );
13591 if ( gripEnt->client )
13592 {//move
13593 VectorCopy( gripEnt->client->renderInfo.headPoint, gripEntOrg );
13594 }
13595 else
13596 {
13597 VectorCopy( gripEnt->currentOrigin, gripEntOrg );
13598 }
13599
13600 //how far are they
13601 dist = Distance( self->client->renderInfo.handLPoint, gripEntOrg );
13602 if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 &&
13603 (!InFront( gripEntOrg, self->client->renderInfo.handLPoint, self->client->ps.viewangles, 0.3f ) ||
13604 DistanceSquared( gripEntOrg, self->client->renderInfo.handLPoint ) > FORCE_GRIP_DIST_SQUARED))
13605 {//must face them
13606 WP_ForcePowerStop( self, FP_GRIP );
13607 return;
13608 }
13609
13610 //check for lift or carry
13611 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
13612 && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) )
13613 {//carry
13614 //cap dist
13615 if ( dist > FORCE_GRIP_3_MAX_DIST )
13616 {
13617 dist = FORCE_GRIP_3_MAX_DIST;
13618 }
13619 else if ( dist < FORCE_GRIP_3_MIN_DIST )
13620 {
13621 dist = FORCE_GRIP_3_MIN_DIST;
13622 }
13623 VectorMA( self->client->renderInfo.handLPoint, dist, dir, gripOrg );
13624 }
13625 else if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13626 {//just lift
13627 VectorCopy( self->client->ps.forceGripOrg, gripOrg );
13628 }
13629 else
13630 {
13631 VectorCopy( gripEnt->currentOrigin, gripOrg );
13632 }
13633 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13634 {//if holding him, make sure there's a clear LOS between my hand and him
13635 trace_t gripTrace;
13636 gi.trace( &gripTrace, self->client->renderInfo.handLPoint, NULL, NULL, gripEntOrg, ENTITYNUM_NONE, MASK_FORCE_PUSH, (EG2_Collision)0, 0 );
13637 if ( gripTrace.startsolid
13638 || gripTrace.allsolid
13639 || gripTrace.fraction < 1.0f )
13640 {//no clear trace, drop them
13641 WP_ForcePowerStop( self, FP_GRIP );
13642 return;
13643 }
13644 }
13645 //now move them
13646 if ( gripEnt->client )
13647 {
13648 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13649 {//level 1 just holds them
13650 VectorSubtract( gripOrg, gripEntOrg, gripEnt->client->ps.velocity );
13651 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
13652 && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK)) ) )
13653 {//level 2 just lifts them
13654 float gripDist = VectorNormalize( gripEnt->client->ps.velocity )/3.0f;
13655 if ( gripDist < 20.0f )
13656 {
13657 if (gripDist<2.0f)
13658 {
13659 VectorClear(gripEnt->client->ps.velocity);
13660 }
13661 else
13662 {
13663 VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
13664 }
13665 }
13666 else
13667 {
13668 VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
13669 }
13670 }
13671 }
13672 //stop them from thinking
13673 gripEnt->client->ps.pm_time = 2000;
13674 gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
13675 if ( gripEnt->NPC )
13676 {
13677 if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
13678 {//not falling to their death
13679 gripEnt->NPC->nextBStateThink = level.time + 2000;
13680 }
13681 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13682 {//level 1 just holds them
13683 vectoangles( dir, angles );
13684 gripEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
13685 gripEnt->NPC->desiredPitch = -angles[PITCH];
13686 SaveNPCGlobals();
13687 SetNPCGlobals( gripEnt );
13688 NPC_UpdateAngles( qtrue, qtrue );
13689 gripEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
13690 gripEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
13691 gripEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
13692 RestoreNPCGlobals();
13693 //FIXME: why does he turn back to his original angles once he dies or is let go?
13694 }
13695 }
13696 else if ( !gripEnt->s.number )
13697 {
13698 //vectoangles( dir, angles );
13699 //gripEnt->client->ps.viewangles[0] = -angles[0];
13700 //gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180);
13701 gripEnt->enemy = self;
13702 NPC_SetLookTarget( gripEnt, self->s.number, level.time+1000 );
13703 }
13704
13705 gripEnt->client->ps.eFlags |= EF_FORCE_GRIPPED;
13706 //dammit! Make sure that saber stays off!
13707 WP_DeactivateSaber( gripEnt );
13708 }
13709 else
13710 {//move
13711 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
13712 {//level 1 just holds them
13713 VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
13714 VectorSubtract( gripOrg, gripEntOrg, gripEnt->s.pos.trDelta );
13715 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2
13716 && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) )
13717 {//level 2 just lifts them
13718 VectorScale( gripEnt->s.pos.trDelta, 10, gripEnt->s.pos.trDelta );
13719 }
13720 gripEnt->s.pos.trType = TR_LINEAR;
13721 gripEnt->s.pos.trTime = level.time;
13722 }
13723
13724 gripEnt->s.eFlags |= EF_FORCE_GRIPPED;
13725 }
13726
13727 //Shouldn't this be discovered?
13728 //AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 );
13729 AddSightEvent( self, gripOrg, 128, AEL_DISCOVERED, 20 );
13730
13731 if ( self->client->ps.forcePowerDebounce[FP_GRIP] < level.time )
13732 {
13733 //GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH );
13734 if ( !gripEnt->client
13735 || gripEnt->client->NPC_class != CLASS_VEHICLE
13736 || (gripEnt->m_pVehicle
13737 && gripEnt->m_pVehicle->m_pVehicleInfo
13738 && gripEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) )
13739 {//we don't damage the empty vehicle
13740 gripEnt->painDebounceTime = 0;
13741 int gripDmg = forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]];
13742 if ( gripLevel != -1 )
13743 {
13744 if ( gripLevel == 1 )
13745 {
13746 gripDmg = floor((float)gripDmg/3.0f);
13747 }
13748 else //if ( gripLevel == 2 )
13749 {
13750 gripDmg = floor((float)gripDmg/1.5f);
13751 }
13752 }
13753 G_Damage( gripEnt, self, self, dir, gripOrg, gripDmg, DAMAGE_NO_ARMOR, MOD_CRUSH );//MOD_???
13754 }
13755 if ( gripEnt->s.number )
13756 {
13757 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
13758 {//do damage faster at level 3
13759 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 150, 750 );
13760 }
13761 else
13762 {
13763 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 250, 1000 );
13764 }
13765 }
13766 else
13767 {//player takes damage faster
13768 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 100, 600 );
13769 }
13770 if ( forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]] > 0 )
13771 {//no damage at level 1
13772 WP_ForcePowerDrain( self, FP_GRIP, 3 );
13773 }
13774 if ( self->client->NPC_class == CLASS_KYLE
13775 && (self->spawnflags&1) )
13776 {//"Boss" Kyle
13777 if ( gripEnt->client )
13778 {
13779 if ( !Q_irand( 0, 2 ) )
13780 {//toss him aside!
13781 vec3_t vRt;
13782 AngleVectors( self->currentAngles, NULL, vRt, NULL );
13783 //stop gripping
13784 TIMER_Set( self, "gripping", -level.time );
13785 WP_ForcePowerStop( self, FP_GRIP );
13786 //now toss him
13787 if ( Q_irand( 0, 1 ) )
13788 {//throw him to my left
13789 NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13790 VectorScale( vRt, -1500.0f, gripEnt->client->ps.velocity );
13791 G_Knockdown( gripEnt, self, vRt, 500, qfalse );
13792 }
13793 else
13794 {//throw him to my right
13795 NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13796 VectorScale( vRt, 1500.0f, gripEnt->client->ps.velocity );
13797 G_Knockdown( gripEnt, self, vRt, 500, qfalse );
13798 }
13799 //don't do anything for a couple seconds
13800 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer + 2000;
13801 self->painDebounceTime = level.time + self->client->ps.weaponTime;
13802 //stop moving
13803 VectorClear( self->client->ps.velocity );
13804 VectorClear( self->client->ps.moveDir );
13805 return;
13806 }
13807 }
13808 }
13809 }
13810 else
13811 {
13812 //WP_ForcePowerDrain( self, FP_GRIP, 0 );
13813 if ( !gripEnt->enemy )
13814 {
13815 if ( gripEnt->client
13816 && gripEnt->client->playerTeam == TEAM_PLAYER
13817 && self->s.number < MAX_CLIENTS
13818 && self->client
13819 && self->client->playerTeam == TEAM_PLAYER )
13820 {//this shouldn't make allies instantly turn on you, let the damage->pain routine determine how allies should react to this
13821 }
13822 else
13823 {
13824 G_SetEnemy( gripEnt, self );
13825 }
13826 }
13827 }
13828 if ( gripEnt->client && gripEnt->health > 0 )
13829 {
13830 int anim = BOTH_CHOKE3; //left-handed choke
13831 if ( gripEnt->client->ps.weapon == WP_NONE || gripEnt->client->ps.weapon == WP_MELEE )
13832 {
13833 anim = BOTH_CHOKE1; //two-handed choke
13834 }
13835 if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
13836 {//still on ground, only set anim on torso
13837 NPC_SetAnim( gripEnt, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13838 }
13839 else
13840 {//in air, set on whole body
13841 NPC_SetAnim( gripEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
13842 }
13843 gripEnt->painDebounceTime = level.time + 2000;
13844 }
13845 }
13846 }
13847 break;
13848 case FP_LIGHTNING:
13849 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
13850 {//higher than level 1
13851 if ( cmd->buttons & BUTTON_FORCE_LIGHTNING )
13852 {//holding it keeps it going
13853 self->client->ps.forcePowerDuration[FP_LIGHTNING] = level.time + 500;
13854 ForceLightningAnim( self );
13855 }
13856 }
13857 if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
13858 {
13859 WP_ForcePowerStop( self, forcePower );
13860 }
13861 else
13862 {
13863 ForceShootLightning( self );
13864 if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING
13865 || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START
13866 || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD
13867 || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE )
13868 {//jackin' 'em up, Palpatine-style
13869 //extra cost
13870 WP_ForcePowerDrain( self, forcePower, 0 );
13871 }
13872 WP_ForcePowerDrain( self, forcePower, 0 );
13873 }
13874 break;
13875 //new Jedi Academy force powers
13876 case FP_RAGE:
13877 if (self->health < 1)
13878 {
13879 WP_ForcePowerStop(self, forcePower);
13880 break;
13881 }
13882 if (self->client->ps.forceRageDrainTime < level.time)
13883 {
13884 int addTime = 400;
13885
13886 self->health -= 2;
13887
13888 if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1)
13889 {
13890 addTime = 100;
13891 }
13892 else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2)
13893 {
13894 addTime = 250;
13895 }
13896 else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3)
13897 {
13898 addTime = 500;
13899 }
13900 self->client->ps.forceRageDrainTime = level.time + addTime;
13901 }
13902
13903 if ( self->health < 1 )
13904 {
13905 self->health = 1;
13906 //WP_ForcePowerStop( self, forcePower );
13907 }
13908 else
13909 {
13910 self->client->ps.stats[STAT_HEALTH] = self->health;
13911
13912 speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1];
13913 if ( !self->s.number )
13914 {//player using force rage
13915 if ( !(self->client->ps.forcePowersActive&(1<<FP_SPEED))
13916 || self->client->ps.forcePowerLevel[FP_RAGE] > self->client->ps.forcePowerLevel[FP_SPEED]+1 )
13917 {//either not using speed or speed is at a lower level than rage
13918 gi.cvar_set("timescale", va("%4.2f", speed));
13919 if ( g_timescale->value > speed )
13920 {
13921 newSpeed = g_timescale->value - 0.05;
13922 if ( newSpeed < speed )
13923 {
13924 newSpeed = speed;
13925 }
13926 gi.cvar_set("timescale", va("%4.2f", newSpeed));
13927 }
13928 }
13929 }
13930 }
13931 break;
13932 case FP_DRAIN:
13933 if ( cmd->buttons & BUTTON_FORCE_DRAIN )
13934 {//holding it keeps it going
13935 self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500;
13936 }
13937 if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
13938 {//no more force power, stop
13939 WP_ForcePowerStop( self, forcePower );
13940 }
13941 else if ( self->client->ps.forceDrainEntityNum >= 0 && self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD )
13942 {//holding someone
13943 if ( !WP_ForcePowerAvailable( self, FP_DRAIN, 0 )
13944 || (self->client->ps.forcePowerLevel[FP_DRAIN]>FORCE_LEVEL_1
13945 && !self->s.number
13946 && !(cmd->buttons&BUTTON_FORCE_DRAIN)
13947 && self->client->ps.forcePowerDuration[FP_DRAIN]<level.time) )
13948 {
13949 WP_ForcePowerStop( self, FP_DRAIN );
13950 return;
13951 }
13952 else
13953 {
13954 drainEnt = &g_entities[self->client->ps.forceDrainEntityNum];
13955
13956 if ( !drainEnt )
13957 {//invalid ent
13958 WP_ForcePowerStop( self, FP_DRAIN );
13959 return;
13960 }
13961 else if ( (drainEnt->health <= 0&&drainEnt->takedamage) )//FIXME: what about things that never had health or lose takedamage when they die?
13962 {//dead ent
13963 WP_ForcePowerStop( self, FP_DRAIN );
13964 return;
13965 }
13966 else if ( drainEnt->client && drainEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( NPC->client->ps.velocity ) > (300*300) )
13967 {//flying creature broke free
13968 WP_ForcePowerStop( self, FP_DRAIN );
13969 return;
13970 }
13971 else if ( drainEnt->client
13972 && drainEnt->health>0 //dead dudes don't fly
13973 && (drainEnt->client->NPC_class == CLASS_BOBAFETT || drainEnt->client->NPC_class == CLASS_ROCKETTROOPER)
13974 && self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time
13975 && !Q_irand( 0, 10 ) )
13976 {//boba fett - fly away!
13977 drainEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim
13978 drainEnt->client->ps.velocity[2] = 250;
13979 drainEnt->client->ps.forceJumpZStart = drainEnt->currentOrigin[2];//so we don't take damage if we land at same height
13980 drainEnt->client->ps.pm_flags |= PMF_JUMPING;
13981 G_AddEvent( drainEnt, EV_JUMP, 0 );
13982 JET_FlyStart( drainEnt );
13983 WP_ForcePowerStop( self, FP_DRAIN );
13984 return;
13985 }
13986 else if ( drainEnt->NPC
13987 && drainEnt->client
13988 && drainEnt->client->ps.forcePowersKnown
13989 && (drainEnt->client->NPC_class==CLASS_REBORN||drainEnt->client->ps.weapon==WP_SABER)
13990 && !Jedi_CultistDestroyer(drainEnt)
13991 && level.time-(self->client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms
13992 && !Q_irand( 0, 100-(drainEnt->NPC->stats.evasion*8)-(g_spskill->integer*15) ) )
13993 {//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
13994 WP_ForceForceThrow( drainEnt );
13995 //FIXME: I need to go into some pushed back anim...
13996 WP_ForcePowerStop( self, FP_DRAIN );
13997 //can't drain again for 2 seconds
13998 self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000;
13999 return;
14000 }
14001 else
14002 {
14003 /*
14004 int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] );
14005 if ( !drainLevel )
14006 {
14007 WP_ForcePowerStop( self, forcePower );
14008 return;
14009 }
14010 */
14011
14012 //NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGER1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14013 if ( self->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_START
14014 || !self->client->ps.torsoAnimTimer )
14015 {
14016 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14017 }
14018 if ( self->handLBolt != -1 )
14019 {
14020 G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handLBolt, self->s.number, self->currentOrigin, 200, qtrue );
14021 }
14022 if ( self->handRBolt != -1 )
14023 {
14024 G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handRBolt, self->s.number, self->currentOrigin, 200, qtrue );
14025 }
14026
14027 //how far are they
14028 dist = Distance( self->client->renderInfo.eyePoint, drainEnt->currentOrigin );
14029 if ( DistanceSquared( drainEnt->currentOrigin, self->currentOrigin ) > FORCE_DRAIN_DIST_SQUARED )
14030 {//must be close, got away somehow!
14031 WP_ForcePowerStop( self, FP_DRAIN );
14032 return;
14033 }
14034
14035 //keep my saber off!
14036 WP_DeactivateSaber( self, qtrue );
14037 if ( drainEnt->client )
14038 {
14039 //now move them
14040 VectorCopy( self->client->ps.viewangles, angles );
14041 angles[0] = 0;
14042 AngleVectors( angles, dir, NULL, NULL );
14043 /*
14044 VectorMA( self->currentOrigin, self->maxs[0], dir, drainEnt->client->ps.forceDrainOrg );
14045 trace_t trace;
14046 gi.trace( &trace, drainEnt->currentOrigin, drainEnt->mins, drainEnt->maxs, drainEnt->client->ps.forceDrainOrg, drainEnt->s.number, drainEnt->clipmask );
14047 if ( !trace.startsolid && !trace.allsolid )
14048 {
14049 G_SetOrigin( drainEnt, trace.endpos );
14050 gi.linkentity( drainEnt );
14051 VectorClear( drainEnt->client->ps.velocity );
14052 }
14053 VectorMA( self->currentOrigin, self->maxs[0]*0.5f, dir, drainEnt->client->ps.forceDrainOrg );
14054 */
14055 //stop them from thinking
14056 drainEnt->client->ps.pm_time = 2000;
14057 drainEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
14058 if ( drainEnt->NPC )
14059 {
14060 if ( !(drainEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
14061 {//not falling to their death
14062 drainEnt->NPC->nextBStateThink = level.time + 2000;
14063 }
14064 vectoangles( dir, angles );
14065 drainEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
14066 drainEnt->NPC->desiredPitch = -angles[PITCH];
14067 SaveNPCGlobals();
14068 SetNPCGlobals( drainEnt );
14069 NPC_UpdateAngles( qtrue, qtrue );
14070 drainEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
14071 drainEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
14072 drainEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
14073 RestoreNPCGlobals();
14074 //FIXME: why does he turn back to his original angles once he dies or is let go?
14075 }
14076 else if ( !drainEnt->s.number )
14077 {
14078 drainEnt->enemy = self;
14079 NPC_SetLookTarget( drainEnt, self->s.number, level.time+1000 );
14080 }
14081
14082 drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED;
14083 //dammit! Make sure that saber stays off!
14084 WP_DeactivateSaber( drainEnt, qtrue );
14085 }
14086 //Shouldn't this be discovered?
14087 AddSightEvent( self, drainEnt->currentOrigin, 128, AEL_DISCOVERED, 20 );
14088
14089 if ( self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time )
14090 {
14091 int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] );
14092 if ( (drainLevel && drainLevel == -1)
14093 || Q_irand( drainLevel, 3 ) < 3 )
14094 {//the drain is being absorbed
14095 ForceDrainEnt( self, drainEnt );
14096 }
14097 WP_ForcePowerDrain( self, FP_DRAIN, 3 );
14098 }
14099 else
14100 {
14101 if ( !Q_irand( 0, 4 ) )
14102 {
14103 WP_ForcePowerDrain( self, FP_DRAIN, 1 );
14104 }
14105 if ( !drainEnt->enemy )
14106 {
14107 G_SetEnemy( drainEnt, self );
14108 }
14109 }
14110 if ( drainEnt->health > 0 )
14111 {//still alive
14112 //NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14113 NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14114 }
14115 }
14116 }
14117 }
14118 else if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1 )
14119 {//regular distance-drain
14120 if ( cmd->buttons & BUTTON_FORCE_DRAIN )
14121 {//holding it keeps it going
14122 self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500;
14123 if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START )
14124 {
14125 if ( !self->client->ps.torsoAnimTimer )
14126 {
14127 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14128 }
14129 else
14130 {
14131 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14132 }
14133 }
14134 else
14135 {
14136 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
14137 }
14138 }
14139 if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
14140 {
14141 WP_ForcePowerStop( self, forcePower );
14142 }
14143 else
14144 {
14145 ForceShootDrain( self );
14146 }
14147 }
14148 break;
14149 case FP_PROTECT:
14150 break;
14151 case FP_ABSORB:
14152 break;
14153 case FP_SEE:
14154 break;
14155 default:
14156 break;
14157 }
14158 }
14159
WP_CheckForcedPowers(gentity_t * self,usercmd_t * ucmd)14160 void WP_CheckForcedPowers( gentity_t *self, usercmd_t *ucmd )
14161 {
14162 for ( int forcePower = FP_FIRST; forcePower < NUM_FORCE_POWERS; forcePower++ )
14163 {
14164 if ( (self->client->ps.forcePowersForced&(1<<forcePower)) )
14165 {
14166 switch ( forcePower )
14167 {
14168 case FP_HEAL:
14169 ForceHeal( self );
14170 //do only once
14171 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14172 break;
14173 case FP_LEVITATION:
14174 //nothing
14175 break;
14176 case FP_SPEED:
14177 ForceSpeed( self );
14178 //do only once
14179 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14180 break;
14181 case FP_PUSH:
14182 ForceThrow( self, qfalse );
14183 //do only once
14184 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14185 break;
14186 case FP_PULL:
14187 ForceThrow( self, qtrue );
14188 //do only once
14189 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14190 break;
14191 case FP_TELEPATHY:
14192 //FIXME: target at enemy?
14193 ForceTelepathy( self );
14194 //do only once
14195 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14196 break;
14197 case FP_GRIP:
14198 ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING);
14199 ucmd->buttons |= BUTTON_FORCEGRIP;
14200 //holds until cleared
14201 break;
14202 case FP_LIGHTNING:
14203 ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN);
14204 ucmd->buttons |= BUTTON_FORCE_LIGHTNING;
14205 //holds until cleared
14206 break;
14207 case FP_SABERTHROW:
14208 ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING);
14209 ucmd->buttons |= BUTTON_ALT_ATTACK;
14210 //holds until cleared?
14211 break;
14212 case FP_SABER_DEFENSE:
14213 //nothing
14214 break;
14215 case FP_SABER_OFFENSE:
14216 //nothing
14217 break;
14218 case FP_RAGE:
14219 ForceRage( self );
14220 //do only once
14221 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14222 break;
14223 case FP_PROTECT:
14224 ForceProtect( self );
14225 //do only once
14226 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14227 break;
14228 case FP_ABSORB:
14229 ForceAbsorb( self );
14230 //do only once
14231 self->client->ps.forcePowersForced &= ~(1<<forcePower);
14232 break;
14233 case FP_DRAIN:
14234 ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_LIGHTNING);
14235 ucmd->buttons |= BUTTON_FORCE_DRAIN;
14236 //holds until cleared
14237 break;
14238 case FP_SEE:
14239 //nothing
14240 break;
14241 }
14242 }
14243 }
14244 }
14245
WP_ForcePowersUpdate(gentity_t * self,usercmd_t * ucmd)14246 void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd )
14247 {
14248 qboolean usingForce = qfalse;
14249 int i;
14250 //see if any force powers are running
14251 if ( !self )
14252 {
14253 return;
14254 }
14255 if ( !self->client )
14256 {
14257 return;
14258 }
14259
14260 if ( self->health <= 0 )
14261 {//if dead, deactivate any active force powers
14262 for ( i = 0; i < NUM_FORCE_POWERS; i++ )
14263 {
14264 if ( self->client->ps.forcePowerDuration[i] || (self->client->ps.forcePowersActive&( 1 << i )) )
14265 {
14266 WP_ForcePowerStop( self, (forcePowers_t)i );
14267 self->client->ps.forcePowerDuration[i] = 0;
14268 }
14269 }
14270 return;
14271 }
14272
14273 WP_CheckForcedPowers( self, ucmd );
14274
14275 if ( !self->s.number )
14276 {//player uses different kind of force-jump
14277 }
14278 else
14279 {
14280 /*
14281 if ( ucmd->buttons & BUTTON_FORCEJUMP )
14282 {//just charging up
14283 ForceJumpCharge( self, ucmd );
14284 }
14285 else */
14286 if ( self->client->ps.forceJumpCharge )
14287 {//let go of charge button, have charge
14288 //if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
14289 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
14290 && !PM_SwimmingAnim( self->client->ps.legsAnim ) )
14291 {//FIXME: stop sound?
14292 //self->client->ps.forceJumpCharge = 0;
14293 //FIXME: actually, we want this to still be cleared... don't clear it if the button isn't being pressed, but clear it if not holding button and not on ground.
14294 }
14295 else
14296 {//still on ground, so jump
14297 ForceJump( self, ucmd );
14298 return;
14299 }
14300 }
14301 }
14302
14303 if ( ucmd->buttons & BUTTON_FORCEGRIP )
14304 {
14305 ForceGrip( self );
14306 }
14307
14308 if ( !self->s.number
14309 && self->client->NPC_class == CLASS_BOBAFETT )
14310 {//Boba Fett
14311 if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
14312 {//start flamethrower
14313 Boba_DoFlameThrower( self );
14314 return;
14315 }
14316 else if ( self->client->ps.forcePowerDuration[FP_LIGHTNING] )
14317 {
14318 self->client->ps.forcePowerDuration[FP_LIGHTNING] = 0;
14319 Boba_StopFlameThrower( self );
14320 return;
14321 }
14322 }
14323 else if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
14324 {
14325 ForceLightning( self );
14326 }
14327
14328 if ( ucmd->buttons & BUTTON_FORCE_DRAIN )
14329 {
14330 if ( !ForceDrain2( self ) )
14331 {//can't drain-grip someone right in front
14332 if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 )
14333 {//try ranged
14334 ForceDrain( self, qtrue );
14335 }
14336 }
14337 }
14338
14339 for ( i = 0; i < NUM_FORCE_POWERS; i++ )
14340 {
14341 if ( self->client->ps.forcePowerDuration[i] )
14342 {
14343 if ( self->client->ps.forcePowerDuration[i] < level.time )
14344 {
14345 if ( (self->client->ps.forcePowersActive&( 1 << i )) )
14346 {//turn it off
14347 WP_ForcePowerStop( self, (forcePowers_t)i );
14348 }
14349 self->client->ps.forcePowerDuration[i] = 0;
14350 }
14351 }
14352 if ( (self->client->ps.forcePowersActive&( 1 << i )) )
14353 {
14354 usingForce = qtrue;
14355 WP_ForcePowerRun( self, (forcePowers_t)i, ucmd );
14356 }
14357 }
14358 if ( self->client->ps.saberInFlight )
14359 {//don't regen force power while throwing saber
14360 if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0
14361 {//
14362 if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR )
14363 {//fell to the ground and we're trying to pull it back
14364 usingForce = qtrue;
14365 }
14366 }
14367 }
14368 if ( PM_ForceUsingSaberAnim( self->client->ps.torsoAnim ) )
14369 {
14370 usingForce = qtrue;
14371 }
14372 if ( !usingForce )
14373 {//when not using the force, regenerate at 10 points per second
14374 if ( self->client->ps.forcePowerRegenDebounceTime < level.time )
14375 {
14376 WP_ForcePowerRegenerate( self, self->client->ps.forcePowerRegenAmount );
14377 self->client->ps.forcePowerRegenDebounceTime = level.time + self->client->ps.forcePowerRegenRate;
14378 if ( self->client->ps.forceRageRecoveryTime >= level.time )
14379 {//regen half as fast
14380 self->client->ps.forcePowerRegenDebounceTime += self->client->ps.forcePowerRegenRate;
14381 }
14382 }
14383 }
14384 }
14385
WP_InitForcePowers(gentity_t * ent)14386 void WP_InitForcePowers( gentity_t *ent )
14387 {
14388 if ( !ent || !ent->client )
14389 {
14390 return;
14391 }
14392
14393 if ( !ent->client->ps.forcePowerMax )
14394 {
14395 ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
14396 }
14397 if ( !ent->client->ps.forcePowerRegenRate )
14398 {
14399 ent->client->ps.forcePowerRegenRate = 100;
14400 }
14401 ent->client->ps.forcePower = ent->client->ps.forcePowerMax;
14402 ent->client->ps.forcePowerRegenDebounceTime = level.time;
14403
14404 ent->client->ps.forceGripEntityNum = ent->client->ps.forceDrainEntityNum = ent->client->ps.pullAttackEntNum = ENTITYNUM_NONE;
14405 ent->client->ps.forceRageRecoveryTime = 0;
14406 ent->client->ps.forceDrainTime = 0;
14407 ent->client->ps.pullAttackTime = 0;
14408
14409 if ( ent->s.number < MAX_CLIENTS )
14410 {//player
14411 if ( !g_cheats->integer )//devmaps give you all the FP
14412 {
14413 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
14414 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
14415 }
14416 else
14417 {
14418 ent->client->ps.forcePowersKnown = ( 1 << FP_HEAL )|( 1 << FP_LEVITATION )|( 1 << FP_SPEED )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_TELEPATHY )|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SABERTHROW)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE )|( 1<< FP_RAGE )|( 1<< FP_DRAIN )|( 1<< FP_PROTECT )|( 1<< FP_ABSORB )|( 1<< FP_SEE );
14419 ent->client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2;
14420 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
14421 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
14422 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
14423 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
14424 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
14425 ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
14426 ent->client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2;
14427
14428 ent->client->ps.forcePowerLevel[FP_RAGE] = FORCE_LEVEL_1;
14429 ent->client->ps.forcePowerLevel[FP_PROTECT] = FORCE_LEVEL_1;
14430 ent->client->ps.forcePowerLevel[FP_ABSORB] = FORCE_LEVEL_1;
14431 ent->client->ps.forcePowerLevel[FP_DRAIN] = FORCE_LEVEL_1;
14432 ent->client->ps.forcePowerLevel[FP_SEE] = FORCE_LEVEL_1;
14433
14434 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
14435 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
14436 ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
14437 }
14438 }
14439 }
14440
WP_DoingMoronicForcedAnimationForForcePowers(gentity_t * ent)14441 bool WP_DoingMoronicForcedAnimationForForcePowers(gentity_t *ent)
14442 {
14443 // :P --eez
14444 if( !ent->client ) return false;
14445 if( ent->client->ps.legsAnim == BOTH_FORCE_ABSORB_START ||
14446 ent->client->ps.legsAnim == BOTH_FORCE_ABSORB_END ||
14447 ent->client->ps.legsAnim == BOTH_FORCE_ABSORB ||
14448 ent->client->ps.torsoAnim == BOTH_FORCE_RAGE ||
14449 ent->client->ps.legsAnim == BOTH_FORCE_PROTECT )
14450 return true;
14451 return false;
14452 }
14453