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_headers.h"
24
25 #include "g_local.h"
26 #include "anims.h"
27 #include "b_local.h"
28 #include "bg_local.h"
29 #include "g_functions.h"
30 #include "wp_saber.h"
31 #include "../../code/qcommon/tri_coll_test.h"
32
33 #define MAX_SABER_VICTIMS 16
34 static int victimEntityNum[MAX_SABER_VICTIMS];
35 static float totalDmg[MAX_SABER_VICTIMS];
36 static vec3_t dmgDir[MAX_SABER_VICTIMS];
37 static vec3_t dmgSpot[MAX_SABER_VICTIMS];
38 static float dmgFraction[MAX_SABER_VICTIMS];
39 static int hitLoc[MAX_SABER_VICTIMS];
40 static qboolean hitDismember[MAX_SABER_VICTIMS];
41 static int hitDismemberLoc[MAX_SABER_VICTIMS];
42 static vec3_t saberHitLocation, saberHitNormal={0,0,1.0};
43 static float saberHitFraction;
44 static float sabersCrossed;
45 static int saberHitEntity;
46 static int numVictims = 0;
47
48 #define SABER_PITCH_HACK 90
49
50
51 extern cvar_t *g_timescale;
52 extern cvar_t *g_dismemberment;
53
54 extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
55 extern qboolean G_ClearViewEntity( gentity_t *ent );
56 extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
57 extern qboolean G_ControlledByPlayer( gentity_t *self );
58 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
59 extern void CG_ChangeWeapon( int num );
60 extern void G_AngerAlert( gentity_t *self );
61 extern void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward );
62 extern int G_CheckLedgeDive( gentity_t *self, float checkDist, vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp );
63 extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
64 extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs );
65 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
66 extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone );
67 extern void WP_FireDreadnoughtBeam( gentity_t *ent );
68 extern void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE );
69 extern evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f );
70 extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
71 extern void NPC_SetPainEvent( gentity_t *self );
72 extern qboolean PM_SwimmingAnim( int anim );
73 extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
74 extern qboolean PM_SpinningSaberAnim( int anim );
75 extern qboolean PM_SaberInSpecialAttack( int anim );
76 extern qboolean PM_SaberInAttack( int move );
77 extern qboolean PM_SaberInTransition( int move );
78 extern qboolean PM_SaberInStart( int move );
79 extern qboolean PM_SaberInTransitionAny( int move );
80 extern qboolean PM_SaberInBounce( int move );
81 extern qboolean PM_SaberInParry( int move );
82 extern qboolean PM_SaberInKnockaway( int move );
83 extern qboolean PM_SaberInBrokenParry( int move );
84 extern qboolean PM_SpinningSaberAnim( int anim );
85 extern int PM_SaberBounceForAttack( int move );
86 extern int PM_BrokenParryForAttack( int move );
87 extern int PM_KnockawayForParry( int move );
88 extern qboolean PM_FlippingAnim( int anim );
89 extern qboolean PM_RollingAnim( int anim );
90 extern qboolean PM_CrouchAnim( int anim );
91 extern qboolean PM_SaberInIdle( int move );
92 extern qboolean PM_SaberInReflect( int move );
93 extern qboolean PM_InSpecialJump( int anim );
94 extern qboolean PM_InKnockDown( playerState_t *ps );
95 extern int PM_PowerLevelForSaberAnim( playerState_t *ps );
96 extern void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir );
97 extern qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir );
98 extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType );
99 extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc );
100 extern void Jedi_PlayDeflectSound( gentity_t *self );
101 extern void Jedi_PlayBlockedPushSound( gentity_t *self );
102 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
103 extern void Jedi_Ambush( gentity_t *self );
104 extern qboolean Jedi_SaberBusy( gentity_t *self );
105
106 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
107 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
108 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd );
109
110 void WP_SaberDrop( gentity_t *self, gentity_t *saber );
111 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
112 void WP_SaberReturn( gentity_t *self, gentity_t *saber );
113 void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missleBlock );
114 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock );
115 void ForceThrow( gentity_t *self, qboolean pull );
116 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
117 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
118
119 extern cvar_t *g_saberAutoBlocking;
120 extern cvar_t *g_saberRealisticCombat;
121 extern int g_crosshairEntNum;
122
123 int g_saberFlashTime = 0;
124 vec3_t g_saberFlashPos = {0,0,0};
125
126 int forcePowerNeeded[NUM_FORCE_POWERS] =
127 {
128 0,//FP_HEAL,//instant
129 10,//FP_LEVITATION,//hold/duration
130 50,//FP_SPEED,//duration
131 15,//FP_PUSH,//hold/duration
132 15,//FP_PULL,//hold/duration
133 20,//FP_TELEPATHY,//instant
134 1,//FP_GRIP,//hold/duration - FIXME: 30?
135 1,//FP_LIGHTNING,//hold/duration
136 20,//FP_SABERTHROW,
137 1,//FP_SABER_DEFENSE,
138 0,//FP_SABER_OFFENSE,
139 //NUM_FORCE_POWERS
140 };
141
142 float forceJumpStrength[NUM_FORCE_POWER_LEVELS] =
143 {
144 JUMP_VELOCITY,//normal jump
145 420,
146 590,
147 840
148 };
149
150 float forceJumpHeight[NUM_FORCE_POWER_LEVELS] =
151 {
152 32,//normal jump (+stepheight+crouchdiff = 66)
153 96,//(+stepheight+crouchdiff = 130)
154 192,//(+stepheight+crouchdiff = 226)
155 384//(+stepheight+crouchdiff = 418)
156 };
157
158 float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] =
159 {
160 66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74)
161 130,//(96+stepheight(18)+crouchdiff(24) = 138)
162 226,//(192+stepheight(18)+crouchdiff(24) = 234)
163 418//(384+stepheight(18)+crouchdiff(24) = 426)
164 };
165
166 float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] =
167 {
168 0,//none
169 384,//256,
170 448,//384,
171 512
172 };
173
174 float forcePushCone[NUM_FORCE_POWER_LEVELS] =
175 {
176 1.0f,//none
177 1.0f,
178 0.8f,
179 0.6f
180 };
181
182 float forcePullCone[NUM_FORCE_POWER_LEVELS] =
183 {
184 1.0f,//none
185 1.0f,
186 1.0f,
187 0.8f
188 };
189
190 float forceSpeedValue[NUM_FORCE_POWER_LEVELS] =
191 {
192 1.0f,//none
193 0.75f,
194 0.5f,
195 0.25f
196 };
197
198 float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] =
199 {
200 0.0f,//none
201 30.0f,
202 45.0f,
203 60.0f
204 };
205
206 float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] =
207 {
208 0.0f,//none
209 20.0f,
210 30.0f,
211 40.0f
212 };
213
214 int forceGripDamage[NUM_FORCE_POWER_LEVELS] =
215 {
216 0,//none
217 0,
218 6,
219 9
220 };
221
222 int mindTrickTime[NUM_FORCE_POWER_LEVELS] =
223 {
224 0,//none
225 5000,
226 10000,
227 15000
228 };
229
230 //NOTE: keep in synch with table below!!!
231 int saberThrowDist[NUM_FORCE_POWER_LEVELS] =
232 {
233 0,//none
234 256,
235 400,
236 400
237 };
238
239 //NOTE: keep in synch with table above!!!
240 int saberThrowDistSquared[NUM_FORCE_POWER_LEVELS] =
241 {
242 0,//none
243 65536,
244 160000,
245 160000
246 };
247
248 int parryDebounce[NUM_FORCE_POWER_LEVELS] =
249 {
250 1000000,//if don't even have defense, can't use defense!
251 300,
252 150,
253 50
254 };
255
256 float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS] =
257 {
258 0.0f,//if don't even have offense, can't use offense!
259 0.75f,
260 1.0f,
261 2.0f
262 };
263 //SABER INITIALIZATION======================================================================
264
G_CreateG2AttachedWeaponModel(gentity_t * ent,const char * psWeaponModel)265 void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel )
266 {
267 if (!psWeaponModel)
268 {
269 assert (psWeaponModel);
270 return;
271 }
272 if ( ent && ent->client && ent->client->NPC_class == CLASS_GALAKMECH )
273 {//hack for galakmech, no weaponmodel
274 ent->weaponModel = -1;
275 return;
276 }
277
278 char weaponModel[MAX_QPATH];
279 Q_strncpyz(weaponModel, psWeaponModel, sizeof(weaponModel));
280 if (char *spot = (char*)Q_stristr(weaponModel, ".md3") ) {
281 *spot = 0;
282 spot = (char*)Q_stristr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
283 if (!spot&&!Q_stristr(weaponModel, "noweap"))
284 {
285 Q_strcat (weaponModel, sizeof(weaponModel), "_w");
286 }
287 Q_strcat (weaponModel, sizeof(weaponModel), ".glm"); //and change to ghoul2
288 }
289
290 if ( ent->playerModel == -1 )
291 {
292 return;
293 }
294 // give us a sabre model
295 ent->weaponModel = gi.G2API_InitGhoul2Model(ent->ghoul2, weaponModel, G_ModelIndex( weaponModel ), NULL_HANDLE, NULL_HANDLE, 0, 0);
296 if ( ent->weaponModel != -1 )
297 {
298 // attach it to the hand
299 gi.G2API_AttachG2Model(&ent->ghoul2[ent->weaponModel], &ent->ghoul2[ent->playerModel],
300 ent->handRBolt, ent->playerModel);
301 // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
302 gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel], "*flash");
303 //gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel], 0 );
304 }
305 }
306
307 //----------------------------------------------------------
G_Throw(gentity_t * targ,vec3_t newDir,float push)308 void G_Throw( gentity_t *targ, vec3_t newDir, float push )
309 //----------------------------------------------------------
310 {
311 vec3_t kvel;
312 float mass;
313
314 if ( targ->physicsBounce > 0 ) //overide the mass
315 {
316 mass = targ->physicsBounce;
317 }
318 else
319 {
320 mass = 200;
321 }
322
323 if ( g_gravity->value > 0 )
324 {
325 VectorScale( newDir, g_knockback->value * (float)push / mass * 0.8, kvel );
326 kvel[2] = newDir[2] * g_knockback->value * (float)push / mass * 1.5;
327 }
328 else
329 {
330 VectorScale( newDir, g_knockback->value * (float)push / mass, kvel );
331 }
332
333 if ( targ->client )
334 {
335 VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
336 }
337 else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
338 {
339 VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
340 VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
341 targ->s.pos.trTime = level.time;
342 }
343
344 // set the timer so that the other client can't cancel
345 // out the movement immediately
346 if ( targ->client && !targ->client->ps.pm_time )
347 {
348 int t;
349
350 t = push * 2;
351
352 if ( t < 50 )
353 {
354 t = 50;
355 }
356 if ( t > 200 )
357 {
358 t = 200;
359 }
360 targ->client->ps.pm_time = t;
361 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
362 }
363 }
364
WP_SetSaberModel(gclient_t * client,class_t npcClass)365 int WP_SetSaberModel( gclient_t *client, class_t npcClass )
366 {
367 if ( client )
368 {
369 switch ( npcClass )
370 {
371 case CLASS_DESANN://Desann
372 client->ps.saberModel = "models/weapons2/saber_desann/saber_w.glm";
373 break;
374 case CLASS_LUKE://Luke
375 client->ps.saberModel = "models/weapons2/saber_luke/saber_w.glm";
376 break;
377 case CLASS_KYLE://Kyle NPC and player
378 client->ps.saberModel = "models/weapons2/saber/saber_w.glm";
379 break;
380 default://reborn and tavion and everyone else
381 client->ps.saberModel = "models/weapons2/saber_reborn/saber_w.glm";
382 break;
383 }
384 return ( G_ModelIndex( client->ps.saberModel ) );
385 }
386 else
387 {
388 switch ( npcClass )
389 {
390 case CLASS_DESANN://Desann
391 return ( G_ModelIndex( "models/weapons2/saber_desann/saber_w.glm" ) );
392 break;
393 case CLASS_LUKE://Luke
394 return ( G_ModelIndex( "models/weapons2/saber_luke/saber_w.glm" ) );
395 break;
396 case CLASS_KYLE://Kyle NPC and player
397 return ( G_ModelIndex( "models/weapons2/saber/saber_w.glm" ) );
398 break;
399 default://reborn and tavion and everyone else
400 return ( G_ModelIndex( "models/weapons2/saber_reborn/saber_w.glm" ) );
401 break;
402 }
403 }
404 }
405
WP_SaberInitBladeData(gentity_t * ent)406 void WP_SaberInitBladeData( gentity_t *ent )
407 {
408 gentity_t *saberent;
409
410 if ( ent->client )
411 {
412 VectorClear( ent->client->renderInfo.muzzlePoint );
413 VectorClear( ent->client->renderInfo.muzzlePointOld );
414 //VectorClear( ent->client->renderInfo.muzzlePointNext );
415 VectorClear( ent->client->renderInfo.muzzleDir );
416 VectorClear( ent->client->renderInfo.muzzleDirOld );
417 //VectorClear( ent->client->renderInfo.muzzleDirNext );
418 ent->client->ps.saberLengthOld = ent->client->ps.saberLength = 0;
419 ent->client->ps.saberLockEnemy = ENTITYNUM_NONE;
420 ent->client->ps.saberLockTime = 0;
421 if ( ent->s.number )
422 {
423 if ( ent->client->NPC_class == CLASS_DESANN )
424 {
425 ent->client->ps.saberAnimLevel = FORCE_LEVEL_4;
426 }
427 else if ( ent->client->NPC_class == CLASS_TAVION )
428 {
429 ent->client->ps.saberAnimLevel = FORCE_LEVEL_5;
430 }
431 else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CIVILIAN || ent->NPC->rank == RANK_LT_JG) )
432 {//grunt and fencer always uses quick attacks
433 ent->client->ps.saberAnimLevel = FORCE_LEVEL_1;
434 }
435 else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CREWMAN || ent->NPC->rank == RANK_ENSIGN) )
436 {//acrobat & force-users always use medium attacks
437 ent->client->ps.saberAnimLevel = FORCE_LEVEL_2;
438 }
439 else if ( ent->client->playerTeam == TEAM_ENEMY && ent->client->NPC_class == CLASS_SHADOWTROOPER )
440 {//shadowtroopers
441 ent->client->ps.saberAnimLevel = Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 );
442 }
443 else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && ent->NPC->rank == RANK_LT )
444 {//boss always starts with strong attacks
445 ent->client->ps.saberAnimLevel = FORCE_LEVEL_3;
446 }
447 else if ( ent->client->NPC_class == CLASS_KYLE )
448 {
449 ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
450 }
451 else
452 {//?
453 ent->client->ps.saberAnimLevel = Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 );
454 }
455 }
456 else
457 {
458 if ( !ent->client->ps.saberAnimLevel )
459 {//initialize, but don't reset
460 ent->client->ps.saberAnimLevel = FORCE_LEVEL_2;
461 }
462 cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel;
463 if ( ent->client->sess.missionStats.weaponUsed[WP_SABER] <= 0 )
464 {//let missionStats know that we actually do have the saber, even if we never use it
465 ent->client->sess.missionStats.weaponUsed[WP_SABER] = 1;
466 }
467 }
468 ent->client->ps.saberAttackChainCount = 0;
469 if ( ent->client->NPC_class == CLASS_DESANN )
470 {//longer saber
471 ent->client->ps.saberLengthMax = 48;
472 }
473 else if ( ent->client->NPC_class == CLASS_REBORN )
474 {//shorter saber
475 ent->client->ps.saberLengthMax = 32;
476 }
477 else
478 {//standard saber length
479 ent->client->ps.saberLengthMax = 40;
480 }
481
482 if ( ent->client->ps.saberEntityNum <= 0 || ent->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
483 {
484 saberent = G_Spawn();
485 ent->client->ps.saberEntityNum = saberent->s.number;
486 saberent->classname = "lightsaber";
487
488 saberent->s.eType = ET_GENERAL;
489 saberent->svFlags = SVF_USE_CURRENT_ORIGIN;
490 saberent->s.weapon = WP_SABER;
491 saberent->owner = ent;
492 saberent->s.otherEntityNum = ent->s.number;
493
494 saberent->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
495 saberent->contents = CONTENTS_LIGHTSABER;//|CONTENTS_SHOTCLIP;
496
497 VectorSet( saberent->mins, -3.0f, -3.0f, -3.0f );
498 VectorSet( saberent->maxs, 3.0f, 3.0f, 3.0f );
499 saberent->mass = 10;//necc?
500
501 saberent->s.eFlags |= EF_NODRAW;
502 saberent->svFlags |= SVF_NOCLIENT;
503 /*
504 Ghoul2 Insert Start
505 */
506 //FIXME: get saberModel from NPCs.cfg for NPCs?
507 saberent->s.modelindex = WP_SetSaberModel( ent->client, ent->client->NPC_class );
508 gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saberModel, saberent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
509 // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0
510 gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" );
511 //gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 );
512
513 /*
514 Ghoul2 Insert End
515 */
516
517 ent->client->ps.saberInFlight = qfalse;
518 ent->client->ps.saberEntityDist = 0;
519 ent->client->ps.saberEntityState = SES_LEAVING;
520
521 ent->client->ps.saberMove = 0;
522
523 //FIXME: need a think function to create alerts when turned on or is on, etc.
524 }
525 }
526 else
527 {
528 ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
529 ent->client->ps.saberInFlight = qfalse;
530 ent->client->ps.saberEntityDist = 0;
531 ent->client->ps.saberEntityState = SES_LEAVING;
532 }
533 }
534
WP_SaberUpdateOldBladeData(gentity_t * ent)535 void WP_SaberUpdateOldBladeData( gentity_t *ent )
536 {
537 if ( ent->client )
538 {
539 VectorCopy( ent->client->renderInfo.muzzlePoint, ent->client->renderInfo.muzzlePointOld );
540 VectorCopy( ent->client->renderInfo.muzzleDir, ent->client->renderInfo.muzzleDirOld );
541 if ( ent->client->ps.saberLengthOld <= 0 && ent->client->ps.saberLength > 0 )
542 {//just turned on
543 //do sound event
544 vec3_t saberOrg;
545 VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, saberOrg );
546 AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS );
547 }
548 ent->client->ps.saberLengthOld = ent->client->ps.saberLength;
549 }
550 }
551
552
553
554 //SABER DAMAGE==============================================================================
555 //SABER DAMAGE==============================================================================
556 //SABER DAMAGE==============================================================================
557 //SABER DAMAGE==============================================================================
558 //SABER DAMAGE==============================================================================
559 //SABER DAMAGE==============================================================================
WPDEBUG_SaberColor(saber_colors_t saberColor)560 int WPDEBUG_SaberColor( saber_colors_t saberColor )
561 {
562 switch( (int)(saberColor) )
563 {
564 case SABER_RED:
565 return 0x000000ff;
566 break;
567 case SABER_ORANGE:
568 return 0x000088ff;
569 break;
570 case SABER_YELLOW:
571 return 0x0000ffff;
572 break;
573 case SABER_GREEN:
574 return 0x0000ff00;
575 break;
576 case SABER_BLUE:
577 return 0x00ff0000;
578 break;
579 case SABER_PURPLE:
580 return 0x00ff00ff;
581 break;
582 default:
583 return 0x00ffffff;//white
584 break;
585 }
586 }
587
WP_GetSaberDeflectionAngle(gentity_t * attacker,gentity_t * defender)588 qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender )
589 {
590 vec3_t temp, att_SaberBase, att_StartPos, saberMidNext, att_HitDir, att_HitPos, def_BladeDir;
591 float att_SaberHitLength, hitDot;
592
593 if ( !attacker || !attacker->client || attacker->client->ps.saberInFlight || attacker->client->ps.saberLength <= 0 )
594 {
595 return qfalse;
596 }
597 if ( !defender || !defender->client || defender->client->ps.saberInFlight || defender->client->ps.saberLength <= 0 )
598 {
599 return qfalse;
600 }
601
602 attacker->client->ps.saberBounceMove = LS_NONE;
603
604 //get the attacker's saber base pos at time of impact
605 VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp );
606 VectorMA( attacker->client->renderInfo.muzzlePointOld, saberHitFraction, temp, att_SaberBase );
607
608 //get the position along the length of the blade where the hit occured
609 att_SaberHitLength = Distance( saberHitLocation, att_SaberBase )/attacker->client->ps.saberLength;
610
611 //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?)
612 VectorMA( attacker->client->renderInfo.muzzlePointOld, att_SaberHitLength, attacker->client->renderInfo.muzzleDirOld, att_StartPos );
613 VectorMA( attacker->client->renderInfo.muzzlePoint, att_SaberHitLength, attacker->client->renderInfo.muzzleDir, saberMidNext );
614 VectorSubtract( saberMidNext, att_StartPos, att_HitDir );
615 VectorMA( att_StartPos, saberHitFraction, att_HitDir, att_HitPos );
616 VectorNormalize( att_HitDir );
617
618 //get the defender's saber dir at time of impact
619 VectorSubtract( defender->client->renderInfo.muzzleDirOld, defender->client->renderInfo.muzzleDir, temp );
620 VectorMA( defender->client->renderInfo.muzzleDirOld, saberHitFraction, temp, def_BladeDir );
621
622 //now compare
623 hitDot = DotProduct( att_HitDir, def_BladeDir );
624 if ( hitDot < 0.25f && hitDot > -0.25f )
625 {//hit pretty much perpendicular, pop straight back
626 attacker->client->ps.saberBounceMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove );
627 return qfalse;
628 }
629 else
630 {//a deflection
631 vec3_t att_Right, att_Up, att_DeflectionDir;
632 float swingRDot, swingUDot;
633
634 //get the direction of the deflection
635 VectorScale( def_BladeDir, hitDot, att_DeflectionDir );
636 //get our bounce straight back direction
637 VectorScale( att_HitDir, -1.0f, temp );
638 //add the bounce back and deflection
639 VectorAdd( att_DeflectionDir, temp, att_DeflectionDir );
640 //normalize the result to determine what direction our saber should bounce back toward
641 VectorNormalize( att_DeflectionDir );
642
643 //need to know the direction of the deflectoin relative to the attacker's facing
644 VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch!
645 AngleVectors( temp, NULL, att_Right, att_Up );
646 swingRDot = DotProduct( att_Right, att_DeflectionDir );
647 swingUDot = DotProduct( att_Up, att_DeflectionDir );
648
649 if ( swingRDot > 0.25f )
650 {//deflect to right
651 if ( swingUDot > 0.25f )
652 {//deflect to top
653 attacker->client->ps.saberBounceMove = LS_D1_TR;
654 }
655 else if ( swingUDot < -0.25f )
656 {//deflect to bottom
657 attacker->client->ps.saberBounceMove = LS_D1_BR;
658 }
659 else
660 {//deflect horizontally
661 attacker->client->ps.saberBounceMove = LS_D1__R;
662 }
663 }
664 else if ( swingRDot < -0.25f )
665 {//deflect to left
666 if ( swingUDot > 0.25f )
667 {//deflect to top
668 attacker->client->ps.saberBounceMove = LS_D1_TL;
669 }
670 else if ( swingUDot < -0.25f )
671 {//deflect to bottom
672 attacker->client->ps.saberBounceMove = LS_D1_BL;
673 }
674 else
675 {//deflect horizontally
676 attacker->client->ps.saberBounceMove = LS_D1__L;
677 }
678 }
679 else
680 {//deflect in middle
681 if ( swingUDot > 0.25f )
682 {//deflect to top
683 attacker->client->ps.saberBounceMove = LS_D1_T_;
684 }
685 else if ( swingUDot < -0.25f )
686 {//deflect to bottom
687 attacker->client->ps.saberBounceMove = LS_D1_B_;
688 }
689 else
690 {//deflect horizontally? Well, no such thing as straight back in my face, so use top
691 if ( swingRDot > 0 )
692 {
693 attacker->client->ps.saberBounceMove = LS_D1_TR;
694 }
695 else if ( swingRDot < 0 )
696 {
697 attacker->client->ps.saberBounceMove = LS_D1_TL;
698 }
699 else
700 {
701 attacker->client->ps.saberBounceMove = LS_D1_T_;
702 }
703 }
704 }
705 #ifndef FINAL_BUILD
706 if ( d_saberCombat->integer )
707 {
708 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 );
709 }
710 #endif
711 return qtrue;
712 }
713 }
714
715
WP_SaberClearDamageForEntNum(int entityNum)716 void WP_SaberClearDamageForEntNum( int entityNum )
717 {
718 #ifndef FINAL_BUILD
719 if ( d_saberCombat->integer )
720 {
721 if ( entityNum )
722 {
723 Com_Printf( "clearing damage for entnum %d\n", entityNum );
724 }
725 }
726 #endif// FINAL_BUILD
727 if ( g_saberRealisticCombat->integer > 1 )
728 {
729 return;
730 }
731 for ( int i = 0; i < numVictims; i++ )
732 {
733 if ( victimEntityNum[i] == entityNum )
734 {
735 totalDmg[i] = 0;//no damage
736 hitLoc[i] = HL_NONE;
737 hitDismemberLoc[i] = HL_NONE;
738 hitDismember[i] = qfalse;
739 victimEntityNum[i] = ENTITYNUM_NONE;//like we never hit him
740 }
741 }
742 }
743
744 extern float damageModifier[];
745 extern float hitLocHealthPercentage[];
WP_SaberApplyDamage(gentity_t * ent,float baseDamage,int baseDFlags,qboolean brokenParry)746 qboolean WP_SaberApplyDamage( gentity_t *ent, float baseDamage, int baseDFlags, qboolean brokenParry )
747 {
748 qboolean didDamage = qfalse;
749 gentity_t *victim;
750 int dFlags = baseDFlags;
751 float maxDmg;
752
753
754 if ( !numVictims )
755 {
756 return qfalse;
757 }
758 for ( int i = 0; i < numVictims; i++ )
759 {
760 dFlags = baseDFlags|DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_HIT_LOC;
761 if ( victimEntityNum[i] != ENTITYNUM_NONE && &g_entities[victimEntityNum[i]] != NULL )
762 { // Don't bother with this damage if the fraction is higher than the saber's fraction
763 if ( dmgFraction[i] < saberHitFraction || brokenParry )
764 {
765 victim = &g_entities[victimEntityNum[i]];
766 if ( !victim )
767 {
768 continue;
769 }
770
771 if ( victim->e_DieFunc == dieF_maglock_die )
772 {//*sigh*, special check for maglocks
773 vec3_t testFrom;
774 if ( ent->client->ps.saberInFlight )
775 {
776 VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, testFrom );
777 }
778 else
779 {
780 VectorCopy( ent->currentOrigin, testFrom );
781 }
782 testFrom[2] = victim->currentOrigin[2];
783 trace_t testTrace;
784 gi.trace( &testTrace, testFrom, vec3_origin, vec3_origin, victim->currentOrigin, ent->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
785 if ( testTrace.entityNum != victim->s.number )
786 {//can only damage maglocks if have a clear trace to the thing's origin
787 continue;
788 }
789 }
790 if ( totalDmg[i] > 0 )
791 {//actually want to do *some* damage here
792 if ( victim->s.weapon == WP_SABER && victim->client && !g_saberRealisticCombat->integer )
793 {//dmg vs other saber fighters is modded by hitloc and capped
794 totalDmg[i] *= damageModifier[hitLoc[i]];
795 if ( hitLoc[i] == HL_NONE )
796 {
797 maxDmg = 33*baseDamage;
798 }
799 else
800 {
801 maxDmg = 50*hitLocHealthPercentage[hitLoc[i]]*baseDamage;//*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f;
802 }
803 if ( maxDmg < totalDmg[i] )
804 {
805 totalDmg[i] = maxDmg;
806 }
807 //dFlags |= DAMAGE_NO_HIT_LOC;
808 }
809 //clamp the dmg
810 if ( victim->s.weapon != WP_SABER )
811 {//clamp the dmg between 25 and maxhealth
812 /*
813 if ( totalDmg[i] > victim->max_health )
814 {
815 totalDmg[i] = victim->max_health;
816 }
817 else */if ( totalDmg[i] < 25 )
818 {
819 totalDmg[i] = 25;
820 }
821 if ( totalDmg[i] > 100 )//+(50*g_spskill->integer) )
822 {//clamp using same adjustment as in NPC_Begin
823 totalDmg[i] = 100;//+(50*g_spskill->integer);
824 }
825 }
826 else
827 {//clamp the dmg between 5 and 100
828 if ( !victim->s.number && totalDmg[i] > 50 )
829 {//never do more than half full health damage to player
830 //prevents one-hit kills
831 totalDmg[i] = 50;
832 }
833 else if ( totalDmg[i] > 100 )
834 {
835 totalDmg[i] = 100;
836 }
837 else
838 {
839 if ( totalDmg[i] < 5 )
840 {
841 totalDmg[i] = 5;
842 }
843 }
844 }
845
846 if ( totalDmg[i] > 0 )
847 {
848 didDamage = qtrue;
849 if( victim->client )
850 {
851 if ( victim->client->ps.pm_time > 0 && victim->client->ps.pm_flags & PMF_TIME_KNOCKBACK && victim->client->ps.velocity[2] > 0 )
852 {//already being knocked around
853 dFlags |= DAMAGE_NO_KNOCKBACK;
854 }
855 if ( g_dismemberment->integer >= 11381138 || g_saberRealisticCombat->integer )
856 {
857 dFlags |= DAMAGE_DISMEMBER;
858 if ( hitDismember[i] )
859 {
860 victim->client->dismembered = qfalse;
861 }
862 }
863 else if ( hitDismember[i] )
864 {
865 dFlags |= DAMAGE_DISMEMBER;
866 }
867 if ( baseDamage <= 1.0f )
868 {//very mild damage
869 if ( victim->s.number == 0 || victim->client->ps.weapon == WP_SABER || victim->client->NPC_class == CLASS_GALAKMECH )
870 {//if it's the player or a saber-user, don't kill them with this blow
871 dFlags |= DAMAGE_NO_KILL;
872 }
873 }
874 }
875 else
876 {
877 if ( victim->takedamage )
878 {//some other breakable thing
879 //create a flash here
880 g_saberFlashTime = level.time-50;
881 VectorCopy( dmgSpot[i], g_saberFlashPos );
882 }
883 }
884 //victim->hitLoc = hitLoc[i];
885
886 dFlags |= DAMAGE_NO_KNOCKBACK;//okay, let's try no knockback whatsoever...
887 dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
888 if ( g_saberRealisticCombat->integer )
889 {
890 dFlags |= DAMAGE_NO_KNOCKBACK;
891 dFlags &= ~DAMAGE_DEATH_KNOCKBACK;
892 dFlags &= ~DAMAGE_NO_KILL;
893 }
894 if ( ent->client && !ent->s.number )
895 {
896 switch( hitLoc[i] )
897 {
898 case HL_FOOT_RT:
899 case HL_FOOT_LT:
900 case HL_LEG_RT:
901 case HL_LEG_LT:
902 ent->client->sess.missionStats.legAttacksCnt++;
903 break;
904 case HL_WAIST:
905 case HL_BACK_RT:
906 case HL_BACK_LT:
907 case HL_BACK:
908 case HL_CHEST_RT:
909 case HL_CHEST_LT:
910 case HL_CHEST:
911 ent->client->sess.missionStats.torsoAttacksCnt++;
912 break;
913 case HL_ARM_RT:
914 case HL_ARM_LT:
915 case HL_HAND_RT:
916 case HL_HAND_LT:
917 ent->client->sess.missionStats.armAttacksCnt++;
918 break;
919 default:
920 ent->client->sess.missionStats.otherAttacksCnt++;
921 break;
922 }
923 }
924 G_Damage( victim, ent, ent, dmgDir[i], dmgSpot[i], ceil(totalDmg[i]), dFlags, MOD_SABER, hitDismemberLoc[i] );
925 #ifndef FINAL_BUILD
926 if ( d_saberCombat->integer )
927 {
928 gi.Printf( S_COLOR_RED"damage: %4.2f, hitLoc %d\n", totalDmg[i], hitLoc[i] );
929 }
930 #endif
931 //do the effect
932 //G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], dmgDir[i] );
933 if ( ent->s.number == 0 )
934 {
935 AddSoundEvent( victim->owner, dmgSpot[i], 256, AEL_DISCOVERED );
936 AddSightEvent( victim->owner, dmgSpot[i], 512, AEL_DISCOVERED, 50 );
937 }
938 if ( ent->client )
939 {
940 if ( ent->enemy && ent->enemy == victim )
941 {//just so Jedi knows that he hit his enemy
942 ent->client->ps.saberEventFlags |= SEF_HITENEMY;
943 }
944 else
945 {
946 ent->client->ps.saberEventFlags |= SEF_HITOBJECT;
947 }
948 }
949 }
950 }
951 }
952 }
953 }
954 return didDamage;
955 }
956
WP_SaberDamageAdd(float trDmg,int trVictimEntityNum,vec3_t trDmgDir,vec3_t trDmgSpot,float dmg,float fraction,int trHitLoc,qboolean trDismember,int trDismemberLoc)957 void WP_SaberDamageAdd( float trDmg, int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot, float dmg, float fraction, int trHitLoc, qboolean trDismember, int trDismemberLoc )
958 {
959 int curVictim = 0;
960 int i;
961
962 if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD )
963 {
964 return;
965 }
966 if ( trDmg * dmg < 10.0f )
967 {//too piddly an amount of damage to really count?
968 //FIXME: but already did the effect, didn't we... sigh...
969 //return;
970 }
971 if ( trDmg )
972 {//did some damage to something
973 for ( i = 0; i < numVictims; i++ )
974 {
975 if ( victimEntityNum[i] == trVictimEntityNum )
976 {//already hit this guy before
977 curVictim = i;
978 break;
979 }
980 }
981 if ( i == numVictims )
982 {//haven't hit his guy before
983 if ( numVictims + 1 >= MAX_SABER_VICTIMS )
984 {//can't add another victim at this time
985 return;
986 }
987 //add a new victim to the list
988 curVictim = numVictims;
989 victimEntityNum[numVictims++] = trVictimEntityNum;
990 }
991
992 float addDmg = trDmg*dmg;
993 if ( trHitLoc!=HL_NONE && (hitLoc[curVictim]==HL_NONE||hitLocHealthPercentage[trHitLoc]>hitLocHealthPercentage[hitLoc[curVictim]]) )
994 {//this hitLoc is more critical than the previous one this frame
995 hitLoc[curVictim] = trHitLoc;
996 }
997
998 totalDmg[curVictim] += addDmg;
999 if ( !VectorLengthSquared( dmgDir[curVictim] ) )
1000 {
1001 VectorCopy( trDmgDir, dmgDir[curVictim] );
1002 }
1003 if ( !VectorLengthSquared( dmgSpot[curVictim] ) )
1004 {
1005 VectorCopy( trDmgSpot, dmgSpot[curVictim] );
1006 }
1007
1008 // Make sure we keep track of the fraction. Why?
1009 // Well, if the saber hits something that stops it, the damage isn't done past that point.
1010 dmgFraction[curVictim] = fraction;
1011 if ( (trDismemberLoc != HL_NONE && hitDismemberLoc[curVictim] == HL_NONE)
1012 || (!hitDismember[curVictim] && trDismember) )
1013 {//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
1014 hitDismemberLoc[curVictim] = trDismemberLoc;
1015 }
1016 if ( trDismember )
1017 {//we scored a dismemberment hit...
1018 hitDismember[curVictim] = trDismember;
1019 }
1020 }
1021 }
1022
1023 /*
1024 WP_SabersIntersect
1025
1026 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
1027
1028 FIXME: subdivide the arc into a consistant increment
1029 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)?
1030 */
1031 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,gentity_t * ent2,qboolean checkDir)1032 qboolean WP_SabersIntersect( gentity_t *ent1, gentity_t *ent2, qboolean checkDir )
1033 {
1034 vec3_t saberBase1, saberTip1, saberBaseNext1, saberTipNext1;
1035 vec3_t saberBase2, saberTip2, saberBaseNext2, saberTipNext2;
1036 vec3_t dir;
1037
1038 /*
1039 #ifndef FINAL_BUILD
1040 if ( d_saberCombat->integer )
1041 {
1042 gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1043 }
1044 #endif
1045 */
1046
1047 if ( !ent1 || !ent2 )
1048 {
1049 return qfalse;
1050 }
1051 if ( !ent1->client || !ent2->client )
1052 {
1053 return qfalse;
1054 }
1055 if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
1056 {
1057 return qfalse;
1058 }
1059
1060 //if ( ent1->client->ps.saberInFlight )
1061 {
1062 VectorCopy( ent1->client->renderInfo.muzzlePointOld, saberBase1 );
1063 VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
1064
1065 VectorSubtract( ent1->client->renderInfo.muzzlePoint, ent1->client->renderInfo.muzzlePointOld, dir );
1066 VectorNormalize( dir );
1067 VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 );
1068
1069 VectorMA( saberBase1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirOld, saberTip1 );
1070 VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );
1071
1072 VectorSubtract( saberTipNext1, saberTip1, dir );
1073 VectorNormalize( dir );
1074 VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 );
1075 }
1076 /*
1077 else
1078 {
1079 VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBase1 );
1080 VectorCopy( ent1->client->renderInfo.muzzlePointNext, saberBaseNext1 );
1081 VectorMA( saberBase1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTip1 );
1082 VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirNext, saberTipNext1 );
1083 }
1084 */
1085
1086 //if ( ent2->client->ps.saberInFlight )
1087 {
1088 VectorCopy( ent2->client->renderInfo.muzzlePointOld, saberBase2 );
1089 VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
1090
1091 VectorSubtract( ent2->client->renderInfo.muzzlePoint, ent2->client->renderInfo.muzzlePointOld, dir );
1092 VectorNormalize( dir );
1093 VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 );
1094
1095 VectorMA( saberBase2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirOld, saberTip2 );
1096 VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );
1097
1098 VectorSubtract( saberTipNext2, saberTip2, dir );
1099 VectorNormalize( dir );
1100 VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 );
1101 }
1102 /*
1103 else
1104 {
1105 VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBase2 );
1106 VectorCopy( ent2->client->renderInfo.muzzlePointNext, saberBaseNext2 );
1107 VectorMA( saberBase2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTip2 );
1108 VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirNext, saberTipNext2 );
1109 }
1110 */
1111 if ( checkDir )
1112 {//check the direction of the two swings to make sure the sabers are swinging towards each other
1113 vec3_t saberDir1, saberDir2;
1114
1115 VectorSubtract( saberTipNext1, saberTip1, saberDir1 );
1116 VectorSubtract( saberTipNext2, saberTip2, saberDir2 );
1117 VectorNormalize( saberDir1 );
1118 VectorNormalize( saberDir2 );
1119 if ( DotProduct( saberDir1, saberDir2 ) > 0.6f )
1120 {//sabers moving in same dir, probably didn't actually hit
1121 return qfalse;
1122 }
1123 //now check orientation of sabers, make sure they're not parallel or close to it
1124 float dot = DotProduct( ent1->client->renderInfo.muzzleDir, ent2->client->renderInfo.muzzleDir );
1125 if ( dot > 0.9f || dot < -0.9f )
1126 {//too parallel to really block effectively?
1127 return qfalse;
1128 }
1129 }
1130
1131 if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1132 {
1133 return qtrue;
1134 }
1135 if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) )
1136 {
1137 return qtrue;
1138 }
1139 if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) )
1140 {
1141 return qtrue;
1142 }
1143 if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) )
1144 {
1145 return qtrue;
1146 }
1147 return qfalse;
1148 }
1149
WP_SabersDistance(gentity_t * ent1,gentity_t * ent2)1150 float WP_SabersDistance( gentity_t *ent1, gentity_t *ent2 )
1151 {
1152 vec3_t saberBaseNext1, saberTipNext1, saberPoint1;
1153 vec3_t saberBaseNext2, saberTipNext2, saberPoint2;
1154
1155 /*
1156 #ifndef FINAL_BUILD
1157 if ( d_saberCombat->integer )
1158 {
1159 gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" );
1160 }
1161 #endif
1162 */
1163
1164 if ( !ent1 || !ent2 )
1165 {
1166 return qfalse;
1167 }
1168 if ( !ent1->client || !ent2->client )
1169 {
1170 return qfalse;
1171 }
1172 if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
1173 {
1174 return qfalse;
1175 }
1176
1177 //if ( ent1->client->ps.saberInFlight )
1178 {
1179 VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
1180 VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );
1181 }
1182 /*
1183 else
1184 {
1185 VectorCopy( ent1->client->renderInfo.muzzlePointNext, saberBaseNext1 );
1186 VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDirNext, saberTipNext1 );
1187 }
1188 */
1189
1190 //if ( ent2->client->ps.saberInFlight )
1191 {
1192 VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
1193 VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );
1194 }
1195 /*
1196 else
1197 {
1198 VectorCopy( ent2->client->renderInfo.muzzlePointNext, saberBaseNext2 );
1199 VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDirNext, saberTipNext2 );
1200 }
1201 */
1202
1203 float sabersDist = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
1204
1205 //okay, this is a super hack, but makes saber collisions look better from the player point of view
1206 /*
1207 if ( sabersDist < 16.0f )
1208 {
1209 vec3_t saberDistDir, saberMidPoint, camLookDir;
1210
1211 VectorSubtract( saberPoint2, saberPoint1, saberDistDir );
1212 VectorMA( saberPoint1, 0.5f, saberDistDir, saberMidPoint );
1213 VectorSubtract( saberMidPoint, cg.refdef.vieworg, camLookDir );
1214 VectorNormalize( saberDistDir );
1215 VectorNormalize( camLookDir );
1216 float dot = fabs(DotProduct( camLookDir, saberDistDir ));
1217 sabersDist -= 8.0f*dot;
1218 if ( sabersDist < 0.0f )
1219 {
1220 sabersDist = 0.0f;
1221 }
1222 }
1223 */
1224
1225 #ifndef FINAL_BUILD
1226 if ( d_saberCombat->integer > 2 )
1227 {
1228 G_DebugLine( saberPoint1, saberPoint2, FRAMETIME, 0x00ffffff, qtrue );
1229 }
1230 #endif
1231 return sabersDist;
1232 }
1233
WP_SabersIntersection(gentity_t * ent1,gentity_t * ent2,vec3_t intersect)1234 qboolean WP_SabersIntersection( gentity_t *ent1, gentity_t *ent2, vec3_t intersect )
1235 {
1236 vec3_t saberBaseNext1, saberTipNext1, saberPoint1;
1237 vec3_t saberBaseNext2, saberTipNext2, saberPoint2;
1238
1239 if ( !ent1 || !ent2 )
1240 {
1241 return qfalse;
1242 }
1243 if ( !ent1->client || !ent2->client )
1244 {
1245 return qfalse;
1246 }
1247 if ( ent1->client->ps.saberLength <= 0 || ent2->client->ps.saberLength <= 0 )
1248 {
1249 return qfalse;
1250 }
1251
1252 VectorCopy( ent1->client->renderInfo.muzzlePoint, saberBaseNext1 );
1253 VectorMA( saberBaseNext1, ent1->client->ps.saberLength, ent1->client->renderInfo.muzzleDir, saberTipNext1 );
1254
1255 VectorCopy( ent2->client->renderInfo.muzzlePoint, saberBaseNext2 );
1256 VectorMA( saberBaseNext2, ent2->client->ps.saberLength, ent2->client->renderInfo.muzzleDir, saberTipNext2 );
1257
1258 ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 );
1259 VectorAdd( saberPoint1, saberPoint2, intersect );
1260 VectorScale( intersect, 0.5, intersect );
1261 return qtrue;
1262 }
1263
1264 char *hit_blood_sparks = "blood_sparks";
1265 char *hit_sparks = "saber_cut";
1266
1267 extern qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod );
WP_SaberDamageEffects(trace_t * tr,const vec3_t start,float length,float dmg,vec3_t dmgDir,vec3_t bladeDir,int enemyTeam)1268 qboolean WP_SaberDamageEffects( trace_t *tr, const vec3_t start, float length, float dmg, vec3_t dmgDir, vec3_t bladeDir, int enemyTeam )
1269 {
1270
1271 int hitEntNum[MAX_G2_COLLISIONS] = {ENTITYNUM_NONE};
1272 float hitEntDmgAdd[MAX_G2_COLLISIONS] = {0};
1273 float hitEntDmgSub[MAX_G2_COLLISIONS] = {0};
1274 vec3_t hitEntPoint[MAX_G2_COLLISIONS];
1275 vec3_t hitEntDir[MAX_G2_COLLISIONS];
1276 float hitEntStartFrac[MAX_G2_COLLISIONS] = {0};
1277 int trHitLoc = HL_NONE;
1278 int trDismemberLoc = HL_NONE;
1279 qboolean trDismember = qfalse;
1280 int i,z;
1281 int numHitEnts = 0;
1282 float distFromStart,doDmg;
1283 char *hitEffect;
1284 gentity_t *hitEnt;
1285
1286 for (z=0; z < MAX_G2_COLLISIONS; z++)
1287 {
1288 if ( tr->G2CollisionMap[z].mEntityNum == -1 )
1289 {//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either
1290 continue;//break;//
1291 }
1292
1293 CCollisionRecord &coll = tr->G2CollisionMap[z];
1294 //distFromStart = Distance( start, coll.mCollisionPosition );
1295 distFromStart = coll.mDistance;
1296
1297 /*
1298 //FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*...
1299 if ( length && saberHitFraction < 1.0f && (distFromStart/length) < 1.0f && (distFromStart/length) > saberHitFraction )
1300 {//a saber was hit before this point, don't count it
1301 #ifndef FINAL_BUILD
1302 if ( d_saberCombat->integer )
1303 {
1304 gi.Printf( S_COLOR_MAGENTA"rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f\n", (distFromStart/length), saberHitFraction );
1305 }
1306 #endif
1307 continue;
1308 }
1309 */
1310
1311 for ( i = 0; i < numHitEnts; i++ )
1312 {
1313 if ( hitEntNum[i] != ENTITYNUM_NONE )
1314 {//we hit this ent before
1315 //we'll want to add this dist
1316 hitEntDmgAdd[i] = distFromStart;
1317 break;
1318 }
1319 }
1320 if ( i == numHitEnts )
1321 {//first time we hit this ent
1322 if ( numHitEnts == MAX_G2_COLLISIONS )
1323 {//hit too many damn ents!
1324 continue;
1325 }
1326 hitEntNum[numHitEnts] = coll.mEntityNum;
1327 if ( !coll.mFlags )
1328 {//hmm, we came out first, so we must have started inside
1329 //we'll want to subtract this dist
1330 hitEntDmgAdd[numHitEnts] = distFromStart;
1331 }
1332 else
1333 {//we're entering the model
1334 //we'll want to subtract this dist
1335 hitEntDmgSub[numHitEnts] = distFromStart;
1336 }
1337 //keep track of how far in the damage was done
1338 hitEntStartFrac[numHitEnts] = hitEntDmgSub[numHitEnts]/length;
1339 //remember the entrance point
1340 VectorCopy( coll.mCollisionPosition, hitEntPoint[numHitEnts] );
1341 //remember the entrance dir
1342 VectorCopy( coll.mCollisionNormal, hitEntDir[numHitEnts] );
1343 VectorNormalize( hitEntDir[numHitEnts] );
1344
1345 //do the effect
1346
1347 //FIXME: check material rather than team?
1348 hitEnt = &g_entities[hitEntNum[numHitEnts]];
1349 hitEffect = hit_blood_sparks;
1350 if ( hitEnt != NULL )
1351 {
1352 if ( hitEnt->client )
1353 {
1354 class_t npc_class = hitEnt->client->NPC_class;
1355 if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE ||
1356 npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 ||
1357 npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
1358 npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
1359 {
1360 hitEffect = hit_sparks;
1361 }
1362 }
1363 else
1364 {
1365 hitEffect = hit_sparks;
1366 }
1367 }
1368
1369 //FIXME: play less if damage is less?
1370 G_PlayEffect( hitEffect, coll.mCollisionPosition, coll.mCollisionNormal );
1371
1372 //Get the hit location based on surface name
1373 if ( (hitLoc[numHitEnts] == HL_NONE && trHitLoc == HL_NONE)
1374 || (hitDismemberLoc[numHitEnts] == HL_NONE && trDismemberLoc == HL_NONE)
1375 || (!hitDismember[numHitEnts] && !trDismember) )
1376 {//no hit loc set for this ent this damage cycle yet
1377 //FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName?
1378 trDismember = G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, dmgDir, bladeDir, MOD_SABER );
1379 trDismemberLoc = trHitLoc;
1380 }
1381 numHitEnts++;
1382 }
1383 }
1384
1385 //now go through all the ents we hit and do the damage
1386 for ( i = 0; i < numHitEnts; i++ )
1387 {
1388 doDmg = dmg;
1389 if ( hitEntNum[i] != ENTITYNUM_NONE )
1390 {
1391 if ( doDmg < 10 )
1392 {//base damage is less than 10
1393 if ( hitEntNum[i] != 0 )
1394 {//not the player
1395 hitEnt = &g_entities[hitEntNum[i]];
1396 if ( !hitEnt->client || (hitEnt->client->ps.weapon!=WP_SABER&&hitEnt->client->NPC_class!=CLASS_GALAKMECH&&hitEnt->client->playerTeam==enemyTeam) )
1397 {//did *not* hit a jedi and did *not* hit the player
1398 //make sure the base damage is high against non-jedi, feels better
1399 doDmg = 10;
1400 }
1401 }
1402 }
1403 if ( !hitEntDmgAdd[i] && !hitEntDmgSub[i] )
1404 {//spent entire time in model
1405 //NOTE: will we even get a collision then?
1406 doDmg *= length;
1407 }
1408 else if ( hitEntDmgAdd[i] && hitEntDmgSub[i] )
1409 {//we did enter and exit
1410 doDmg *= hitEntDmgAdd[i] - hitEntDmgSub[i];
1411 }
1412 else if ( !hitEntDmgAdd[i] )
1413 {//we didn't exit, just entered
1414 doDmg *= length - hitEntDmgSub[i];
1415 }
1416 else if ( !hitEntDmgSub[i] )
1417 {//we didn't enter, only exited
1418 doDmg *= hitEntDmgAdd[i];
1419 }
1420 if ( doDmg > 0 )
1421 {
1422 WP_SaberDamageAdd( 1.0, hitEntNum[i], hitEntDir[i], hitEntPoint[i], ceil(doDmg), hitEntStartFrac[i], trHitLoc, trDismember, trDismemberLoc );
1423 }
1424 }
1425 }
1426 return (qboolean)(numHitEnts > 0);
1427 }
1428
WP_SaberKnockaway(gentity_t * attacker,trace_t * tr)1429 void WP_SaberKnockaway( gentity_t *attacker, trace_t *tr )
1430 {
1431 WP_SaberDrop( attacker, &g_entities[attacker->client->ps.saberEntityNum] );
1432 G_Sound( &g_entities[attacker->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
1433 G_PlayEffect( "saber_block", tr->endpos );
1434 saberHitFraction = tr->fraction;
1435 #ifndef FINAL_BUILD
1436 if ( d_saberCombat->integer )
1437 {
1438 gi.Printf( S_COLOR_MAGENTA"WP_SaberKnockaway: saberHitFraction %4.2f\n", saberHitFraction );
1439 }
1440 #endif
1441 VectorCopy( tr->endpos, saberHitLocation );
1442 saberHitEntity = tr->entityNum;
1443 g_saberFlashTime = level.time-50;
1444 VectorCopy( saberHitLocation, g_saberFlashPos );
1445
1446 //FIXME: make hitEnt play an attack anim or some other special anim when this happens
1447 //gentity_t *hitEnt = &g_entities[tr->entityNum];
1448 //NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1449 }
1450
1451 #define SABER_COLLISION_DIST 6//was 2//was 4//was 8//was 16
1452 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,qboolean extrapolate=qtrue)1453 qboolean WP_SaberDamageForTrace( int ignore, vec3_t start, vec3_t end, float dmg,
1454 vec3_t bladeDir, qboolean noGhoul, int attackStrength,
1455 qboolean extrapolate = qtrue )
1456 {
1457 trace_t tr;
1458 vec3_t dir;
1459 int mask = (MASK_SHOT|CONTENTS_LIGHTSABER);
1460 gentity_t *attacker = &g_entities[ignore];
1461
1462 vec3_t end2;
1463 VectorCopy( end, end2 );
1464 if ( extrapolate )
1465 {
1466 //NOTE: since we can no longer use the predicted point, extrapolate the trace some.
1467 // this may allow saber hits that aren't actually hits, but it doesn't look too bad
1468 vec3_t diff;
1469 VectorSubtract( end, start, diff );
1470 VectorNormalize( diff );
1471 VectorMA( end2, SABER_EXTRAPOLATE_DIST, diff, end2 );
1472 }
1473
1474 if ( !noGhoul )
1475 {
1476 if ( !attacker->s.number || (attacker->client&&(attacker->client->playerTeam==TEAM_PLAYER||attacker->client->NPC_class==CLASS_SHADOWTROOPER||attacker->client->NPC_class==CLASS_TAVION||attacker->client->NPC_class==CLASS_DESANN) ) )//&&attackStrength==FORCE_LEVEL_3)
1477 {//player,. player allies, shadowtroopers, tavion and desann use larger traces
1478 vec3_t traceMins = {-2,-2,-2}, traceMaxs = {2,2,2};
1479 gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
1480 }
1481 /*
1482 else if ( !attacker->s.number )
1483 {
1484 vec3_t traceMins = {-1,-1,-1}, traceMaxs = {1,1,1};
1485 gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
1486 }
1487 */
1488 else
1489 {//reborn use smaller traces
1490 gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX
1491 }
1492 }
1493 else
1494 {
1495 gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_NOCOLLIDE, 10 );
1496 }
1497
1498
1499 #ifndef FINAL_BUILD
1500 if ( d_saberCombat->integer > 1 )
1501 {
1502 if ( attacker != NULL && attacker->client != NULL )
1503 {
1504 G_DebugLine(start, end2, FRAMETIME, WPDEBUG_SaberColor( attacker->client->ps.saberColor ), qtrue);
1505 }
1506 }
1507 #endif
1508
1509 if ( tr.entityNum == ENTITYNUM_NONE )
1510 {
1511 return qfalse;
1512 }
1513
1514 if ( tr.entityNum == ENTITYNUM_WORLD )
1515 {
1516 return qtrue;
1517 }
1518
1519 if ( &g_entities[tr.entityNum] )
1520 {
1521 gentity_t *hitEnt = &g_entities[tr.entityNum];
1522 gentity_t *owner = g_entities[tr.entityNum].owner;
1523 if ( hitEnt->contents & CONTENTS_LIGHTSABER )
1524 {
1525 if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
1526 {//thrown saber hit something
1527 if ( owner
1528 && owner->s.number
1529 && owner->client
1530 && owner->NPC
1531 && owner->health > 0
1532 && ( owner->client->NPC_class == CLASS_TAVION
1533 /*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 ))
1534 || (Q_irand( -5, owner->NPC->rank ) > RANK_CIVILIAN && !Q_irand( 0, g_spskill->integer*3 ))*/ )
1535 )
1536 {//Tavion can toss a blocked thrown saber aside
1537 WP_SaberKnockaway( attacker, &tr );
1538 Jedi_PlayDeflectSound( owner );
1539 return qfalse;
1540 }
1541 }
1542 //FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow?
1543 qboolean sabersIntersect = WP_SabersIntersect( attacker, owner, qfalse );//qtrue );
1544 float sabersDist;
1545 if ( attacker && attacker->client && attacker->client->ps.saberInFlight
1546 && owner && owner->s.number == 0 && (g_saberAutoBlocking->integer||attacker->client->ps.saberBlockingTime>level.time) )//NPC flying saber hit player's saber bounding box
1547 {//players have g_saberAutoBlocking, do the more generous check against flying sabers
1548 //FIXME: instead of hitting the player's saber bounding box
1549 //and picking an anim afterwards, have him use AI similar
1550 //to the AI the jedi use for picking a saber melee block...?
1551 sabersDist = 0;
1552 }
1553 else
1554 {//sabers must actually collide with the attacking saber
1555 sabersDist = WP_SabersDistance( attacker, owner );
1556 if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
1557 {
1558 sabersDist /= 2.0f;
1559 if ( sabersDist <= 16.0f )
1560 {
1561 sabersIntersect = qtrue;
1562 }
1563 }
1564 #ifndef FINAL_BUILD
1565 if ( d_saberCombat->integer > 1 )
1566 {
1567 gi.Printf( "sabersDist: %4.2f\n", sabersDist );
1568 }
1569 #endif//FINAL_BUILD
1570 }
1571 if ( sabersCrossed == -1 || sabersCrossed > sabersDist )
1572 {
1573 sabersCrossed = sabersDist;
1574 }
1575 float collisionDist;
1576 if ( g_saberRealisticCombat->integer )
1577 {
1578 collisionDist = SABER_COLLISION_DIST;
1579 }
1580 else
1581 {
1582 collisionDist = SABER_COLLISION_DIST+6+g_spskill->integer*4;
1583 }
1584 if ( owner && owner->client && (attacker != NULL)
1585 && (sabersDist > collisionDist )//|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f ))
1586 && !sabersIntersect )//was qtrue, but missed too much?
1587 {//swing came from behind and/or was not stopped by a lightsaber
1588 //re-try the trace without checking for lightsabers
1589 gi.trace ( &tr, start, NULL, NULL, end2, ignore, mask&~CONTENTS_LIGHTSABER, G2_NOCOLLIDE, 10 );
1590 if ( tr.entityNum == ENTITYNUM_WORLD )
1591 {
1592 return qtrue;
1593 }
1594 if ( tr.entityNum == ENTITYNUM_NONE || &g_entities[tr.entityNum] == NULL )
1595 {//didn't hit the owner
1596 /*
1597 if ( attacker
1598 && attacker->client
1599 && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
1600 && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
1601 {
1602 if ( owner->NPC
1603 && !owner->client->ps.saberInFlight
1604 && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
1605 && !Jedi_SaberBusy( owner ) )
1606 {//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
1607 if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
1608 {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
1609 //FIXME: also take into account the owner's FP_DEFENSE?
1610 if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
1611 {//lower-rank Jedi aren't as good blockers
1612 vec3_t attDir;
1613 VectorSubtract( end2, start, attDir );
1614 VectorNormalize( attDir );
1615 Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
1616 }
1617 }
1618 }
1619 }
1620 */
1621 return qfalse; // Exit, but we didn't hit the wall.
1622 }
1623 #ifndef FINAL_BUILD
1624 if ( d_saberCombat->integer > 1 )
1625 {
1626 if ( !attacker->s.number )
1627 {
1628 gi.Printf( S_COLOR_MAGENTA"%d saber hit owner through saber %4.2f, dist = %4.2f\n", level.time, saberHitFraction, sabersDist );
1629 }
1630 }
1631 #endif//FINAL_BUILD
1632 hitEnt = &g_entities[tr.entityNum];
1633 owner = g_entities[tr.entityNum].owner;
1634 }
1635 else
1636 {//hit a lightsaber
1637 if ( (tr.fraction < saberHitFraction || tr.startsolid)
1638 && sabersDist < (8.0f+g_spskill->value)*4.0f// 50.0f//16.0f
1639 && (sabersIntersect || sabersDist < (4.0f+g_spskill->value)*2.0f) )//32.0f) )
1640 { // This saber hit closer than the last one.
1641 if ( (tr.allsolid || tr.startsolid) && owner && owner->client )
1642 {//tr.fraction will be 0, unreliable... so calculate actual
1643 float dist = Distance( start, end2 );
1644 if ( dist )
1645 {
1646 float hitFrac = WP_SabersDistance( attacker, owner )/dist;
1647 if ( hitFrac > 1.0f )
1648 {//umm... minimum distance between sabers was longer than trace...?
1649 hitFrac = 1.0f;
1650 }
1651 if ( hitFrac < saberHitFraction )
1652 {
1653 saberHitFraction = hitFrac;
1654 }
1655 }
1656 else
1657 {
1658 saberHitFraction = 0.0f;
1659 }
1660 #ifndef FINAL_BUILD
1661 if ( d_saberCombat->integer > 1 )
1662 {
1663 if ( !attacker->s.number )
1664 {
1665 gi.Printf( S_COLOR_GREEN"%d saber hit saber dist %4.2f allsolid %4.2f\n", level.time, sabersDist, saberHitFraction );
1666 }
1667 }
1668 #endif//FINAL_BUILD
1669 }
1670 else
1671 {
1672 #ifndef FINAL_BUILD
1673 if ( d_saberCombat->integer > 1 )
1674 {
1675 if ( !attacker->s.number )
1676 {
1677 gi.Printf( S_COLOR_BLUE"%d saber hit saber dist %4.2f, frac %4.2f\n", level.time, sabersDist, saberHitFraction );
1678 }
1679 saberHitFraction = tr.fraction;
1680 }
1681 #endif//FINAL_BUILD
1682 }
1683 #ifndef FINAL_BUILD
1684 if ( d_saberCombat->integer )
1685 {
1686 gi.Printf( S_COLOR_MAGENTA"hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d\n", saberHitFraction, tr.allsolid, tr.startsolid );
1687 }
1688 #endif//FINAL_BUILD
1689 VectorCopy(tr.endpos, saberHitLocation);
1690 saberHitEntity = tr.entityNum;
1691 }
1692 /*
1693 if ( owner
1694 && owner->client
1695 && attacker
1696 && attacker->client
1697 && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove ))
1698 && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 )
1699 {
1700 if ( owner->NPC
1701 && !owner->client->ps.saberInFlight
1702 && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
1703 && !Jedi_SaberBusy( owner ) )
1704 {//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
1705 if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 )))
1706 {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat
1707 //FIXME: also take into account the owner's FP_DEFENSE?
1708 if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN )
1709 {//lower-rank Jedi aren't as good blockers
1710 vec3_t attDir;
1711 VectorSubtract( end2, start, attDir );
1712 VectorNormalize( attDir );
1713 Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL );
1714 }
1715 }
1716 }
1717 }
1718 */
1719 return qfalse; // Exit, but we didn't hit the wall.
1720 }
1721 }
1722 else
1723 {
1724 #ifndef FINAL_BUILD
1725 if ( d_saberCombat->integer > 1 )
1726 {
1727 if ( !attacker->s.number )
1728 {
1729 gi.Printf( S_COLOR_RED"%d saber hit owner directly %4.2f\n", level.time, saberHitFraction );
1730 }
1731 }
1732 #endif//FINAL_BUILD
1733 }
1734
1735 if ( attacker && attacker->client && attacker->client->ps.saberInFlight )
1736 {//thrown saber hit something
1737 if ( ( hitEnt && hitEnt->client && hitEnt->health > 0 && ( hitEnt->client->NPC_class == CLASS_DESANN || hitEnt->client->NPC_class == CLASS_LUKE || (hitEnt->client->NPC_class == CLASS_GALAKMECH&&hitEnt->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) ||
1738 ( owner && owner->client && owner->health > 0 && ( owner->client->NPC_class == CLASS_DESANN || owner->client->NPC_class == CLASS_LUKE || (owner->client->NPC_class==CLASS_GALAKMECH&&owner->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) )
1739 {//Luke and Desann slap thrown sabers aside
1740 //FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin?
1741 WP_SaberKnockaway( attacker, &tr );
1742 if ( hitEnt->client )
1743 {
1744 Jedi_PlayDeflectSound( hitEnt );
1745 }
1746 else
1747 {
1748 Jedi_PlayDeflectSound( owner );
1749 }
1750 return qfalse; // Exit, but we didn't hit the wall.
1751 }
1752 }
1753
1754 if ( hitEnt->takedamage )
1755 {
1756 //no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) )
1757 {
1758 //multiply the damage by the total distance of the swipe
1759 VectorSubtract( end2, start, dir );
1760 float len = VectorNormalize( dir );//VectorLength( dir );
1761 if ( noGhoul || !hitEnt->ghoul2.size() )
1762 {//we weren't doing a ghoul trace
1763 char *hitEffect;
1764 if ( dmg >= 1.0 && hitEnt->bmodel )
1765 {
1766 dmg = 1.0;
1767 }
1768 if ( len > 1 )
1769 {
1770 dmg *= len;
1771 }
1772 #ifndef FINAL_BUILD
1773 if ( d_saberCombat->integer > 1 )
1774 {
1775 if ( !(hitEnt->contents & CONTENTS_LIGHTSABER) )
1776 {
1777 gi.Printf( S_COLOR_GREEN"Hit ent, but no ghoul collisions\n" );
1778 }
1779 }
1780 #endif
1781 float trFrac, dmgFrac;
1782 if ( tr.allsolid )
1783 {//totally inside them
1784 trFrac = 1.0;
1785 dmgFrac = 0.0;
1786 }
1787 else if ( tr.startsolid )
1788 {//started inside them
1789 //we don't know how much was inside, we know it's less than all, so use half?
1790 trFrac = 0.5;
1791 dmgFrac = 0.0;
1792 }
1793 else
1794 {//started outside them and hit them
1795 //yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2)
1796 trFrac = (1.0f - tr.fraction);
1797 dmgFrac = tr.fraction;
1798 }
1799 WP_SaberDamageAdd( trFrac, tr.entityNum, dir, tr.endpos, dmg, dmgFrac, HL_NONE, qfalse, HL_NONE );
1800 if ( !tr.allsolid && !tr.startsolid )
1801 {
1802 VectorScale( dir, -1, dir );
1803 }
1804 //FIXME: don't do blood sparks on non-living things
1805 hitEffect = hit_blood_sparks;
1806 if ( hitEnt != NULL )
1807 {
1808 if ( hitEnt->client )
1809 {
1810 class_t npc_class = hitEnt->client->NPC_class;
1811 if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
1812 npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE ||
1813 npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
1814 npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
1815 {
1816 hitEffect = hit_sparks;
1817 }
1818 }
1819 else
1820 {
1821 hitEffect = hit_sparks;
1822 }
1823 }
1824
1825 G_PlayEffect( hitEffect, tr.endpos, dir );//"saber_cut"
1826 }
1827 else
1828 {//we were doing a ghoul trace
1829 if ( !WP_SaberDamageEffects( &tr, start, len, dmg, dir, bladeDir, attacker->client->enemyTeam ) )
1830 {//didn't hit a ghoul ent
1831 /*
1832 if ( && hitEnt->ghoul2.size() )
1833 {//it was a ghoul2 model so we should have hit it
1834 return qfalse;
1835 }
1836 */
1837 }
1838 }
1839 }
1840 }
1841 }
1842
1843 return qfalse;
1844 }
1845
1846 typedef enum
1847 {
1848 LOCK_FIRST = 0,
1849 LOCK_TOP = LOCK_FIRST,
1850 LOCK_DIAG_TR,
1851 LOCK_DIAG_TL,
1852 LOCK_DIAG_BR,
1853 LOCK_DIAG_BL,
1854 LOCK_R,
1855 LOCK_L,
1856 LOCK_RANDOM
1857 } sabersLockMode_t;
1858
1859 #define LOCK_IDEAL_DIST_TOP 32.0f
1860 #define LOCK_IDEAL_DIST_CIRCLE 48.0f
1861 extern void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs );
1862 extern qboolean ValidAnimFileIndex ( int index );
WP_SabersCheckLock2(gentity_t * attacker,gentity_t * defender,sabersLockMode_t lockMode)1863 qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode )
1864 {
1865 animation_t *anim;
1866 int attAnim, defAnim, advance = 0;
1867 float attStart = 0.5f;
1868 float idealDist = 48.0f;
1869 //MATCH ANIMS
1870 if ( lockMode == LOCK_RANDOM )
1871 {
1872 lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 );
1873 }
1874 switch ( lockMode )
1875 {
1876 case LOCK_TOP:
1877 attAnim = BOTH_BF2LOCK;
1878 defAnim = BOTH_BF1LOCK;
1879 attStart = 0.5f;
1880 idealDist = LOCK_IDEAL_DIST_TOP;
1881 break;
1882 case LOCK_DIAG_TR:
1883 attAnim = BOTH_CCWCIRCLELOCK;
1884 defAnim = BOTH_CWCIRCLELOCK;
1885 attStart = 0.5f;
1886 idealDist = LOCK_IDEAL_DIST_CIRCLE;
1887 break;
1888 case LOCK_DIAG_TL:
1889 attAnim = BOTH_CWCIRCLELOCK;
1890 defAnim = BOTH_CCWCIRCLELOCK;
1891 attStart = 0.5f;
1892 idealDist = LOCK_IDEAL_DIST_CIRCLE;
1893 break;
1894 case LOCK_DIAG_BR:
1895 attAnim = BOTH_CWCIRCLELOCK;
1896 defAnim = BOTH_CCWCIRCLELOCK;
1897 attStart = 0.85f;
1898 idealDist = LOCK_IDEAL_DIST_CIRCLE;
1899 break;
1900 case LOCK_DIAG_BL:
1901 attAnim = BOTH_CCWCIRCLELOCK;
1902 defAnim = BOTH_CWCIRCLELOCK;
1903 attStart = 0.85f;
1904 idealDist = LOCK_IDEAL_DIST_CIRCLE;
1905 break;
1906 case LOCK_R:
1907 attAnim = BOTH_CWCIRCLELOCK;
1908 defAnim = BOTH_CCWCIRCLELOCK;
1909 attStart = 0.75f;
1910 idealDist = LOCK_IDEAL_DIST_CIRCLE;
1911 break;
1912 case LOCK_L:
1913 attAnim = BOTH_CCWCIRCLELOCK;
1914 defAnim = BOTH_CWCIRCLELOCK;
1915 attStart = 0.75f;
1916 idealDist = LOCK_IDEAL_DIST_CIRCLE;
1917 break;
1918 default:
1919 return qfalse;
1920 break;
1921 }
1922 NPC_SetAnim( attacker, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1923 NPC_SetAnim( defender, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1924 if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) )
1925 {
1926 anim = &level.knownAnimFileSets[attacker->client->clientInfo.animFileIndex].animations[attAnim];
1927 advance = floor( anim->numFrames*attStart );
1928 PM_SetAnimFrame( attacker, anim->firstFrame + advance, qtrue, qtrue );
1929 #ifndef FINAL_BUILD
1930 if ( d_saberCombat->integer )
1931 {
1932 Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", attacker->NPC_type, animTable[attAnim].name, anim->numFrames-advance );
1933 }
1934 #endif
1935 }
1936 if( ValidAnimFileIndex( defender->client->clientInfo.animFileIndex ) )
1937 {
1938 anim = &level.knownAnimFileSets[defender->client->clientInfo.animFileIndex].animations[defAnim];
1939 PM_SetAnimFrame( defender, anim->firstFrame + advance, qtrue, qtrue );//was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims
1940 #ifndef FINAL_BUILD
1941 if ( d_saberCombat->integer )
1942 {
1943 Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", defender->NPC_type, animTable[defAnim].name, advance );
1944 }
1945 #endif
1946 }
1947 VectorClear( attacker->client->ps.velocity );
1948 VectorClear( defender->client->ps.velocity );
1949 attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + SABER_LOCK_TIME;
1950 attacker->client->ps.legsAnimTimer = attacker->client->ps.torsoAnimTimer = defender->client->ps.legsAnimTimer = defender->client->ps.torsoAnimTimer = SABER_LOCK_TIME;
1951 //attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME;
1952 attacker->client->ps.saberLockEnemy = defender->s.number;
1953 defender->client->ps.saberLockEnemy = attacker->s.number;
1954
1955 //MATCH ANGLES
1956 //FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer?
1957 float defPitchAdd = 0, zDiff = ((attacker->currentOrigin[2]+attacker->client->standheight)-(defender->currentOrigin[2]+defender->client->standheight));
1958 if ( zDiff > 24 )
1959 {
1960 defPitchAdd = -30;
1961 }
1962 else if ( zDiff < -24 )
1963 {
1964 defPitchAdd = 30;
1965 }
1966 else
1967 {
1968 defPitchAdd = zDiff/24.0f*-30.0f;
1969 }
1970 if ( attacker->NPC && defender->NPC )
1971 {//if 2 NPCs, just set pitch to 0
1972 attacker->client->ps.viewangles[PITCH] = -defPitchAdd;
1973 defender->client->ps.viewangles[PITCH] = defPitchAdd;
1974 }
1975 else
1976 {//if a player is involved, clamp player's pitch and match NPC's to player
1977 if ( !attacker->s.number )
1978 {
1979 //clamp to defPitch
1980 if ( attacker->client->ps.viewangles[PITCH] > -defPitchAdd + 10 )
1981 {
1982 attacker->client->ps.viewangles[PITCH] = -defPitchAdd + 10;
1983 }
1984 else if ( attacker->client->ps.viewangles[PITCH] < -defPitchAdd-10 )
1985 {
1986 attacker->client->ps.viewangles[PITCH] = -defPitchAdd-10;
1987 }
1988 //clamp to sane numbers
1989 if ( attacker->client->ps.viewangles[PITCH] > 50 )
1990 {
1991 attacker->client->ps.viewangles[PITCH] = 50;
1992 }
1993 else if ( attacker->client->ps.viewangles[PITCH] < -50 )
1994 {
1995 attacker->client->ps.viewangles[PITCH] = -50;
1996 }
1997 defender->client->ps.viewangles[PITCH] = attacker->client->ps.viewangles[PITCH]*-1;
1998 defPitchAdd = defender->client->ps.viewangles[PITCH];
1999 }
2000 else if ( !defender->s.number )
2001 {
2002 //clamp to defPitch
2003 if ( defender->client->ps.viewangles[PITCH] > defPitchAdd + 10 )
2004 {
2005 defender->client->ps.viewangles[PITCH] = defPitchAdd + 10;
2006 }
2007 else if ( defender->client->ps.viewangles[PITCH] < defPitchAdd-10 )
2008 {
2009 defender->client->ps.viewangles[PITCH] = defPitchAdd-10;
2010 }
2011 //clamp to sane numbers
2012 if ( defender->client->ps.viewangles[PITCH] > 50 )
2013 {
2014 defender->client->ps.viewangles[PITCH] = 50;
2015 }
2016 else if ( defender->client->ps.viewangles[PITCH] < -50 )
2017 {
2018 defender->client->ps.viewangles[PITCH] = -50;
2019 }
2020 defPitchAdd = defender->client->ps.viewangles[PITCH];
2021 attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH]*-1;
2022 }
2023 }
2024 vec3_t attAngles, defAngles, defDir;
2025 VectorSubtract( defender->currentOrigin, attacker->currentOrigin, defDir );
2026 VectorCopy( attacker->client->ps.viewangles, attAngles );
2027 attAngles[YAW] = vectoyaw( defDir );
2028 SetClientViewAngle( attacker, attAngles );
2029 defAngles[PITCH] = attAngles[PITCH]*-1;
2030 defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180);
2031 defAngles[ROLL] = 0;
2032 SetClientViewAngle( defender, defAngles );
2033
2034 //MATCH POSITIONS
2035 vec3_t newOrg;
2036 /*
2037 idealDist -= fabs(defPitchAdd)/8.0f;
2038 */
2039 float scale = VectorLength( attacker->s.modelScale );
2040 if ( scale )
2041 {
2042 idealDist += 8*(scale-1.0f);
2043 }
2044 scale = VectorLength( defender->s.modelScale );
2045 if ( scale )
2046 {
2047 idealDist += 8*(scale-1.0f);
2048 }
2049
2050 float diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist
2051 //try to move attacker half the diff towards the defender
2052 VectorMA( attacker->currentOrigin, diff*0.5f, defDir, newOrg );
2053 trace_t trace;
2054 gi.trace( &trace, attacker->currentOrigin, attacker->mins, attacker->maxs, newOrg, attacker->s.number, attacker->clipmask, G2_NOCOLLIDE, 0 );
2055 if ( !trace.startsolid && !trace.allsolid )
2056 {
2057 G_SetOrigin( attacker, trace.endpos );
2058 gi.linkentity( attacker );
2059 }
2060 //now get the defender's dist and do it for him too
2061 vec3_t attDir;
2062 VectorSubtract( attacker->currentOrigin, defender->currentOrigin, attDir );
2063 diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist
2064 //try to move defender all of the remaining diff towards the attacker
2065 VectorMA( defender->currentOrigin, diff, attDir, newOrg );
2066 gi.trace( &trace, defender->currentOrigin, defender->mins, defender->maxs, newOrg, defender->s.number, defender->clipmask, G2_NOCOLLIDE, 0 );
2067 if ( !trace.startsolid && !trace.allsolid )
2068 {
2069 G_SetOrigin( defender, trace.endpos );
2070 gi.linkentity( defender );
2071 }
2072
2073 //DONE!
2074
2075 return qtrue;
2076 }
2077
WP_SabersCheckLock(gentity_t * ent1,gentity_t * ent2)2078 qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 )
2079 {
2080 if ( ent1->client->playerTeam == ent2->client->playerTeam )
2081 {
2082 return qfalse;
2083 }
2084 if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE ||
2085 ent2->client->ps.groundEntityNum == ENTITYNUM_NONE )
2086 {
2087 return qfalse;
2088 }
2089 if ( ent1->painDebounceTime > level.time-1000 || ent2->painDebounceTime > level.time-1000 )
2090 {//can't saberlock if you're not ready
2091 return qfalse;
2092 }
2093 if ( fabs( ent1->currentOrigin[2]-ent2->currentOrigin[2]) > 18 )
2094 {
2095 return qfalse;
2096 }
2097 float dist = DistanceSquared(ent1->currentOrigin,ent2->currentOrigin);
2098 if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 )
2099 {//between 8 and 80 from each other//was 16 and 48
2100 return qfalse;
2101 }
2102 if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) )
2103 {
2104 return qfalse;
2105 }
2106 //Check for certain anims that *cannot* lock
2107 //FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up
2108 if ( ent1->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent1->client->ps.torsoAnimTimer > 300 )
2109 {//can't lock when saber is behind you
2110 return qfalse;
2111 }
2112 if ( ent2->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent2->client->ps.torsoAnimTimer > 300 )
2113 {//can't lock when saber is behind you
2114 return qfalse;
2115 }
2116 //BR to TL lock
2117 if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2118 ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2119 ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2120 ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2121 ent1->client->ps.torsoAnim == BOTH_A5_BR_TL )
2122 {//ent1 is attacking in the opposite diagonal
2123 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
2124 }
2125 if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2126 ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2127 ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2128 ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2129 ent2->client->ps.torsoAnim == BOTH_A5_BR_TL )
2130 {//ent2 is attacking in the opposite diagonal
2131 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
2132 }
2133 //BL to TR lock
2134 if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2135 ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2136 ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2137 ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2138 ent1->client->ps.torsoAnim == BOTH_A5_BL_TR )
2139 {//ent1 is attacking in the opposite diagonal
2140 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
2141 }
2142 if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2143 ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2144 ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2145 ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2146 ent2->client->ps.torsoAnim == BOTH_A5_BL_TR )
2147 {//ent2 is attacking in the opposite diagonal
2148 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
2149 }
2150 //L to R lock
2151 if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R ||
2152 ent1->client->ps.torsoAnim == BOTH_A2__L__R ||
2153 ent1->client->ps.torsoAnim == BOTH_A3__L__R ||
2154 ent1->client->ps.torsoAnim == BOTH_A4__L__R ||
2155 ent1->client->ps.torsoAnim == BOTH_A5__L__R )
2156 {//ent1 is attacking l to r
2157 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2158 /*
2159 if ( ent2BlockingPlayer )
2160 {//player will block this anyway
2161 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2162 }
2163 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2164 ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2165 ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2166 ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2167 ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2168 ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ||
2169 ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
2170 {//ent2 is attacking or blocking on the r
2171 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2172 }
2173 if ( ent2Boss && !Q_irand( 0, 3 ) )
2174 {
2175 return WP_SabersCheckLock2( ent1, ent2, LOCK_L );
2176 }
2177 */
2178 }
2179 if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R ||
2180 ent2->client->ps.torsoAnim == BOTH_A2__L__R ||
2181 ent2->client->ps.torsoAnim == BOTH_A3__L__R ||
2182 ent2->client->ps.torsoAnim == BOTH_A4__L__R ||
2183 ent2->client->ps.torsoAnim == BOTH_A5__L__R )
2184 {//ent2 is attacking l to r
2185 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2186 /*
2187 if ( ent1BlockingPlayer )
2188 {//player will block this anyway
2189 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2190 }
2191 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2192 ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2193 ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2194 ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2195 ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2196 ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ||
2197 ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
2198 {//ent1 is attacking or blocking on the r
2199 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2200 }
2201 if ( ent1Boss && !Q_irand( 0, 3 ) )
2202 {
2203 return WP_SabersCheckLock2( ent2, ent1, LOCK_L );
2204 }
2205 */
2206 }
2207 //R to L lock
2208 if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L ||
2209 ent1->client->ps.torsoAnim == BOTH_A2__R__L ||
2210 ent1->client->ps.torsoAnim == BOTH_A3__R__L ||
2211 ent1->client->ps.torsoAnim == BOTH_A4__R__L ||
2212 ent1->client->ps.torsoAnim == BOTH_A5__R__L )
2213 {//ent1 is attacking r to l
2214 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2215 /*
2216 if ( ent2BlockingPlayer )
2217 {//player will block this anyway
2218 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2219 }
2220 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2221 ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2222 ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2223 ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2224 ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2225 ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ||
2226 ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
2227 {//ent2 is attacking or blocking on the l
2228 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2229 }
2230 if ( ent2Boss && !Q_irand( 0, 3 ) )
2231 {
2232 return WP_SabersCheckLock2( ent1, ent2, LOCK_R );
2233 }
2234 */
2235 }
2236 if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L ||
2237 ent2->client->ps.torsoAnim == BOTH_A2__R__L ||
2238 ent2->client->ps.torsoAnim == BOTH_A3__R__L ||
2239 ent2->client->ps.torsoAnim == BOTH_A4__R__L ||
2240 ent2->client->ps.torsoAnim == BOTH_A5__R__L )
2241 {//ent2 is attacking r to l
2242 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2243 /*
2244 if ( ent1BlockingPlayer )
2245 {//player will block this anyway
2246 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2247 }
2248 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2249 ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2250 ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2251 ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2252 ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2253 ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ||
2254 ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
2255 {//ent1 is attacking or blocking on the l
2256 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2257 }
2258 if ( ent1Boss && !Q_irand( 0, 3 ) )
2259 {
2260 return WP_SabersCheckLock2( ent2, ent1, LOCK_R );
2261 }
2262 */
2263 }
2264 //TR to BL lock
2265 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2266 ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2267 ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2268 ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2269 ent1->client->ps.torsoAnim == BOTH_A5_TR_BL )
2270 {//ent1 is attacking diagonally
2271 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2272 /*
2273 if ( ent2BlockingPlayer )
2274 {//player will block this anyway
2275 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2276 }
2277 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2278 ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2279 ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2280 ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2281 ent2->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2282 ent2->client->ps.torsoAnim == BOTH_P1_S1_TL )
2283 {//ent2 is attacking in the opposite diagonal
2284 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2285 }
2286 if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2287 ent2->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2288 ent2->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2289 ent2->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2290 ent2->client->ps.torsoAnim == BOTH_A5_BR_TL ||
2291 ent2->client->ps.torsoAnim == BOTH_P1_S1_BL )
2292 {//ent2 is attacking in the opposite diagonal
2293 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL );
2294 }
2295 if ( ent2Boss && !Q_irand( 0, 3 ) )
2296 {
2297 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR );
2298 }
2299 */
2300 }
2301
2302 if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2303 ent2->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2304 ent2->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2305 ent2->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2306 ent2->client->ps.torsoAnim == BOTH_A5_TR_BL )
2307 {//ent2 is attacking diagonally
2308 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2309 /*
2310 if ( ent1BlockingPlayer )
2311 {//player will block this anyway
2312 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2313 }
2314 if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL ||
2315 ent1->client->ps.torsoAnim == BOTH_A2_TR_BL ||
2316 ent1->client->ps.torsoAnim == BOTH_A3_TR_BL ||
2317 ent1->client->ps.torsoAnim == BOTH_A4_TR_BL ||
2318 ent1->client->ps.torsoAnim == BOTH_A5_TR_BL ||
2319 ent1->client->ps.torsoAnim == BOTH_P1_S1_TL )
2320 {//ent1 is attacking in the opposite diagonal
2321 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2322 }
2323 if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL ||
2324 ent1->client->ps.torsoAnim == BOTH_A2_BR_TL ||
2325 ent1->client->ps.torsoAnim == BOTH_A3_BR_TL ||
2326 ent1->client->ps.torsoAnim == BOTH_A4_BR_TL ||
2327 ent1->client->ps.torsoAnim == BOTH_A5_BR_TL ||
2328 ent1->client->ps.torsoAnim == BOTH_P1_S1_BL )
2329 {//ent1 is attacking in the opposite diagonal
2330 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL );
2331 }
2332 if ( ent1Boss && !Q_irand( 0, 3 ) )
2333 {
2334 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR );
2335 }
2336 */
2337 }
2338
2339 //TL to BR lock
2340 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2341 ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2342 ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2343 ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2344 ent1->client->ps.torsoAnim == BOTH_A5_TL_BR )
2345 {//ent1 is attacking diagonally
2346 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2347 /*
2348 if ( ent2BlockingPlayer )
2349 {//player will block this anyway
2350 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2351 }
2352 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2353 ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2354 ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2355 ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2356 ent2->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2357 ent2->client->ps.torsoAnim == BOTH_P1_S1_TR )
2358 {//ent2 is attacking in the opposite diagonal
2359 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2360 }
2361 if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2362 ent2->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2363 ent2->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2364 ent2->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2365 ent2->client->ps.torsoAnim == BOTH_A5_BL_TR ||
2366 ent2->client->ps.torsoAnim == BOTH_P1_S1_BR )
2367 {//ent2 is attacking in the opposite diagonal
2368 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR );
2369 }
2370 if ( ent2Boss && !Q_irand( 0, 3 ) )
2371 {
2372 return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL );
2373 }
2374 */
2375 }
2376
2377 if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2378 ent2->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2379 ent2->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2380 ent2->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2381 ent2->client->ps.torsoAnim == BOTH_A5_TL_BR )
2382 {//ent2 is attacking diagonally
2383 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2384 /*
2385 if ( ent1BlockingPlayer )
2386 {//player will block this anyway
2387 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2388 }
2389 if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR ||
2390 ent1->client->ps.torsoAnim == BOTH_A2_TL_BR ||
2391 ent1->client->ps.torsoAnim == BOTH_A3_TL_BR ||
2392 ent1->client->ps.torsoAnim == BOTH_A4_TL_BR ||
2393 ent1->client->ps.torsoAnim == BOTH_A5_TL_BR ||
2394 ent1->client->ps.torsoAnim == BOTH_P1_S1_TR )
2395 {//ent1 is attacking in the opposite diagonal
2396 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2397 }
2398 if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR ||
2399 ent1->client->ps.torsoAnim == BOTH_A2_BL_TR ||
2400 ent1->client->ps.torsoAnim == BOTH_A3_BL_TR ||
2401 ent1->client->ps.torsoAnim == BOTH_A4_BL_TR ||
2402 ent1->client->ps.torsoAnim == BOTH_A5_BL_TR ||
2403 ent1->client->ps.torsoAnim == BOTH_P1_S1_BR )
2404 {//ent1 is attacking in the opposite diagonal
2405 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR );
2406 }
2407 if ( ent1Boss && !Q_irand( 0, 3 ) )
2408 {
2409 return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL );
2410 }
2411 */
2412 }
2413 //T to B lock
2414 if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ ||
2415 ent1->client->ps.torsoAnim == BOTH_A2_T__B_ ||
2416 ent1->client->ps.torsoAnim == BOTH_A3_T__B_ ||
2417 ent1->client->ps.torsoAnim == BOTH_A4_T__B_ ||
2418 ent1->client->ps.torsoAnim == BOTH_A5_T__B_ )
2419 {//ent1 is attacking top-down
2420 /*
2421 if ( ent2->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
2422 ent2->client->ps.torsoAnim == BOTH_K1_S1_T_ )
2423 */
2424 {//ent2 is blocking at top
2425 return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP );
2426 }
2427 }
2428
2429 if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ ||
2430 ent2->client->ps.torsoAnim == BOTH_A2_T__B_ ||
2431 ent2->client->ps.torsoAnim == BOTH_A3_T__B_ ||
2432 ent2->client->ps.torsoAnim == BOTH_A4_T__B_ ||
2433 ent2->client->ps.torsoAnim == BOTH_A5_T__B_ )
2434 {//ent2 is attacking top-down
2435 /*
2436 if ( ent1->client->ps.torsoAnim == BOTH_P1_S1_T_ ||
2437 ent1->client->ps.torsoAnim == BOTH_K1_S1_T_ )
2438 */
2439 {//ent1 is blocking at top
2440 return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP );
2441 }
2442 }
2443 /*
2444 if ( !Q_irand( 0, 10 ) )
2445 {
2446 return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM );
2447 }
2448 */
2449 return qfalse;
2450 }
2451
WP_SaberParry(gentity_t * victim,gentity_t * attacker)2452 qboolean WP_SaberParry( gentity_t *victim, gentity_t *attacker )
2453 {
2454 if ( !victim || !victim->client || !attacker )
2455 {
2456 return qfalse;
2457 }
2458 if ( victim->s.number || g_saberAutoBlocking->integer || victim->client->ps.saberBlockingTime > level.time )
2459 {//either an NPC or a player who is blocking
2460 if ( !PM_SaberInTransitionAny( victim->client->ps.saberMove )
2461 && !PM_SaberInBounce( victim->client->ps.saberMove )
2462 && !PM_SaberInKnockaway( victim->client->ps.saberMove ) )
2463 {//I'm not attacking, in transition or in a bounce or knockaway, so play a parry
2464 WP_SaberBlockNonRandom( victim, saberHitLocation, qfalse );
2465 }
2466 victim->client->ps.saberEventFlags |= SEF_PARRIED;
2467
2468 //since it was parried, take away any damage done
2469 //FIXME: what if the damage was done before the parry?
2470 WP_SaberClearDamageForEntNum( victim->s.number );
2471
2472 //tell the victim to get mad at me
2473 if ( victim->enemy != attacker && victim->client->playerTeam != attacker->client->playerTeam )
2474 {//they're not mad at me and they're not on my team
2475 G_ClearEnemy( victim );
2476 G_SetEnemy( victim, attacker );
2477 }
2478 return qtrue;
2479 }
2480 return qfalse;
2481 }
2482
WP_BrokenParryKnockDown(gentity_t * victim)2483 qboolean WP_BrokenParryKnockDown( gentity_t *victim )
2484 {
2485 if ( !victim || !victim->client )
2486 {
2487 return qfalse;
2488 }
2489 if ( victim->client->ps.saberMove == LS_PARRY_UP
2490 || victim->client->ps.saberMove == LS_PARRY_UR
2491 || victim->client->ps.saberMove == LS_PARRY_UL
2492 || victim->client->ps.saberMove == LS_H1_BR
2493 || victim->client->ps.saberMove == LS_H1_B_
2494 || victim->client->ps.saberMove == LS_H1_BL )
2495 {//knock their asses down!
2496 int knockAnim = BOTH_KNOCKDOWN1;
2497 if ( PM_CrouchAnim( victim->client->ps.legsAnim ) )
2498 {
2499 knockAnim = BOTH_KNOCKDOWN4;
2500 }
2501 NPC_SetAnim( victim, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
2502 G_AddEvent( victim, EV_PAIN, victim->health );
2503 return qtrue;
2504 }
2505 return qfalse;
2506 }
2507 /*
2508 ---------------------------------------------------------
2509 void WP_SaberDamageTrace( gentity_t *ent )
2510
2511 Constantly trace from the old blade pos to new, down the saber beam and do damage
2512
2513 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!
2514 ---------------------------------------------------------
2515 */
2516 #define MAX_SABER_SWING_INC 0.33f
WP_SaberDamageTrace(gentity_t * ent)2517 void WP_SaberDamageTrace( gentity_t *ent )
2518 {
2519 vec3_t mp1, mp2, md1, md2, baseOld, baseNew, baseDiff, endOld, endNew, bladePointOld, bladePointNew;
2520 float tipDmgMod = 1.0f;
2521 float baseDamage;
2522 int baseDFlags = 0;
2523 qboolean hit_wall = qfalse;
2524 qboolean brokenParry = qfalse;
2525
2526 for ( int ven = 0; ven < MAX_SABER_VICTIMS; ven++ )
2527 {
2528 victimEntityNum[ven] = ENTITYNUM_NONE;
2529 }
2530 memset( totalDmg, 0, sizeof( totalDmg) );
2531 memset( dmgDir, 0, sizeof( dmgDir ) );
2532 memset( dmgSpot, 0, sizeof( dmgSpot ) );
2533 memset( dmgFraction, 0, sizeof( dmgFraction ) );
2534 memset( hitLoc, HL_NONE, sizeof( hitLoc ) );
2535 memset( hitDismemberLoc, HL_NONE, sizeof( hitDismemberLoc ) );
2536 memset( hitDismember, qfalse, sizeof( hitDismember ) );
2537 numVictims = 0;
2538 VectorClear(saberHitLocation);
2539 VectorClear(saberHitNormal);
2540 saberHitFraction = 1.0; // Closest saber hit. The saber can do no damage past this point.
2541 saberHitEntity = ENTITYNUM_NONE;
2542 sabersCrossed = -1;
2543
2544 if ( !ent->client )
2545 {
2546 return;
2547 }
2548
2549 if ( !ent->s.number )
2550 {//player never uses these
2551 ent->client->ps.saberEventFlags &= ~SEF_EVENTS;
2552 }
2553
2554 if ( ent->client->ps.saberLength <= 1 )//cen get down to 1 when in a wall
2555 {//saber is not on
2556 return;
2557 }
2558
2559 if ( VectorCompare( ent->client->renderInfo.muzzlePointOld, vec3_origin ) || VectorCompare( ent->client->renderInfo.muzzleDirOld, vec3_origin ) )
2560 {
2561 //just started up the saber?
2562 return;
2563 }
2564
2565 int saberContents = gi.pointcontents( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberEntityNum );
2566 if ( (saberContents&CONTENTS_WATER)||
2567 (saberContents&CONTENTS_SLIME)||
2568 (saberContents&CONTENTS_LAVA) )
2569 {//um... turn off? Or just set length to 1?
2570 //FIXME: short-out effect/sound?
2571 ent->client->ps.saberActive = qfalse;
2572 return;
2573 }
2574 else if ( saberContents&CONTENTS_OUTSIDE )
2575 {
2576 if ( (level.worldFlags&WF_RAINING) )
2577 {
2578 //add steam in rain
2579 if ( Q_flrand( 0, 500 ) < ent->client->ps.saberLength )
2580 {
2581 vec3_t end, normal = {0,0,1};//FIXME: opposite of rain angles?
2582 VectorMA( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberLength*Q_flrand(0, 1), ent->client->renderInfo.muzzleDir, end );
2583 G_PlayEffect( "saber/fizz", end, normal );
2584 }
2585 }
2586 }
2587
2588 //FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because
2589 // we animate so much faster that the arc is pretty much flat...
2590
2591 int entPowerLevel;
2592 if ( !ent->s.number && (ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
2593 {
2594 entPowerLevel = FORCE_LEVEL_3;
2595 }
2596 else
2597 {
2598 entPowerLevel = PM_PowerLevelForSaberAnim( &ent->client->ps );
2599 }
2600
2601 if ( ent->client->ps.saberInFlight )
2602 {//flying sabers are much more deadly
2603 //unless you're dead
2604 if ( ent->health <= 0 && !g_saberRealisticCombat->integer )
2605 {//so enemies don't keep trying to block it
2606 //FIXME: still do damage, just not to humanoid clients who should try to avoid it
2607 //baseDamage = 0.0f;
2608 return;
2609 }
2610 //or unless returning
2611 else if ( ent->client->ps.saberEntityState == SES_RETURNING )
2612 {//special case, since we're returning, chances are if we hit something
2613 //it's going to be butt-first. So do less damage.
2614 baseDamage = 0.1f;
2615 }
2616 else
2617 {
2618 if ( !ent->s.number )
2619 {//cheat for player
2620 baseDamage = 10.0f;
2621 }
2622 else
2623 {
2624 baseDamage = 2.5f;
2625 }
2626 }
2627 //Use old to current since can't predict it
2628 VectorCopy( ent->client->renderInfo.muzzlePointOld, mp1 );
2629 VectorCopy( ent->client->renderInfo.muzzleDirOld, md1 );
2630 VectorCopy( ent->client->renderInfo.muzzlePoint, mp2 );
2631 VectorCopy( ent->client->renderInfo.muzzleDir, md2 );
2632 }
2633 else
2634 {
2635 if ( ent->client->ps.saberMove == LS_READY )
2636 {//just do effects
2637 if ( g_saberRealisticCombat->integer < 2 )
2638 {//don't kill with this hit
2639 baseDFlags = DAMAGE_NO_KILL;
2640 }
2641 baseDamage = 0;
2642 }
2643 else if ( ent->client->ps.saberLockTime > level.time )
2644 {//just do effects
2645 baseDamage = 0;
2646 }
2647 else if ( ent->client->ps.saberBlocked > BLOCKED_NONE
2648 || ( !PM_SaberInAttack( ent->client->ps.saberMove )
2649 && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
2650 && !PM_SaberInTransitionAny( ent->client->ps.saberMove )
2651 )
2652 )
2653 {//don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks
2654 baseDamage = 0;
2655 }
2656 else
2657 {//okay, in a saberMove that does damage
2658 //make sure we're in the right anim
2659 if ( !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
2660 && !PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) )
2661 {//forced into some other animation somehow, like a pain or death?
2662 baseDamage = 0;
2663 }
2664 else if ( ent->client->ps.weaponstate == WEAPON_FIRING && ent->client->ps.saberBlocked == BLOCKED_NONE &&
2665 ( PM_SaberInAttack(ent->client->ps.saberMove) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SpinningSaberAnim(ent->client->ps.torsoAnim) || entPowerLevel > FORCE_LEVEL_2 ) )
2666 {//normal attack swing swinging/spinning (or if using strong set), do normal damage
2667 //FIXME: more damage for higher attack power levels?
2668 // More damage based on length/color of saber?
2669 //FIXME: Desann does double damage?
2670 if ( g_saberRealisticCombat->integer )
2671 {
2672 switch ( entPowerLevel )
2673 {
2674 case FORCE_LEVEL_3:
2675 baseDamage = 10.0f;
2676 break;
2677 case FORCE_LEVEL_2:
2678 baseDamage = 5.0f;
2679 break;
2680 default:
2681 case FORCE_LEVEL_1:
2682 baseDamage = 2.5f;
2683 break;
2684 }
2685 }
2686 else
2687 {
2688 baseDamage = 2.5f * (float)entPowerLevel;
2689 }
2690 }
2691 else
2692 {//saber is transitioning, defending or idle, don't do as much damage
2693 //FIXME: strong attacks and returns should do damage and be unblockable
2694 if ( g_timescale->value < 1.0 )
2695 {//in slow mo or force speed, we need to do damage during the transitions
2696 if ( g_saberRealisticCombat->integer )
2697 {
2698 switch ( entPowerLevel )
2699 {
2700 case FORCE_LEVEL_3:
2701 baseDamage = 10.0f;
2702 break;
2703 case FORCE_LEVEL_2:
2704 baseDamage = 5.0f;
2705 break;
2706 default:
2707 case FORCE_LEVEL_1:
2708 baseDamage = 2.5f;
2709 break;
2710 }
2711 }
2712 else
2713 {
2714 baseDamage = 2.5f * (float)entPowerLevel;
2715 }
2716 }
2717 else// if ( !ent->s.number )
2718 {//I have to do *some* damage in transitions or else you feel like a total gimp
2719 baseDamage = 0.1f;
2720 }
2721 /*
2722 else
2723 {
2724 baseDamage = 0;//was 1.0f;//was 0.25
2725 }
2726 */
2727 }
2728 }
2729
2730 //Use current to next since can predict it
2731 //FIXME: if they're closer than the saber blade start, we don't want the
2732 // arm to pass through them without any damage... so check the radius
2733 // and push them away (do pain & knockback)
2734 //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?
2735 //VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 );
2736 //VectorCopy( ent->client->renderInfo.muzzleDir, md1 );
2737 //VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 );
2738 //VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 );
2739 //prediction was causing gaps in swing (G2 problem) so *don't* predict
2740 VectorCopy( ent->client->renderInfo.muzzlePointOld, mp1 );
2741 VectorCopy( ent->client->renderInfo.muzzleDirOld, md1 );
2742 VectorCopy( ent->client->renderInfo.muzzlePoint, mp2 );
2743 VectorCopy( ent->client->renderInfo.muzzleDir, md2 );
2744
2745 //NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow...
2746 //see if anyone is so close that they're within the dist from my origin to the start of the saber
2747 if ( ent->health > 0 && !ent->client->ps.saberLockTime )
2748 {
2749 trace_t trace;
2750 gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, mp1, ent->s.number, (MASK_SHOT&~(CONTENTS_CORPSE|CONTENTS_ITEM)), G2_NOCOLLIDE, 0 );
2751 if ( trace.entityNum < ENTITYNUM_WORLD && (trace.entityNum > 0||ent->client->NPC_class == CLASS_DESANN) )//NPCs don't push player away, unless it's Desann
2752 {//a valid ent
2753 gentity_t *traceEnt = &g_entities[trace.entityNum];
2754 if ( traceEnt
2755 && traceEnt->client
2756 && traceEnt->health > 0
2757 && traceEnt->client->playerTeam != ent->client->playerTeam
2758 && !PM_InKnockDown( &traceEnt->client->ps ) )
2759 {//enemy client, push them away
2760 if ( !traceEnt->client->ps.saberLockTime && !traceEnt->message )
2761 {//don't push people in saberlock or with security keys
2762 vec3_t hitDir;
2763 VectorSubtract( trace.endpos, ent->currentOrigin, hitDir );
2764 float totalDist = Distance( mp1, ent->currentOrigin );
2765 float knockback = (totalDist-VectorNormalize( hitDir ))/totalDist * 200.0f;
2766 hitDir[2] = 0;
2767 //FIXME: do we need to call G_Throw? Seems unfair to put actual knockback on them, stops the attack
2768 //G_Throw( traceEnt, hitDir, knockback );
2769 VectorMA( traceEnt->client->ps.velocity, knockback, hitDir, traceEnt->client->ps.velocity );
2770 traceEnt->client->ps.pm_time = 200;
2771 traceEnt->client->ps.pm_flags |= PMF_TIME_NOFRICTION;
2772 #ifndef FINAL_BUILD
2773 if ( d_saberCombat->integer )
2774 {
2775 gi.Printf( "%s pushing away %s at %s\n", ent->NPC_type, traceEnt->NPC_type, vtos( traceEnt->client->ps.velocity ) );
2776 }
2777 #endif
2778 }
2779 }
2780 }
2781 }
2782 }
2783
2784 if ( g_saberRealisticCombat->integer > 1 )
2785 {//always do damage, and lots of it
2786 if ( g_saberRealisticCombat->integer > 2 )
2787 {//always do damage, and lots of it
2788 baseDamage = 25.0f;
2789 }
2790 else if ( baseDamage > 0.1f )
2791 {//only do super damage if we would have done damage according to normal rules
2792 baseDamage = 25.0f;
2793 }
2794 }
2795 else if ( (!ent->s.number&&ent->client->ps.forcePowersActive&(1<<FP_SPEED)) )
2796 {
2797 baseDamage *= (1.0f-g_timescale->value);
2798 }
2799 // Get the old state of the blade
2800 VectorCopy( mp1, baseOld );
2801 VectorMA( baseOld, ent->client->ps.saberLength, md1, endOld );
2802 // Get the future state of the blade
2803 VectorCopy( mp2, baseNew );
2804 VectorMA( baseNew, ent->client->ps.saberLength, md2, endNew );
2805
2806 sabersCrossed = -1;
2807 if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) )
2808 {
2809 hit_wall = WP_SaberDamageForTrace( ent->s.number, mp2, endNew, baseDamage*4, md2, qfalse, entPowerLevel, qfalse );
2810 }
2811 else
2812 {
2813 float aveLength, step = 8, stepsize = 8;
2814 vec3_t ma1, ma2, md2ang, curBase1, curBase2;
2815 int xx;
2816 //do the trace at the base first
2817 hit_wall = WP_SaberDamageForTrace( ent->s.number, baseOld, baseNew, baseDamage, md2, qfalse, entPowerLevel );
2818
2819 //if hit a saber, shorten rest of traces to match
2820 if ( saberHitFraction < 1.0 )
2821 {
2822 //adjust muzzleDir...
2823 vec3_t ma1, ma2;
2824 vectoangles( md1, ma1 );
2825 vectoangles( md2, ma2 );
2826 for ( xx = 0; xx < 3; xx++ )
2827 {
2828 md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction );
2829 }
2830 AngleVectors( md2ang, md2, NULL, NULL );
2831 //shorten the base pos
2832 VectorSubtract( mp2, mp1, baseDiff );
2833 VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
2834 VectorMA( baseNew, ent->client->ps.saberLength, md2, endNew );
2835 }
2836
2837 //If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc
2838 float dirInc, curDirFrac;
2839 if ( PM_SaberInAttack( ent->client->ps.saberMove )
2840 || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim )
2841 || PM_SpinningSaberAnim( ent->client->ps.torsoAnim )
2842 || PM_InSpecialJump( ent->client->ps.torsoAnim )
2843 || (g_timescale->value<1.0f&&PM_SaberInTransitionAny( ent->client->ps.saberMove )) )
2844 {
2845 curDirFrac = DotProduct( md1, md2 );
2846 }
2847 else
2848 {
2849 curDirFrac = 1.0f;
2850 }
2851 //NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...!
2852 if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC )
2853 {//the saber blade spun more than 33 degrees since the last damage trace
2854 curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC);
2855 }
2856 else
2857 {
2858 curDirFrac = 1.0f;
2859 dirInc = 0.0f;
2860 }
2861 qboolean hit_saber = qfalse;
2862
2863 vectoangles( md1, ma1 );
2864 vectoangles( md2, ma2 );
2865
2866 vec3_t curMD1, curMD2;//, mdDiff, dirDiff;
2867 //VectorSubtract( md2, md1, mdDiff );
2868 VectorCopy( md1, curMD2 );
2869 VectorCopy( baseOld, curBase2 );
2870
2871 while ( 1 )
2872 {
2873 VectorCopy( curMD2, curMD1 );
2874 VectorCopy( curBase2, curBase1 );
2875 if ( curDirFrac >= 1.0f )
2876 {
2877 VectorCopy( md2, curMD2 );
2878 VectorCopy( baseNew, curBase2 );
2879 }
2880 else
2881 {
2882 for ( xx = 0; xx < 3; xx++ )
2883 {
2884 md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac );
2885 }
2886 AngleVectors( md2ang, curMD2, NULL, NULL );
2887 //VectorMA( md1, curDirFrac, mdDiff, curMD2 );
2888 VectorSubtract( baseNew, baseOld, baseDiff );
2889 VectorMA( baseOld, curDirFrac, baseDiff, curBase2 );
2890 }
2891 // Move up the blade in intervals of stepsize
2892 for ( step = stepsize; step < ent->client->ps.saberLength && step < ent->client->ps.saberLengthOld; step+=12 )
2893 {
2894 VectorMA( curBase1, step, curMD1, bladePointOld );
2895 VectorMA( curBase2, step, curMD2, bladePointNew );
2896 if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2, qfalse, entPowerLevel ) )
2897 {
2898 hit_wall = qtrue;
2899 }
2900
2901 //if hit a saber, shorten rest of traces to match
2902 if ( saberHitFraction < 1.0 )
2903 {
2904 //adjust muzzle endpoint
2905 VectorSubtract( mp2, mp1, baseDiff );
2906 VectorMA( mp1, saberHitFraction, baseDiff, baseNew );
2907 VectorMA( baseNew, ent->client->ps.saberLength, curMD2, endNew );
2908 //adjust muzzleDir...
2909 vec3_t curMA1, curMA2;
2910 vectoangles( curMD1, curMA1 );
2911 vectoangles( curMD2, curMA2 );
2912 for ( xx = 0; xx < 3; xx++ )
2913 {
2914 md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction );
2915 }
2916 AngleVectors( md2ang, curMD2, NULL, NULL );
2917 /*
2918 VectorSubtract( curMD2, curMD1, dirDiff );
2919 VectorMA( curMD1, saberHitFraction, dirDiff, curMD2 );
2920 */
2921 hit_saber = qtrue;
2922 }
2923 if (hit_wall)
2924 {
2925 break;
2926 }
2927 }
2928 if ( hit_wall || hit_saber )
2929 {
2930 break;
2931 }
2932 if ( curDirFrac >= 1.0f )
2933 {
2934 break;
2935 }
2936 else
2937 {
2938 curDirFrac += dirInc;
2939 if ( curDirFrac >= 1.0f )
2940 {
2941 curDirFrac = 1.0f;
2942 }
2943 }
2944 }
2945
2946 //do the trace at the end last
2947 //Special check- adjust for length of blade not being a multiple of 12
2948 aveLength = (ent->client->ps.saberLengthOld + ent->client->ps.saberLength)/2;
2949 if ( step > aveLength )
2950 {//less dmg if the last interval was not stepsize
2951 tipDmgMod = (stepsize-(step-aveLength))/stepsize;
2952 }
2953 //NOTE: since this is the tip, we do not extrapolate the extra 16
2954 if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2, qfalse, entPowerLevel, qfalse ) )
2955 {
2956 hit_wall = qtrue;
2957 }
2958 }
2959
2960 if ( (saberHitFraction < 1.0f||(sabersCrossed>=0&&sabersCrossed<=32.0f)) && (ent->client->ps.weaponstate == WEAPON_FIRING || ent->client->ps.saberInFlight) )
2961 {// The saber (in-hand) hit another saber, mano.
2962 qboolean inFlightSaberBlocked = qfalse;
2963 qboolean collisionResolved = qfalse;
2964 qboolean deflected = qfalse;
2965
2966 gentity_t *hitEnt = &g_entities[saberHitEntity];
2967 gentity_t *hitOwner = NULL;
2968 int hitOwnerPowerLevel = FORCE_LEVEL_0;
2969
2970 if ( hitEnt )
2971 {
2972 hitOwner = hitEnt->owner;
2973 }
2974 if ( hitOwner && hitOwner->client )
2975 {
2976 hitOwnerPowerLevel = PM_PowerLevelForSaberAnim( &hitOwner->client->ps );
2977 if ( entPowerLevel == FORCE_LEVEL_3 && PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
2978 {//a special "unblockable" attack
2979 if ( hitOwner->client->NPC_class == CLASS_DESANN
2980 || hitOwner->client->NPC_class == CLASS_TAVION
2981 || hitOwner->client->NPC_class == CLASS_LUKE )
2982 {//these masters can even block unblockables (stops cheap kills)
2983 entPowerLevel = FORCE_LEVEL_2;
2984 }
2985 }
2986 }
2987
2988 //FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose
2989 //SEF_LOCKED
2990
2991 if ( ent->client->ps.saberInFlight &&
2992 ent->client->ps.saberActive &&
2993 ent->client->ps.saberEntityNum != ENTITYNUM_NONE &&
2994 ent->client->ps.saberEntityState != SES_RETURNING )
2995 {//saber was blocked, return it
2996 inFlightSaberBlocked = qtrue;
2997 }
2998
2999 //FIXME: based on strength, position and angle of attack & defense, decide if:
3000 // defender and attacker lock sabers
3001 // *defender's parry should hold and attack bounces (or deflects, based on angle of sabers)
3002 // *defender's parry is somewhat broken and both bounce (or deflect)
3003 // *defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead)
3004 // defender is knocked down and attack goes through
3005
3006 //Check deflections and broken parries
3007 if ( hitOwner && hitOwner->health > 0 && ent->health > 0 //both are alive
3008 && !inFlightSaberBlocked && hitOwner->client && !hitOwner->client->ps.saberInFlight && !ent->client->ps.saberInFlight//both have sabers in-hand
3009 && ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN
3010 && ent->client->ps.saberLockTime < level.time
3011 && hitOwner->client->ps.saberLockTime < level.time )
3012 {//2 in-hand sabers hit
3013 //FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown...
3014 if ( baseDamage )
3015 {//there is damage involved, not just effects
3016 qboolean entAttacking = qfalse;
3017 qboolean hitOwnerAttacking = qfalse;
3018 qboolean entDefending = qfalse;
3019 qboolean hitOwnerDefending = qfalse;
3020
3021 if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) )
3022 {
3023 entAttacking = qtrue;
3024 }
3025 else if ( entPowerLevel > FORCE_LEVEL_2 )
3026 {
3027 if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SaberInTransitionAny( ent->client->ps.saberMove ) )
3028 {
3029 entAttacking = qtrue;
3030 }
3031 }
3032 if ( PM_SaberInParry( ent->client->ps.saberMove )
3033 || ent->client->ps.saberMove == LS_READY )
3034 {
3035 entDefending = qtrue;
3036 }
3037
3038 if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) )
3039 {
3040 hitOwnerAttacking = qtrue;
3041 }
3042 else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
3043 {
3044 if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SaberInTransitionAny( hitOwner->client->ps.saberMove ) )
3045 {
3046 hitOwnerAttacking = qtrue;
3047 }
3048 }
3049 if ( PM_SaberInParry( hitOwner->client->ps.saberMove )
3050 || hitOwner->client->ps.saberMove == LS_READY )
3051 {
3052 hitOwnerDefending = qtrue;
3053 }
3054
3055 if ( entAttacking
3056 && hitOwnerAttacking
3057 && ( entPowerLevel == hitOwnerPowerLevel
3058 || (entPowerLevel > FORCE_LEVEL_2 && hitOwnerPowerLevel > FORCE_LEVEL_2 )
3059 || (entPowerLevel < FORCE_LEVEL_3 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && Q_irand( 0, 2 ))
3060 || (entPowerLevel < FORCE_LEVEL_2 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 1 ))
3061 || (hitOwnerPowerLevel < FORCE_LEVEL_3 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && !Q_irand( 0, 1 ))
3062 || (hitOwnerPowerLevel < FORCE_LEVEL_2 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && !Q_irand( 0, 1 )))
3063 && WP_SabersCheckLock( ent, hitOwner ) )
3064 {
3065 collisionResolved = qtrue;
3066 }
3067 else if ( hitOwnerAttacking
3068 && entDefending
3069 && !Q_irand( 0, 2 )
3070 && (ent->client->ps.saberMove != LS_READY || (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
3071 && ((hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )||
3072 (hitOwnerPowerLevel < FORCE_LEVEL_2 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )||
3073 (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 )))
3074 && WP_SabersCheckLock( hitOwner, ent ) )
3075 {
3076 collisionResolved = qtrue;
3077 }
3078 else if ( entAttacking && hitOwnerDefending )
3079 {//I'm attacking hit, they're parrying
3080 qboolean activeDefense = (qboolean)(
3081 hitOwner->s.number ||
3082 g_saberAutoBlocking->integer ||
3083 hitOwner->client->ps.saberBlockingTime > level.time);
3084
3085 if ( !Q_irand( 0, 2 )
3086 && activeDefense
3087 && (hitOwner->client->ps.saberMove != LS_READY || (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) )
3088 && ( ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
3089 || ( entPowerLevel < FORCE_LEVEL_2 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )
3090 || ( 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 )) )
3091 && WP_SabersCheckLock( ent, hitOwner ) )
3092 {
3093 collisionResolved = qtrue;
3094 }
3095 else if ( saberHitFraction < 1.0f )
3096 {//an actual collision
3097 if ( entPowerLevel < FORCE_LEVEL_3 && activeDefense )
3098 {//strong attacks cannot be deflected
3099 //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
3100 deflected = WP_GetSaberDeflectionAngle( ent, hitOwner );
3101 //just so Jedi knows that he was blocked
3102 ent->client->ps.saberEventFlags |= SEF_BLOCKED;
3103 }
3104 //base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
3105 if ( entPowerLevel < FORCE_LEVEL_3
3106 //&& 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?
3107 && hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
3108 && activeDefense
3109 && (hitOwnerPowerLevel > FORCE_LEVEL_2||(hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&Q_irand(0,hitOwner->client->ps.saberAnimLevel))) )
3110 {//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion)
3111 //make me parry
3112 WP_SaberParry( hitOwner, ent );
3113 //turn the parry into a knockaway
3114 hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
3115 //make them go into a broken parry
3116 ent->client->ps.saberBounceMove = PM_BrokenParryForAttack( ent->client->ps.saberMove );
3117 ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3118 if ( ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
3119 && (ent->s.number||g_saberRealisticCombat->integer) )//&& !Q_irand( 0, 3 ) )
3120 {//knocked the saber right out of his hand! (never happens to player)
3121 //Get a good velocity to send the saber in based on my parry move
3122 vec3_t throwDir;
3123 if ( !PM_VelocityForBlockedMove( &hitOwner->client->ps, throwDir ) )
3124 {
3125 PM_VelocityForSaberMove( &ent->client->ps, throwDir );
3126 }
3127 WP_SaberLose( ent, throwDir );
3128 }
3129 //just so Jedi knows that he was blocked
3130 ent->client->ps.saberEventFlags |= SEF_BLOCKED;
3131 #ifndef FINAL_BUILD
3132 if ( d_saberCombat->integer )
3133 {
3134 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 );
3135 }
3136 #endif
3137 }
3138 else if ( entPowerLevel > FORCE_LEVEL_2
3139 || !activeDefense
3140 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &ent->client->ps ) - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]/*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) )
3141 {//broke their parry altogether
3142 if ( entPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
3143 {//chance of continuing with the attack (not bouncing back)
3144 ent->client->ps.saberEventFlags &= ~SEF_BLOCKED;
3145 ent->client->ps.saberBounceMove = LS_NONE;
3146 brokenParry = qtrue;
3147 }
3148 //do some time-consuming saber-knocked-aside broken parry anim
3149 hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3150 hitOwner->client->ps.saberBounceMove = LS_NONE;
3151 if ( hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2
3152 && (ent->s.number||g_saberRealisticCombat->integer)
3153 && !Q_irand( 0, 2 ) )
3154 {//knocked the saber right out of his hand!
3155 //get the right velocity for my attack direction
3156 vec3_t throwDir;
3157 PM_VelocityForSaberMove( &ent->client->ps, throwDir );
3158 WP_SaberLose( hitOwner, throwDir );
3159 if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,3) )
3160 || ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4&&!Q_irand(0,1) ) )
3161 {// a strong attack
3162 if ( WP_BrokenParryKnockDown( hitOwner ) )
3163 {
3164 hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
3165 hitOwner->client->ps.saberBounceMove = LS_NONE;
3166 }
3167 }
3168 }
3169 else
3170 {
3171 if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,5) )
3172 || ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4&&!Q_irand(0,3) ) )
3173 {// a strong attack
3174 if ( WP_BrokenParryKnockDown( hitOwner ) )
3175 {
3176 hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
3177 hitOwner->client->ps.saberBounceMove = LS_NONE;
3178 }
3179 }
3180 }
3181 #ifndef FINAL_BUILD
3182 if ( d_saberCombat->integer )
3183 {
3184 if ( ent->client->ps.saberEventFlags&SEF_BLOCKED )
3185 {
3186 gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", hitOwner->targetname );
3187 }
3188 else
3189 {
3190 gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", hitOwner->targetname );
3191 }
3192 }
3193 #endif
3194 }
3195 else
3196 {
3197 WP_SaberParry( hitOwner, ent );
3198 if ( PM_SaberInBounce( ent->client->ps.saberMove ) //FIXME: saberMove not set until pmove!
3199 && activeDefense
3200 && hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_1 && hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5
3201 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )
3202 {//attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks
3203 //turn the parry into a knockaway
3204 hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked );
3205 }
3206 else if ( (ent->client->ps.saberAnimLevel == FORCE_LEVEL_3 && !Q_irand(0,6) )
3207 || ( ent->client->ps.saberAnimLevel==FORCE_LEVEL_4 && !Q_irand(0,3) ) )
3208 {// a strong attack can sometimes do a knockdown
3209 //HMM... maybe only if they're moving backwards?
3210 if ( WP_BrokenParryKnockDown( hitOwner ) )
3211 {
3212 hitOwner->client->ps.saberBlocked = BLOCKED_NONE;
3213 hitOwner->client->ps.saberBounceMove = LS_NONE;
3214 }
3215 }
3216 }
3217 collisionResolved = qtrue;
3218 }
3219 }
3220 /*
3221 else if ( entDefending && hitOwnerAttacking )
3222 {//I'm parrying, they're attacking
3223 if ( hitOwnerPowerLevel < FORCE_LEVEL_3 )
3224 {//strong attacks cannot be deflected
3225 //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back
3226 deflected = WP_GetSaberDeflectionAngle( hitOwner, ent );
3227 //just so Jedi knows that he was blocked
3228 hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
3229 }
3230 //FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE
3231 if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &hitOwner->client->ps ) - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) > 0 ) )
3232 {//broke my parry altogether
3233 if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) )
3234 {//chance of continuing with the attack (not bouncing back)
3235 hitOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED;
3236 hitOwner->client->ps.saberBounceMove = LS_NONE;
3237 }
3238 //do some time-consuming saber-knocked-aside broken parry anim
3239 ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3240 #ifndef FINAL_BUILD
3241 if ( d_saberCombat->integer )
3242 {
3243 if ( hitOwner->client->ps.saberEventFlags&SEF_BLOCKED )
3244 {
3245 gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", ent->targetname );
3246 }
3247 else
3248 {
3249 gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", ent->targetname );
3250 }
3251 }
3252 #endif
3253 }
3254 else
3255 {
3256 WP_SaberParry( ent, hitOwner );
3257 }
3258 collisionResolved = qtrue;
3259 }
3260 */
3261 else
3262 {//some other kind of in-hand saber collision
3263 }
3264 }
3265 }
3266 else
3267 {//some kind of in-flight collision
3268 }
3269
3270 if ( saberHitFraction < 1.0f )
3271 {
3272 if ( !collisionResolved && baseDamage )
3273 {//some other kind of in-hand saber collision
3274 //handle my reaction
3275 if ( !ent->client->ps.saberInFlight
3276 && ent->client->ps.saberLockTime < level.time )
3277 {//my saber is in hand
3278 if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
3279 {
3280 if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ||
3281 (entPowerLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(ent->client->ps.saberMove)&&!PM_SaberInParry(ent->client->ps.saberMove)&&!PM_SaberInReflect(ent->client->ps.saberMove)) )
3282 {//in the middle of attacking
3283 if ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->health > 0 )
3284 {//don't deflect/bounce in strong attack or when enemy is dead
3285 WP_GetSaberDeflectionAngle( ent, hitOwner );
3286 ent->client->ps.saberEventFlags |= SEF_BLOCKED;
3287 //since it was blocked/deflected, take away any damage done
3288 //FIXME: what if the damage was done before the parry?
3289 WP_SaberClearDamageForEntNum( hitOwner->s.number );
3290 }
3291 }
3292 else
3293 {//saber collided when not attacking, parry it
3294 //since it was blocked/deflected, take away any damage done
3295 //FIXME: what if the damage was done before the parry?
3296 WP_SaberClearDamageForEntNum( hitOwner->s.number );
3297 /*
3298 if ( ent->s.number || g_saberAutoBlocking->integer || ent->client->ps.saberBlockingTime > level.time )
3299 {//either an NPC or a player who has blocking
3300 if ( !PM_SaberInTransitionAny( ent->client->ps.saberMove ) && !PM_SaberInBounce( ent->client->ps.saberMove ) )
3301 {//I'm not attacking, in transition or in a bounce, so play a parry
3302 //just so Jedi knows that he parried something
3303 WP_SaberBlockNonRandom( ent, saberHitLocation, qfalse );
3304 }
3305 ent->client->ps.saberEventFlags |= SEF_PARRIED;
3306 }
3307 */
3308 }
3309 }
3310 else
3311 {
3312 //since it was blocked/deflected, take away any damage done
3313 //FIXME: what if the damage was done before the parry?
3314 WP_SaberClearDamageForEntNum( hitOwner->s.number );
3315 }
3316 }
3317 else
3318 {//nothing happens to *me* when my inFlight saber hits something
3319 }
3320 //handle their reaction
3321 if ( hitOwner
3322 && hitOwner->health > 0
3323 && hitOwner->client
3324 && !hitOwner->client->ps.saberInFlight
3325 && hitOwner->client->ps.saberLockTime < level.time )
3326 {//their saber is in hand
3327 if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) ||
3328 (hitOwner->client->ps.saberAnimLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(hitOwner->client->ps.saberMove)&&!PM_SaberInParry(hitOwner->client->ps.saberMove)&&!PM_SaberInReflect(hitOwner->client->ps.saberMove)) )
3329 {//in the middle of attacking
3330 /*
3331 if ( hitOwner->client->ps.saberAnimLevel < FORCE_LEVEL_3 )
3332 {//don't deflect/bounce in strong attack
3333 WP_GetSaberDeflectionAngle( hitOwner, ent );
3334 hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED;
3335 }
3336 */
3337 }
3338 else
3339 {//saber collided when not attacking, parry it
3340 if ( !PM_SaberInBrokenParry( hitOwner->client->ps.saberMove ) )
3341 {//not currently in a broken parry
3342 if ( !WP_SaberParry( hitOwner, ent ) )
3343 {//FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim?
3344 //hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN;
3345 }
3346 }
3347 }
3348 }
3349 else
3350 {//nothing happens to *hitOwner* when their inFlight saber hits something
3351 }
3352 }
3353
3354 //collision must have been handled by now
3355 //Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim
3356 if ( ent->client->ps.saberEventFlags & SEF_BLOCKED )
3357 {
3358 if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
3359 {
3360 ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
3361 }
3362 }
3363 /*
3364 if ( hitOwner && hitOwner->client->ps.saberEventFlags & SEF_BLOCKED )
3365 {
3366 hitOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE;
3367 }
3368 */
3369 }
3370
3371 if ( saberHitFraction < 1.0f || collisionResolved )
3372 {//either actually hit or locked
3373 if ( ent->client->ps.saberLockTime < level.time )
3374 {
3375 if ( inFlightSaberBlocked )
3376 {//FIXME: never hear this sound
3377 G_Sound( &g_entities[ent->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
3378 }
3379 else
3380 {
3381 if ( deflected )
3382 {
3383 G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) );
3384 }
3385 else
3386 {
3387 G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3388 }
3389 }
3390 G_PlayEffect( "saber_block", saberHitLocation, saberHitNormal );
3391 }
3392 // Set the little screen flash - only when an attack is blocked
3393 g_saberFlashTime = level.time-50;
3394 VectorCopy( saberHitLocation, g_saberFlashPos );
3395 }
3396
3397 if ( saberHitFraction < 1.0f )
3398 {
3399 if ( inFlightSaberBlocked )
3400 {//we threw a saber and it was blocked, do any effects, etc.
3401 int knockAway = 5;
3402 if ( hitEnt
3403 && hitOwner
3404 && hitOwner->client
3405 && (PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SpinningSaberAnim( hitOwner->client->ps.torsoAnim )) )
3406 {//if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away
3407 if ( hitOwnerPowerLevel > FORCE_LEVEL_2 )
3408 {//string attacks almost always knock it aside!
3409 knockAway = 1;
3410 }
3411 else
3412 {//33% chance
3413 knockAway = 2;
3414 }
3415 }
3416 if ( !Q_irand( 0, knockAway ) || //random
3417 ( hitOwner && hitOwner->client &&
3418 (hitOwner->client->NPC_class==CLASS_DESANN||hitOwner->client->NPC_class==CLASS_TAVION||hitOwner->client->NPC_class==CLASS_LUKE)
3419 ) //or if blocked by a Boss character FIXME: or base on defense level?
3420 )//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
3421 {//knock it aside and turn it off
3422 G_PlayEffect( "saber_cut", saberHitLocation, saberHitNormal );
3423 if ( hitEnt )
3424 {
3425 vec3_t newDir;
3426
3427 VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
3428 VectorNormalize( newDir );
3429 G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
3430 }
3431 Jedi_PlayDeflectSound( hitOwner );
3432 WP_SaberDrop( ent, &g_entities[ent->client->ps.saberEntityNum] );
3433 }
3434 else
3435 {
3436 if ( !Q_irand( 0, 2 ) && hitEnt )
3437 {
3438 vec3_t newDir;
3439 VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir );
3440 VectorNormalize( newDir );
3441 G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir );
3442 }
3443 WP_SaberReturn( ent, &g_entities[ent->client->ps.saberEntityNum] );
3444 }
3445 }
3446 }
3447 }
3448
3449 if ( ent->client->ps.saberLockTime > level.time
3450 && ent->s.number < ent->client->ps.saberLockEnemy
3451 && !Q_irand( 0, 3 ) )
3452 {//need to make some kind of effect
3453 vec3_t hitNorm = {0,0,1};
3454 if ( WP_SabersIntersection( ent, &g_entities[ent->client->ps.saberLockEnemy], g_saberFlashPos ) )
3455 {
3456 if ( Q_irand( 0, 10 ) )
3457 {
3458 G_PlayEffect( "saber_block", g_saberFlashPos, hitNorm );
3459 }
3460 else
3461 {
3462 g_saberFlashTime = level.time-50;
3463 G_PlayEffect( "saber_cut", g_saberFlashPos, hitNorm );
3464 }
3465 G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3466 }
3467 }
3468
3469 if ( WP_SaberApplyDamage( ent, baseDamage, baseDFlags, brokenParry ) )
3470 {//actually did damage to something
3471 #ifndef FINAL_BUILD
3472 if ( d_saberCombat->integer )
3473 {
3474 gi.Printf( "base damage was %4.2f\n", baseDamage );
3475 }
3476 #endif
3477 G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", Q_irand( 1, 3 ) ) ) );
3478 }
3479
3480 if ( hit_wall )
3481 {
3482 //just so Jedi knows that he hit a wall
3483 ent->client->ps.saberEventFlags |= SEF_HITWALL;
3484 if ( ent->s.number == 0 )
3485 {
3486 AddSoundEvent( ent, ent->currentOrigin, 128, AEL_DISCOVERED );
3487 AddSightEvent( ent, ent->currentOrigin, 256, AEL_DISCOVERED, 50 );
3488 }
3489 }
3490 }
3491
3492 //SABER THROWING============================================================================
3493 //SABER THROWING============================================================================
3494 //SABER THROWING============================================================================
3495 //SABER THROWING============================================================================
3496 //SABER THROWING============================================================================
3497 //SABER THROWING============================================================================
3498
3499 /*
3500 ================
3501 WP_SaberImpact
3502
3503 ================
3504 */
WP_SaberImpact(gentity_t * owner,gentity_t * saber,trace_t * trace)3505 void WP_SaberImpact( gentity_t *owner, gentity_t *saber, trace_t *trace )
3506 {
3507 gentity_t *other;
3508
3509 other = &g_entities[trace->entityNum];
3510
3511 if ( other->takedamage && (other->svFlags&SVF_BBRUSH) )
3512 {//a breakable brush? break it!
3513 vec3_t dir;
3514 VectorCopy( saber->s.pos.trDelta, dir );
3515 VectorNormalize( dir );
3516
3517 int dmg = other->health*2;
3518 if ( other->health > 50 && dmg > 20 && !(other->svFlags&SVF_GLASS_BRUSH) )
3519 {
3520 dmg = 20;
3521 }
3522 G_Damage( other, owner, saber, dir, trace->endpos, dmg, 0, MOD_SABER );
3523 G_PlayEffect( "saber_cut", trace->endpos, dir );
3524 if ( owner->s.number == 0 )
3525 {
3526 AddSoundEvent( owner, trace->endpos, 256, AEL_DISCOVERED );
3527 AddSightEvent( owner, trace->endpos, 512, AEL_DISCOVERED, 50 );
3528 }
3529 return;
3530 }
3531
3532 if ( saber->s.pos.trType == TR_LINEAR )
3533 {
3534 //hit a wall? send it back
3535 WP_SaberReturn( saber->owner, saber );
3536 }
3537
3538 if ( other && !other->client && (other->contents&CONTENTS_LIGHTSABER) )//&& other->s.weapon == WP_SABER )
3539 {//2 in-flight sabers collided!
3540 //Big flash
3541 //FIXME: bigger effect/sound?
3542 //FIXME: STILL DOESNT WORK!!!
3543 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3544 G_PlayEffect( "saber_block", trace->endpos );
3545 g_saberFlashTime = level.time-50;
3546 VectorCopy( trace->endpos, g_saberFlashPos );
3547 }
3548
3549 if ( owner && owner->s.number == 0 && owner->client )
3550 {
3551 //Add the event
3552 if ( owner->client->ps.saberLength > 0 )
3553 {//saber is on, very suspicious
3554 AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED );
3555 AddSightEvent( owner, saber->currentOrigin, 256, AEL_DISCOVERED, 50 );
3556 }
3557 else
3558 {//saber is off, not as suspicious
3559 AddSoundEvent( owner, saber->currentOrigin, 128, AEL_SUSPICIOUS );
3560 AddSightEvent( owner, saber->currentOrigin, 256, AEL_SUSPICIOUS );
3561 }
3562 }
3563
3564 // check for bounce
3565 if ( !other->takedamage && ( saber->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
3566 {
3567 // Check to see if there is a bounce count
3568 if ( saber->bounceCount ) {
3569 // decrement number of bounces and then see if it should be done bouncing
3570 if ( --saber->bounceCount <= 0 ) {
3571 // He (or she) will bounce no more (after this current bounce, that is).
3572 saber->s.eFlags &= ~(EF_BOUNCE | EF_BOUNCE_HALF);
3573 if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
3574 {
3575 WP_SaberDrop( saber->owner, saber );
3576 }
3577 return;
3578 }
3579 else
3580 {//bounced and still have bounces left
3581 if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
3582 {//under telekinetic control
3583 if ( !gi.inPVS( saber->currentOrigin, owner->client->renderInfo.handRPoint ) )
3584 {//not in the PVS of my master
3585 saber->bounceCount -= 25;
3586 }
3587 }
3588 }
3589 }
3590
3591 if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING )
3592 {
3593 //don't home for a few frames so we can get around this thing
3594 trace_t bounceTr;
3595 vec3_t end;
3596 float owner_dist = Distance( owner->client->renderInfo.handRPoint, saber->currentOrigin );
3597
3598 VectorMA( saber->currentOrigin, 10, trace->plane.normal, end );
3599 gi.trace( &bounceTr, saber->currentOrigin, saber->mins, saber->maxs, end, owner->s.number, saber->clipmask, G2_NOCOLLIDE, 0 );
3600 VectorCopy( bounceTr.endpos, saber->currentOrigin );
3601 if ( owner_dist > 0 )
3602 {
3603 if ( owner_dist > 50 )
3604 {
3605 owner->client->ps.saberEntityDist = owner_dist-50;
3606 }
3607 else
3608 {
3609 owner->client->ps.saberEntityDist = 0;
3610 }
3611 }
3612 return;
3613 }
3614
3615 G_BounceMissile( saber, trace );
3616
3617 if ( saber->s.pos.trType == TR_GRAVITY )
3618 {//bounced
3619 //play a bounce sound
3620 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
3621 //change rotation
3622 VectorCopy( saber->currentAngles, saber->s.apos.trBase );
3623 saber->s.apos.trType = TR_LINEAR;
3624 saber->s.apos.trTime = level.time;
3625 VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
3626 }
3627 //see if we stopped
3628 else if ( saber->s.pos.trType == TR_STATIONARY )
3629 {//stopped
3630 //play a bounce sound
3631 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) );
3632 //stop rotation
3633 VectorClear( saber->s.apos.trDelta );
3634 saber->currentAngles[0] = SABER_PITCH_HACK;
3635 VectorCopy( saber->currentAngles, saber->s.apos.trBase );
3636 //remember when it fell so it can return automagically
3637 saber->aimDebounceTime = level.time;
3638 }
3639 }
3640 else if ( other->client && other->health > 0
3641 && ( other->client->NPC_class == CLASS_DESANN || other->client->NPC_class == CLASS_TAVION || other->client->NPC_class == CLASS_LUKE || ( other->client->NPC_class == CLASS_GALAKMECH && other->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) ) )
3642 {//Luke, Desann and Tavion slap thrown sabers aside
3643 WP_SaberDrop( owner, saber );
3644 G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3645 G_PlayEffect( "saber_block", trace->endpos );
3646 g_saberFlashTime = level.time-50;
3647 VectorCopy( trace->endpos, g_saberFlashPos );
3648 //FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens
3649 Jedi_PlayDeflectSound( other );
3650 }
3651 }
3652
3653 extern float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from );
WP_SaberInFlightReflectCheck(gentity_t * self,usercmd_t * ucmd)3654 void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd )
3655 {
3656 gentity_t *ent;
3657 gentity_t *entityList[MAX_GENTITIES];
3658 gentity_t *missile_list[MAX_GENTITIES];
3659 int numListedEntities;
3660 vec3_t mins, maxs;
3661 int i, e;
3662 int ent_count = 0;
3663 int radius = 180;
3664 vec3_t center, forward;
3665 vec3_t tip;
3666 vec3_t up = {0,0,1};
3667
3668 if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) )
3669 {//don't react to things flying at me...
3670 return;
3671 }
3672 //sanity checks: make sure we actually have a saberent
3673 if ( self->client->ps.weapon != WP_SABER )
3674 {
3675 return;
3676 }
3677 if ( !self->client->ps.saberInFlight )
3678 {
3679 return;
3680 }
3681 if ( !self->client->ps.saberLength )
3682 {
3683 return;
3684 }
3685 if ( self->client->ps.saberEntityNum == ENTITYNUM_NONE )
3686 {
3687 return;
3688 }
3689 gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum];
3690 if ( !saberent )
3691 {
3692 return;
3693 }
3694 //okay, enough damn sanity checks
3695
3696 VectorCopy( saberent->currentOrigin, center );
3697
3698 for ( i = 0 ; i < 3 ; i++ )
3699 {
3700 mins[i] = center[i] - radius;
3701 maxs[i] = center[i] + radius;
3702 }
3703
3704 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
3705
3706 //FIXME: check visibility?
3707 for ( e = 0 ; e < numListedEntities ; e++ )
3708 {
3709 ent = entityList[ e ];
3710
3711 if (ent == self)
3712 continue;
3713 if (ent->owner == self)
3714 continue;
3715 if ( !(ent->inuse) )
3716 continue;
3717 if ( ent->s.eType != ET_MISSILE )
3718 {
3719 if ( ent->client || ent->s.weapon != WP_SABER )
3720 {//FIXME: wake up bad guys?
3721 continue;
3722 }
3723 if ( ent->s.eFlags & EF_NODRAW )
3724 {
3725 continue;
3726 }
3727 if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
3728 {//not a lightsaber
3729 continue;
3730 }
3731 }
3732 else
3733 {//FIXME: make exploding missiles explode?
3734 if ( ent->s.pos.trType == TR_STATIONARY )
3735 {//nothing you can do with a stationary missile
3736 continue;
3737 }
3738 if ( ent->splashDamage || ent->splashRadius )
3739 {//can't deflect exploding missiles
3740 if ( DistanceSquared( ent->currentOrigin, center ) < 256 )//16 squared
3741 {
3742 G_MissileImpacted( ent, saberent, ent->currentOrigin, up );
3743 }
3744 continue;
3745 }
3746 }
3747
3748 //don't deflect it if it's not within 16 units of the blade
3749 VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, tip );
3750
3751 if( G_PointDistFromLineSegment( self->client->renderInfo.muzzlePoint, tip, ent->currentOrigin ) > 32 )
3752 {
3753 continue;
3754 }
3755 // ok, we are within the radius, add us to the incoming list
3756 missile_list[ent_count] = ent;
3757 ent_count++;
3758
3759 }
3760
3761 if ( ent_count )
3762 {
3763 vec3_t fx_dir;
3764 // we are done, do we have any to deflect?
3765 if ( ent_count )
3766 {
3767 for ( int x = 0; x < ent_count; x++ )
3768 {
3769 if ( missile_list[x]->s.weapon == WP_SABER )
3770 {//just send it back
3771 if ( missile_list[x]->owner && missile_list[x]->owner->client && missile_list[x]->owner->client->ps.saberActive && missile_list[x]->s.pos.trType == TR_LINEAR && missile_list[x]->owner->client->ps.saberEntityState != SES_RETURNING )
3772 {//it's on and being controlled
3773 //FIXME: prevent it from damaging me?
3774 WP_SaberReturn( missile_list[x]->owner, missile_list[x] );
3775 VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
3776 G_PlayEffect( "saber_block", missile_list[x]->currentOrigin, fx_dir );
3777 if ( missile_list[x]->owner->client->ps.saberInFlight && self->client->ps.saberInFlight )
3778 {
3779 G_Sound( missile_list[x], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) );
3780 g_saberFlashTime = level.time-50;
3781 gentity_t *saber = &g_entities[self->client->ps.saberEntityNum];
3782 vec3_t org;
3783 VectorSubtract( missile_list[x]->currentOrigin, saber->currentOrigin, org );
3784 VectorMA( saber->currentOrigin, 0.5, org, org );
3785 VectorCopy( org, g_saberFlashPos );
3786 }
3787 }
3788 }
3789 else
3790 {//bounce it
3791 if ( self->client && !self->s.number )
3792 {
3793 self->client->sess.missionStats.saberBlocksCnt++;
3794 }
3795
3796 G_ReflectMissile( self, missile_list[x], forward );
3797 //do an effect
3798 VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir );
3799 G_PlayEffect( "blaster/deflect", missile_list[x]->currentOrigin, fx_dir );
3800 }
3801 }
3802 }
3803 }
3804 }
3805
WP_SaberValidateEnemy(gentity_t * self,gentity_t * enemy)3806 qboolean WP_SaberValidateEnemy( gentity_t *self, gentity_t *enemy )
3807 {
3808 if ( !enemy )
3809 {
3810 return qfalse;
3811 }
3812
3813 if ( !enemy || enemy == self || !enemy->inuse || !enemy->client )
3814 {//not valid
3815 return qfalse;
3816 }
3817
3818 if ( enemy->health <= 0 )
3819 {//corpse
3820 return qfalse;
3821 }
3822
3823 if ( DistanceSquared( self->client->renderInfo.handRPoint, enemy->currentOrigin ) > saberThrowDistSquared[self->client->ps.forcePowerLevel[FP_SABERTHROW]] )
3824 {//too far
3825 return qfalse;
3826 }
3827
3828 if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
3829 && ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) )
3830 {//(not in front or not clear LOS) & greater than 256 away
3831 return qfalse;
3832 }
3833
3834 if ( enemy->client->playerTeam == self->client->playerTeam )
3835 {//on same team
3836 return qfalse;
3837 }
3838
3839 //LOS?
3840
3841 return qtrue;
3842 }
3843
WP_SaberRateEnemy(gentity_t * enemy,vec3_t center,vec3_t forward,float radius)3844 float WP_SaberRateEnemy( gentity_t *enemy, vec3_t center, vec3_t forward, float radius )
3845 {
3846 float rating;
3847 vec3_t dir;
3848
3849 VectorSubtract( enemy->currentOrigin, center, dir );
3850 rating = (1.0f-(VectorNormalize( dir )/radius));
3851 rating *= DotProduct( forward, dir );
3852 return rating;
3853 }
3854
WP_SaberFindEnemy(gentity_t * self,gentity_t * saber)3855 gentity_t *WP_SaberFindEnemy( gentity_t *self, gentity_t *saber )
3856 {
3857 //FIXME: should be a more intelligent way of doing this, like auto aim?
3858 //closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
3859 gentity_t *ent, *bestEnt = NULL;
3860 gentity_t *entityList[MAX_GENTITIES];
3861 int numListedEntities;
3862 vec3_t center, mins, maxs, fwdangles, forward;
3863 int i, e;
3864 float radius = 400;
3865 float rating, bestRating = 0.0f;
3866
3867 //FIXME: no need to do this in 1st person?
3868 fwdangles[1] = self->client->ps.viewangles[1];
3869 AngleVectors( fwdangles, forward, NULL, NULL );
3870
3871 VectorCopy( saber->currentOrigin, center );
3872
3873 for ( i = 0 ; i < 3 ; i++ )
3874 {
3875 mins[i] = center[i] - radius;
3876 maxs[i] = center[i] + radius;
3877 }
3878
3879 if ( WP_SaberValidateEnemy( self, self->enemy ) )
3880 {
3881 bestEnt = self->enemy;
3882 bestRating = WP_SaberRateEnemy( bestEnt, center, forward, radius );
3883 }
3884
3885 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
3886
3887 if ( !numListedEntities )
3888 {//should we clear the enemy?
3889 return bestEnt;
3890 }
3891
3892 for ( e = 0 ; e < numListedEntities ; e++ )
3893 {
3894 ent = entityList[ e ];
3895
3896 if ( ent == self || ent == saber || ent == bestEnt )
3897 {
3898 continue;
3899 }
3900 if ( !WP_SaberValidateEnemy( self, ent ) )
3901 {//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
3902 continue;
3903 }
3904 if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
3905 {//not even potentially visible
3906 continue;
3907 }
3908 if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
3909 {//can't see him
3910 continue;
3911 }
3912 //rate him based on how close & how in front he is
3913 rating = WP_SaberRateEnemy( ent, center, forward, radius );
3914 if ( rating > bestRating )
3915 {
3916 bestEnt = ent;
3917 bestRating = rating;
3918 }
3919 }
3920 return bestEnt;
3921 }
3922
WP_RunSaber(gentity_t * self,gentity_t * saber)3923 void WP_RunSaber( gentity_t *self, gentity_t *saber )
3924 {
3925 vec3_t origin, oldOrg;
3926 trace_t tr;
3927
3928 VectorCopy( saber->currentOrigin, oldOrg );
3929 // get current position
3930 EvaluateTrajectory( &saber->s.pos, level.time, origin );
3931 // get current angles
3932 EvaluateTrajectory( &saber->s.apos, level.time, saber->currentAngles );
3933
3934 // trace a line from the previous position to the current position,
3935 // ignoring interactions with the missile owner
3936 int clipmask = saber->clipmask;
3937 if ( !self || !self->client || self->client->ps.saberLength <= 0 )
3938 {//don't keep hitting other sabers when turned off
3939 clipmask &= ~CONTENTS_LIGHTSABER;
3940 }
3941 gi.trace( &tr, saber->currentOrigin, saber->mins, saber->maxs, origin,
3942 saber->owner ? saber->owner->s.number : ENTITYNUM_NONE, clipmask, G2_NOCOLLIDE, 0 );
3943
3944 VectorCopy( tr.endpos, saber->currentOrigin );
3945
3946 if ( self->client->ps.saberActive )
3947 {
3948 if ( self->client->ps.saberInFlight || (self->client->ps.weaponTime&&!Q_irand( 0, 100 )) )
3949 {//make enemies run from a lit saber in flight or from me when I'm attacking
3950 if ( !Q_irand( 0, 10 ) )
3951 {//not so often...
3952 AddSightEvent( self, saber->currentOrigin, self->client->ps.saberLength*3, AEL_DANGER, 100 );
3953 }
3954 }
3955 }
3956
3957 if ( tr.startsolid )
3958 {
3959 tr.fraction = 0;
3960 }
3961
3962 gi.linkentity( saber );
3963
3964 //touch push triggers?
3965
3966 if ( tr.fraction != 1 )
3967 {
3968 WP_SaberImpact( self, saber, &tr );
3969 }
3970
3971 if ( saber->s.pos.trType == TR_LINEAR )
3972 {//home
3973 //figure out where saber should be
3974 vec3_t forward, saberHome, saberDest, fwdangles = {0};
3975
3976 VectorCopy( self->client->ps.viewangles, fwdangles );
3977 if ( self->s.number )
3978 {
3979 fwdangles[0] -= 8;
3980 }
3981 else if ( cg.renderingThirdPerson )
3982 {
3983 fwdangles[0] -= 5;
3984 }
3985
3986 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1
3987 || self->client->ps.saberEntityState == SES_RETURNING
3988 || VectorCompare( saber->s.pos.trDelta, vec3_origin ) )
3989 {//control if it's returning or just starting
3990 float saberSpeed = 500;//FIXME: based on force level?
3991 float dist;
3992 gentity_t *enemy = NULL;
3993
3994 AngleVectors( fwdangles, forward, NULL, NULL );
3995
3996 if ( self->client->ps.saberEntityDist < 100 )
3997 {//make the saber head to my hand- the bolt it was attached to
3998 VectorCopy( self->client->renderInfo.handRPoint, saberHome );
3999 }
4000 else
4001 {//aim saber from eyes
4002 VectorCopy( self->client->renderInfo.eyePoint, saberHome );
4003 }
4004 VectorMA( saberHome, self->client->ps.saberEntityDist, forward, saberDest );
4005
4006 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING )
4007 {//max level
4008 //pick an enemy
4009 enemy = WP_SaberFindEnemy( self, saber );
4010 if ( enemy )
4011 {//home in on enemy
4012 float enemyDist = Distance( self->client->renderInfo.handRPoint, enemy->currentOrigin );
4013 VectorCopy( enemy->currentOrigin, saberDest );
4014 saberDest[2] += enemy->maxs[2]/2.0f;//FIXME: when in a knockdown anim, the saber float above them... do we care?
4015 self->client->ps.saberEntityDist = enemyDist;
4016 }
4017 }
4018
4019
4020 //Make the saber head there
4021 VectorSubtract( saberDest, saber->currentOrigin, saber->s.pos.trDelta );
4022 dist = VectorNormalize( saber->s.pos.trDelta );
4023 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING && !enemy )
4024 {
4025 if ( dist < 200 )
4026 {
4027 saberSpeed = 400 - (dist*2);
4028 }
4029 }
4030 else if ( self->client->ps.saberEntityState == SES_LEAVING && dist < 50 )
4031 {
4032 saberSpeed = dist * 2 + 30;
4033 if ( (enemy && dist > enemy->maxs[0]) || (!enemy && dist > 24) )
4034 {//auto-tracking an enemy and we can't hit him
4035 if ( saberSpeed < 120 )
4036 {//clamp to a minimum speed
4037 saberSpeed = 120;
4038 }
4039 }
4040 }
4041 /*
4042 if ( self->client->ps.saberEntityState == SES_RETURNING )
4043 {//FIXME: if returning, move faster?
4044 saberSpeed = 800;
4045 if ( dist < 200 )
4046 {
4047 saberSpeed -= 400 - (dist*2);
4048 }
4049 }
4050 */
4051 VectorScale( saber->s.pos.trDelta, saberSpeed, saber->s.pos.trDelta );
4052 //SnapVector( saber->s.pos.trDelta ); // save net bandwidth
4053 VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
4054 saber->s.pos.trTime = level.time;
4055 saber->s.pos.trType = TR_LINEAR;
4056 }
4057 else
4058 {
4059 VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
4060 saber->s.pos.trTime = level.time;
4061 saber->s.pos.trType = TR_LINEAR;
4062 }
4063
4064 //if it's heading back, point it's base at us
4065 if ( self->client->ps.saberEntityState == SES_RETURNING )
4066 {
4067 fwdangles[0] += SABER_PITCH_HACK;
4068 VectorCopy( fwdangles, saber->s.apos.trBase );
4069 saber->s.apos.trTime = level.time;
4070 saber->s.apos.trType = TR_INTERPOLATE;
4071 VectorClear( saber->s.apos.trDelta );
4072 }
4073 }
4074 }
4075
4076
WP_SaberLaunch(gentity_t * self,gentity_t * saber,qboolean thrown)4077 qboolean WP_SaberLaunch( gentity_t *self, gentity_t *saber, qboolean thrown )
4078 {//FIXME: probably need a debounce time
4079 vec3_t saberMins={-3.0f,-3.0f,-3.0f};
4080 vec3_t saberMaxs={3.0f,3.0f,3.0f};
4081 trace_t trace;
4082
4083 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4084 {
4085 if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 20 ) )
4086 {
4087 return qfalse;
4088 }
4089 }
4090 else
4091 {
4092 if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 0 ) )
4093 {
4094 return qfalse;
4095 }
4096 }
4097 if ( !self->s.number && (cg.zoomMode || in_camera) )
4098 {//can't saber throw when zoomed in or in cinematic
4099 return qfalse;
4100 }
4101 //make sure it won't start in solid
4102 gi.trace( &trace, self->client->renderInfo.handRPoint, saberMins, saberMaxs, self->client->renderInfo.handRPoint, saber->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4103 if ( trace.startsolid || trace.allsolid )
4104 {
4105 return qfalse;
4106 }
4107 //make sure I'm not throwing it on the other side of a door or wall or whatever
4108 gi.trace( &trace, self->currentOrigin, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4109 if ( trace.startsolid || trace.allsolid || trace.fraction < 1.0f )
4110 {
4111 return qfalse;
4112 }
4113
4114 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4115 {//at max skill, the cost increases as keep it out
4116 WP_ForcePowerStart( self, FP_SABERTHROW, 10 );
4117 }
4118 else
4119 {
4120 WP_ForcePowerStart( self, FP_SABERTHROW, 0 );
4121 }
4122 //draw it
4123 saber->s.eFlags &= ~EF_NODRAW;
4124 saber->svFlags |= SVF_BROADCAST;
4125 saber->svFlags &= ~SVF_NOCLIENT;
4126
4127 //place it
4128 VectorCopy( self->client->renderInfo.handRPoint, saber->currentOrigin );//muzzlePoint
4129 VectorCopy( saber->currentOrigin, saber->s.pos.trBase );
4130 saber->s.pos.trTime = level.time;
4131 saber->s.pos.trType = TR_LINEAR;
4132 VectorClear( saber->s.pos.trDelta );
4133 gi.linkentity( saber );
4134
4135 //spin it
4136 VectorClear( saber->s.apos.trBase );
4137 saber->s.apos.trTime = level.time;
4138 saber->s.apos.trType = TR_LINEAR;
4139 if ( self->health > 0 && thrown )
4140 {//throwing it
4141 saber->s.apos.trBase[1] = self->client->ps.viewangles[1];
4142 saber->s.apos.trBase[0] = SABER_PITCH_HACK;
4143 }
4144 else
4145 {//dropping it
4146 vectoangles( self->client->renderInfo.muzzleDir, saber->s.apos.trBase );
4147 }
4148 VectorClear( saber->s.apos.trDelta );
4149
4150 switch ( self->client->ps.forcePowerLevel[FP_SABERTHROW] )
4151 {//FIXME: make a table?
4152 default:
4153 case FORCE_LEVEL_1:
4154 saber->s.apos.trDelta[1] = 600;
4155 break;
4156 case FORCE_LEVEL_2:
4157 saber->s.apos.trDelta[1] = 800;
4158 break;
4159 case FORCE_LEVEL_3:
4160 saber->s.apos.trDelta[1] = 1200;
4161 break;
4162 }
4163
4164 //Take it out of my hand
4165 self->client->ps.saberInFlight = qtrue;
4166 self->client->ps.saberEntityState = SES_LEAVING;
4167 self->client->ps.saberEntityDist = saberThrowDist[self->client->ps.forcePowerLevel[FP_SABERTHROW]];
4168 self->client->ps.saberThrowTime = level.time;
4169 //if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4170 {
4171 self->client->ps.forcePowerDebounce[FP_SABERTHROW] = level.time + 1000;//so we can keep it out for a minimum amount of time
4172 }
4173
4174 //if it's not active, turn it on
4175 self->client->ps.saberActive = qtrue;
4176
4177 //turn on the saber trail
4178 self->client->saberTrail.inAction = qtrue;
4179 self->client->saberTrail.duration = 150;
4180
4181 //reset the mins
4182 VectorCopy( saberMins, saber->mins );
4183 VectorCopy( saberMaxs, saber->maxs );
4184 saber->contents = 0;//CONTENTS_LIGHTSABER;
4185 saber->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER;
4186
4187 // remove the ghoul2 sabre model on the player
4188 if ( self->weaponModel >= 0 )
4189 {
4190 gi.G2API_RemoveGhoul2Model(self->ghoul2, self->weaponModel);
4191 self->weaponModel = -1;
4192 }
4193
4194 return qtrue;
4195 }
4196
WP_SaberLose(gentity_t * self,vec3_t throwDir)4197 qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir )
4198 {
4199 if ( !self || !self->client || self->client->ps.saberEntityNum <= 0 )
4200 {//WTF?!! We lost it already?
4201 return qfalse;
4202 }
4203 gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum];
4204 if ( !self->client->ps.saberInFlight )
4205 {//not alreay in air
4206 //make it so we can throw it
4207 self->client->ps.forcePowersKnown |= (1<<FP_SABERTHROW);
4208 self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1;
4209 //throw it
4210 if ( !WP_SaberLaunch( self, dropped, qfalse ) )
4211 {//couldn't throw it
4212 return qfalse;
4213 }
4214 }
4215 if ( self->client->ps.saberActive )
4216 {//on
4217 //drop it instantly
4218 WP_SaberDrop( self, dropped );
4219 }
4220 //optionally give it some thrown velocity
4221 if ( throwDir && !VectorCompare( throwDir, vec3_origin ) )
4222 {
4223 VectorCopy( throwDir, dropped->s.pos.trDelta );
4224 }
4225 //don't pull it back on the next frame
4226 if ( self->NPC )
4227 {
4228 self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK;
4229 }
4230 return qtrue;
4231 }
4232
WP_SaberCatch(gentity_t * self,gentity_t * saber,qboolean switchToSaber)4233 void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber )
4234 {//FIXME: probably need a debounce time
4235 if ( self->health > 0 && !PM_SaberInBrokenParry( self->client->ps.saberMove ) && self->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN )
4236 {
4237 //don't draw it
4238 saber->s.eFlags |= EF_NODRAW;
4239 saber->svFlags &= SVF_BROADCAST;
4240 saber->svFlags |= SVF_NOCLIENT;
4241
4242 //take off any gravity stuff if we'd dropped it
4243 saber->s.pos.trType = TR_LINEAR;
4244 saber->s.eFlags &= ~EF_BOUNCE_HALF;
4245
4246 //Put it in my hand
4247 self->client->ps.saberInFlight = qfalse;
4248 self->client->ps.saberEntityState = SES_LEAVING;
4249
4250 //turn off the saber trail
4251 self->client->saberTrail.inAction = qfalse;
4252 self->client->saberTrail.duration = 75;
4253
4254 //reset its contents/clipmask
4255 saber->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
4256 saber->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
4257
4258 //play catch sound
4259 G_Sound( saber, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) );
4260 //FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead...
4261 //if it's not our current weapon, make it our current weapon
4262 if ( self->client->ps.weapon == WP_SABER )
4263 {
4264 G_CreateG2AttachedWeaponModel( self, self->client->ps.saberModel );
4265 }
4266 if ( switchToSaber )
4267 {
4268 if ( self->client->ps.weapon != WP_SABER )
4269 {
4270 CG_ChangeWeapon( WP_SABER );
4271 }
4272 else
4273 {//if it's not active, turn it on
4274 self->client->ps.saberActive = qtrue;
4275 }
4276 }
4277 }
4278 }
4279
4280
WP_SaberReturn(gentity_t * self,gentity_t * saber)4281 void WP_SaberReturn( gentity_t *self, gentity_t *saber )
4282 {
4283 if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
4284 {
4285 return;
4286 }
4287 if ( self && self->client )
4288 {//still alive and stuff
4289 //FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm...
4290 self->client->ps.saberEntityState = SES_RETURNING;
4291 //turn down the saber trail
4292 self->client->saberTrail.inAction = qfalse;
4293 self->client->saberTrail.duration = 75;
4294 }
4295 if ( !(saber->s.eFlags&EF_BOUNCE) )
4296 {
4297 saber->s.eFlags |= EF_BOUNCE;
4298 saber->bounceCount = 300;
4299 }
4300 }
4301
4302
WP_SaberDrop(gentity_t * self,gentity_t * saber)4303 void WP_SaberDrop( gentity_t *self, gentity_t *saber )
4304 {
4305 saber->s.eFlags &= ~EF_BOUNCE;
4306 saber->bounceCount = 0;
4307 //make it fall
4308 saber->s.pos.trType = TR_GRAVITY;
4309 //make it bounce some
4310 saber->s.eFlags |= EF_BOUNCE_HALF;
4311 //make it spin
4312 VectorCopy( saber->currentAngles, saber->s.apos.trBase );
4313 saber->s.apos.trType = TR_LINEAR;
4314 saber->s.apos.trTime = level.time;
4315 VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), saber->s.apos.trDelta[1], Q_irand( -300, 300 ) );
4316 if ( !saber->s.apos.trDelta[1] )
4317 {
4318 saber->s.apos.trDelta[1] = Q_irand( -300, 300 );
4319 }
4320 //force it to be ready to return
4321 self->client->ps.saberEntityDist = 0;
4322 self->client->ps.saberEntityState = SES_RETURNING;
4323 //turn it off
4324 self->client->ps.saberActive = qfalse;
4325 //turn off the saber trail
4326 self->client->saberTrail.inAction = qfalse;
4327 self->client->saberTrail.duration = 75;
4328 //play the saber turning off sound
4329 if ( self->client->playerTeam == TEAM_PLAYER )
4330 {
4331 G_SoundOnEnt( saber, CHAN_AUTO, "sound/weapons/saber/saberoff.wav" );
4332 }
4333 else
4334 {
4335 G_SoundOnEnt( saber, CHAN_AUTO, "sound/weapons/saber/enemy_saber_off.wav" );
4336 }
4337
4338 if ( self->health <= 0 )
4339 {//owner is dead!
4340 saber->s.time = level.time;//will make us free ourselves after a time
4341 }
4342 }
4343
4344
WP_SaberPull(gentity_t * self,gentity_t * saber)4345 void WP_SaberPull( gentity_t *self, gentity_t *saber )
4346 {
4347 if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN )
4348 {
4349 return;
4350 }
4351 if ( self->health > 0 )
4352 {
4353 //take off gravity
4354 saber->s.pos.trType = TR_LINEAR;
4355 //take off bounce
4356 saber->s.eFlags &= EF_BOUNCE_HALF;
4357 //play sound
4358 G_Sound( self, G_SoundIndex( "sound/weapons/force/pull.wav" ) );
4359 }
4360 }
4361
4362
4363 // Check if we are throwing it, launch it if needed, update position if needed.
WP_SaberThrow(gentity_t * self,usercmd_t * ucmd)4364 void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd )
4365 {
4366 vec3_t saberDiff;
4367 trace_t tr;
4368 //static float SABER_SPEED = 10;
4369
4370 gentity_t *saberent;
4371
4372 if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
4373 {//WTF?!! We lost it?
4374 return;
4375 }
4376
4377 saberent = &g_entities[self->client->ps.saberEntityNum];
4378
4379 VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
4380
4381 //is our saber in flight?
4382 if ( !self->client->ps.saberInFlight )
4383 {//saber is not in flight right now
4384 if ( self->client->ps.weapon != WP_SABER )
4385 {//don't even have it out
4386 return;
4387 }
4388 else if ( ucmd->buttons & BUTTON_ALT_ATTACK && !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) )
4389 {//still holding it, not still holding attack from a previous throw, so throw it.
4390 if ( !(self->client->ps.saberEventFlags&SEF_INWATER) && WP_SaberLaunch( self, saberent, qtrue ) )
4391 {
4392 if ( self->client && !self->s.number )
4393 {
4394 self->client->sess.missionStats.saberThrownCnt++;
4395 }
4396 //need to recalc this because we just moved it
4397 VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff );
4398 }
4399 else
4400 {//couldn't throw it
4401 return;
4402 }
4403 }
4404 else
4405 {//holding it, don't want to throw it, go away.
4406 return;
4407 }
4408 }
4409 else
4410 {//inflight
4411 //is our saber currently on it's way back to us?
4412 if ( self->client->ps.saberEntityState == SES_RETURNING )
4413 {//see if we're close enough to pick it up
4414 if ( VectorLengthSquared( saberDiff ) <= 256 )//16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )//
4415 {//caught it
4416 vec3_t axisPoint;
4417 trace_t trace;
4418 VectorCopy( self->currentOrigin, axisPoint );
4419 axisPoint[2] = self->client->renderInfo.handRPoint[2];
4420 gi.trace( &trace, axisPoint, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4421 if ( !trace.startsolid && trace.fraction >= 1.0f )
4422 {//our hand isn't through a wall
4423 WP_SaberCatch( self, saberent, qtrue );
4424 NPC_SetAnim( self, SETANIM_TORSO, TORSO_HANDRETRACT1, SETANIM_FLAG_OVERRIDE );
4425 }
4426 return;
4427 }
4428 }
4429
4430 if ( saberent->s.pos.trType != TR_STATIONARY )
4431 {//saber is in flight, lerp it
4432 WP_RunSaber( self, saberent );
4433 }
4434 else
4435 {//it fell on the ground
4436 if ( self->health <= 0 && level.time > saberent->s.time + 5000 )
4437 {//make us free ourselves after a time
4438 G_FreeEntity( saberent );
4439 self->client->ps.saberEntityNum = ENTITYNUM_NONE;
4440 return;
4441 }
4442 if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000)
4443 || (self->s.number && level.time - saberent->aimDebounceTime > 5000) )
4444 {//(only for player) been missing for 15 seconds, automagicially return
4445 WP_SaberCatch( self, saberent, qfalse );
4446 return;
4447 }
4448 }
4449 }
4450
4451 //are we still trying to use the saber?
4452 if ( self->client->ps.weapon != WP_SABER )
4453 {//switched away
4454 if ( !self->client->ps.saberInFlight )
4455 {//wasn't throwing saber
4456 return;
4457 }
4458 else if ( saberent->s.pos.trType == TR_LINEAR )
4459 {//switched away while controlling it, just drop the saber
4460 WP_SaberDrop( self, saberent );
4461 return;
4462 }
4463 else
4464 {//it's on the ground, see if it's inside us (touching)
4465 if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
4466 {//it's in us, pick it up automatically
4467 WP_SaberPull( self, saberent );
4468 }
4469 }
4470 }
4471 else if ( saberent->s.pos.trType != TR_LINEAR )
4472 {//weapon is saber and not flying
4473 if ( self->client->ps.saberInFlight )
4474 {//we dropped it
4475 if ( ucmd->buttons & BUTTON_ATTACK )//|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK ||
4476 {//we actively want to pick it up or we just switched to it, so pull it back
4477 gi.trace( &tr, saberent->currentOrigin, saberent->mins, saberent->maxs, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID, G2_NOCOLLIDE, 0 );
4478
4479 if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0f )
4480 {//can't pick it up yet, no LOS
4481 return;
4482 }
4483 //clear LOS, pick it up
4484 WP_SaberPull( self, saberent );
4485 }
4486 else
4487 {//see if it's inside us (touching)
4488 if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) )
4489 {//it's in us, pick it up automatically
4490 WP_SaberPull( self, saberent );
4491 }
4492 }
4493 }
4494 }
4495 else if ( self->health <= 0 && self->client->ps.saberInFlight )
4496 {//we died, drop it
4497 WP_SaberDrop( self, saberent );
4498 return;
4499 }
4500 else if ( !self->client->ps.saberActive && self->client->ps.saberEntityState != SES_RETURNING )
4501 {//we turned it off, drop it
4502 WP_SaberDrop( self, saberent );
4503 return;
4504 }
4505
4506 //TODO: if deactivate saber in flight, should it drop?
4507
4508 if ( saberent->s.pos.trType != TR_LINEAR )
4509 {//don't home
4510 return;
4511 }
4512
4513 float saberDist = VectorLength( saberDiff );
4514 if ( self->client->ps.saberEntityState == SES_LEAVING )
4515 {//saber still flying forward
4516 if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 )
4517 {//still holding it out
4518 if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
4519 {//done throwing, return to me
4520 if ( self->client->ps.saberActive )
4521 {//still on
4522 WP_SaberReturn( self, saberent );
4523 }
4524 }
4525 else if ( level.time - self->client->ps.saberThrowTime >= 100 )
4526 {
4527 if ( WP_ForcePowerAvailable( self, FP_SABERTHROW, 1 ) )
4528 {
4529 WP_ForcePowerDrain( self, FP_SABERTHROW, 1 );
4530 self->client->ps.saberThrowTime = level.time;
4531 }
4532 else
4533 {//out of force power, return to me
4534 WP_SaberReturn( self, saberent );
4535 }
4536 }
4537 }
4538 else
4539 {
4540 if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time )
4541 {//not holding button and has been out at least 1 second, return to me
4542 if ( self->client->ps.saberActive )
4543 {//still on
4544 WP_SaberReturn( self, saberent );
4545 }
4546 }
4547 else if ( level.time - self->client->ps.saberThrowTime > 3000
4548 || (self->client->ps.forcePowerLevel[FP_SABERTHROW]==FORCE_LEVEL_1&&saberDist>=self->client->ps.saberEntityDist) )
4549 {//been out too long, or saber throw 1 went too far, return to me
4550 if ( self->client->ps.saberActive )
4551 {//still on
4552 WP_SaberReturn( self, saberent );
4553 }
4554 }
4555 }
4556 }
4557 if ( self->client->ps.saberEntityState == SES_RETURNING )
4558 {
4559 if ( self->client->ps.saberEntityDist > 0 )
4560 {
4561 self->client->ps.saberEntityDist -= 25;
4562 }
4563 if ( self->client->ps.saberEntityDist < 0 )
4564 {
4565 self->client->ps.saberEntityDist = 0;
4566 }
4567 else if ( saberDist < self->client->ps.saberEntityDist )
4568 {//if it's coming back to me, never push it away
4569 self->client->ps.saberEntityDist = saberDist;
4570 }
4571 }
4572 }
4573
4574
4575 //SABER BLOCKING============================================================================
4576 //SABER BLOCKING============================================================================
4577 //SABER BLOCKING============================================================================
4578 //SABER BLOCKING============================================================================
4579 //SABER BLOCKING============================================================================
WP_MissileBlockForBlock(int saberBlock)4580 int WP_MissileBlockForBlock( int saberBlock )
4581 {
4582 switch( saberBlock )
4583 {
4584 case BLOCKED_UPPER_RIGHT:
4585 return BLOCKED_UPPER_RIGHT_PROJ;
4586 break;
4587 case BLOCKED_UPPER_LEFT:
4588 return BLOCKED_UPPER_LEFT_PROJ;
4589 break;
4590 case BLOCKED_LOWER_RIGHT:
4591 return BLOCKED_LOWER_RIGHT_PROJ;
4592 break;
4593 case BLOCKED_LOWER_LEFT:
4594 return BLOCKED_LOWER_LEFT_PROJ;
4595 break;
4596 case BLOCKED_TOP:
4597 return BLOCKED_TOP_PROJ;
4598 break;
4599 }
4600 return saberBlock;
4601 }
4602
WP_SaberBlockNonRandom(gentity_t * self,vec3_t hitloc,qboolean missileBlock)4603 void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock )
4604 {
4605 vec3_t diff, fwdangles={0,0,0}, right;
4606 float rightdot;
4607 float zdiff;
4608
4609 if ( self->client->ps.weaponstate == WEAPON_DROPPING ||
4610 self->client->ps.weaponstate == WEAPON_RAISING )
4611 {//don't block while changing weapons
4612 return;
4613 }
4614 //NPCs don't auto-block
4615 if ( !missileBlock && self->s.number != 0 && self->client->ps.saberBlocked != BLOCKED_NONE )
4616 {
4617 return;
4618 }
4619
4620 VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff );
4621 diff[2] = 0;
4622 VectorNormalize( diff );
4623
4624 fwdangles[1] = self->client->ps.viewangles[1];
4625 // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
4626 AngleVectors( fwdangles, NULL, right, NULL );
4627
4628 rightdot = DotProduct(right, diff);
4629 zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];
4630
4631 //FIXME: take torsoAngles into account?
4632 if ( zdiff > -5 )//0 )//40 )
4633 {
4634 if ( rightdot > 0.3 )
4635 {
4636 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
4637 }
4638 else if ( rightdot < -0.3 )
4639 {
4640 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4641 }
4642 else
4643 {
4644 self->client->ps.saberBlocked = BLOCKED_TOP;
4645 }
4646 }
4647 else if ( zdiff > -22 )//-20 )//20 )
4648 {
4649 if ( zdiff < -10 )//30 )
4650 {//hmm, pretty low, but not low enough to use the low block, so we need to duck
4651 //NPC should duck, but NPC should never get here
4652 }
4653 if ( rightdot > 0.1 )
4654 {
4655 self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
4656 }
4657 else if ( rightdot < -0.1 )
4658 {
4659 self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4660 }
4661 else
4662 {//FIXME: this looks really weird if the shot is too low!
4663 self->client->ps.saberBlocked = BLOCKED_TOP;
4664 }
4665 }
4666 else
4667 {
4668 if ( rightdot >= 0 )
4669 {
4670 self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
4671 }
4672 else
4673 {
4674 self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
4675 }
4676 }
4677
4678 #ifndef FINAL_BUILD
4679 if ( d_saberCombat->integer )
4680 {
4681 if ( !self->s.number )
4682 {
4683 gi.Printf( "EyeZ: %4.2f HitZ: %4.2f zdiff: %4.2f rdot: %4.2f\n", self->client->renderInfo.eyePoint[2], hitloc[2], zdiff, rightdot );
4684 switch ( self->client->ps.saberBlocked )
4685 {
4686 case BLOCKED_TOP:
4687 gi.Printf( "BLOCKED_TOP\n" );
4688 break;
4689 case BLOCKED_UPPER_RIGHT:
4690 gi.Printf( "BLOCKED_UPPER_RIGHT\n" );
4691 break;
4692 case BLOCKED_UPPER_LEFT:
4693 gi.Printf( "BLOCKED_UPPER_LEFT\n" );
4694 break;
4695 case BLOCKED_LOWER_RIGHT:
4696 gi.Printf( "BLOCKED_LOWER_RIGHT\n" );
4697 break;
4698 case BLOCKED_LOWER_LEFT:
4699 gi.Printf( "BLOCKED_LOWER_LEFT\n" );
4700 break;
4701 default:
4702 break;
4703 }
4704 }
4705 }
4706 #endif
4707
4708 if ( missileBlock )
4709 {
4710 self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked );
4711 }
4712
4713 if ( self->client->ps.saberBlocked != BLOCKED_NONE )
4714 {
4715 int parryReCalcTime = Jedi_ReCalcParryTime( self, EVASION_PARRY );
4716 if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime )
4717 {
4718 self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime;
4719 }
4720 }
4721 }
4722
WP_SaberBlock(gentity_t * saber,vec3_t hitloc,qboolean missileBlock)4723 void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missileBlock )
4724 {
4725 gentity_t *playerent;
4726 vec3_t diff, fwdangles={0,0,0}, right;
4727 float rightdot;
4728 float zdiff;
4729
4730 if (saber && saber->owner)
4731 {
4732 playerent = saber->owner;
4733 if (!playerent->client)
4734 {
4735 return;
4736 }
4737 if ( playerent->client->ps.weaponstate == WEAPON_DROPPING ||
4738 playerent->client->ps.weaponstate == WEAPON_RAISING )
4739 {//don't block while changing weapons
4740 return;
4741 }
4742 }
4743 else
4744 { // Bad entity passed.
4745 return;
4746 }
4747
4748 //temporarily disabling auto-blocking for NPCs...
4749 if ( !missileBlock && playerent->s.number != 0 && playerent->client->ps.saberBlocked != BLOCKED_NONE )
4750 {
4751 return;
4752 }
4753
4754 VectorSubtract(hitloc, playerent->currentOrigin, diff);
4755 VectorNormalize(diff);
4756
4757 fwdangles[1] = playerent->client->ps.viewangles[1];
4758 // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
4759 AngleVectors( fwdangles, NULL, right, NULL );
4760
4761 rightdot = DotProduct(right, diff) + Q_flrand(-0.2f,0.2f);
4762 zdiff = hitloc[2] - playerent->currentOrigin[2] + Q_irand(-8,8);
4763
4764 // Figure out what quadrant the block was in.
4765 if (zdiff > 24)
4766 { // Attack from above
4767 if (Q_irand(0,1))
4768 {
4769 playerent->client->ps.saberBlocked = BLOCKED_TOP;
4770 }
4771 else
4772 {
4773 playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4774 }
4775 }
4776 else if (zdiff > 13)
4777 { // The upper half has three viable blocks...
4778 if (rightdot > 0.25)
4779 { // In the right quadrant...
4780 if (Q_irand(0,1))
4781 {
4782 playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT;
4783 }
4784 else
4785 {
4786 playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
4787 }
4788 }
4789 else
4790 {
4791 switch(Q_irand(0,3))
4792 {
4793 case 0:
4794 playerent->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT;
4795 break;
4796 case 1:
4797 case 2:
4798 playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
4799 break;
4800 case 3:
4801 playerent->client->ps.saberBlocked = BLOCKED_TOP;
4802 break;
4803 }
4804 }
4805 }
4806 else
4807 { // The lower half is a bit iffy as far as block coverage. Pick one of the "low" ones at random.
4808 if (Q_irand(0,1))
4809 {
4810 playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT;
4811 }
4812 else
4813 {
4814 playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT;
4815 }
4816 }
4817
4818 if ( missileBlock )
4819 {
4820 playerent->client->ps.saberBlocked = WP_MissileBlockForBlock( playerent->client->ps.saberBlocked );
4821 }
4822 }
4823
WP_SaberStartMissileBlockCheck(gentity_t * self,usercmd_t * ucmd)4824 void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd )
4825 {
4826 float dist;
4827 gentity_t *ent, *incoming = NULL;
4828 gentity_t *entityList[MAX_GENTITIES];
4829 int numListedEntities;
4830 vec3_t mins, maxs;
4831 int i, e;
4832 float closestDist, radius = 256;
4833 vec3_t forward, dir, missile_dir, fwdangles = {0};
4834 trace_t trace;
4835 vec3_t traceTo, entDir;
4836
4837
4838 if ( self->client->ps.weapon != WP_SABER )
4839 {
4840 return;
4841 }
4842
4843 if ( self->client->ps.saberInFlight )
4844 {
4845 return;
4846 }
4847
4848 if ( self->client->ps.forcePowersActive&(1<<FP_LIGHTNING) )
4849 {//can't block while zapping
4850 return;
4851 }
4852
4853 if ( self->client->ps.forcePowersActive&(1<<FP_PUSH) )
4854 {//can't block while shoving
4855 return;
4856 }
4857
4858 if ( self->client->ps.forcePowersActive&(1<<FP_GRIP) )
4859 {//can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
4860 return;
4861 }
4862
4863 if ( self->health <= 0 )
4864 {//dead don't try to block (NOTE: actual deflection happens in missile code)
4865 return;
4866 }
4867
4868 if ( PM_InKnockDown( &self->client->ps ) )
4869 {//can't block when knocked down
4870 return;
4871 }
4872
4873 if ( !self->client->ps.saberLength )
4874 {
4875 if ( self->s.number == 0 )
4876 {//player doesn't auto-activate
4877 return;
4878 }
4879 }
4880
4881 if ( !self->s.number )
4882 {//don't do this if already attacking!
4883 if ( ucmd->buttons & BUTTON_ATTACK
4884 || PM_SaberInAttack( self->client->ps.saberMove )
4885 || PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
4886 || PM_SaberInTransitionAny( self->client->ps.saberMove ))
4887 {
4888 return;
4889 }
4890 }
4891
4892 if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time )
4893 {//can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...)
4894 return;
4895 }
4896
4897 if ( !self->s.number && !g_saberAutoBlocking->integer && self->client->ps.saberBlockingTime<level.time )
4898 {
4899 return;
4900 }
4901
4902 fwdangles[1] = self->client->ps.viewangles[1];
4903 AngleVectors( fwdangles, forward, NULL, NULL );
4904
4905 for ( i = 0 ; i < 3 ; i++ )
4906 {
4907 mins[i] = self->currentOrigin[i] - radius;
4908 maxs[i] = self->currentOrigin[i] + radius;
4909 }
4910
4911 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
4912
4913 closestDist = radius;
4914
4915 for ( e = 0 ; e < numListedEntities ; e++ )
4916 {
4917 ent = entityList[ e ];
4918
4919 if (ent == self)
4920 continue;
4921 if (ent->owner == self)
4922 continue;
4923 if ( !(ent->inuse) )
4924 continue;
4925 if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) )
4926 {//not a normal projectile
4927 if ( ent->client || ent->s.weapon != WP_SABER )
4928 {//FIXME: wake up bad guys?
4929 continue;
4930 }
4931 if ( ent->s.eFlags & EF_NODRAW )
4932 {
4933 continue;
4934 }
4935 if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
4936 {//not a lightsaber
4937 //FIXME: what about general objects that are small in size- like rocks, etc...
4938 continue;
4939 }
4940 //a lightsaber.. make sure it's on and inFlight
4941 if ( !ent->owner || !ent->owner->client )
4942 {
4943 continue;
4944 }
4945 if ( !ent->owner->client->ps.saberInFlight )
4946 {//not in flight
4947 continue;
4948 }
4949 if ( ent->owner->client->ps.saberLength <= 0 )
4950 {//not on
4951 continue;
4952 }
4953 if ( ent->owner->health <= 0 && !g_saberRealisticCombat->integer )
4954 {//it's not doing damage, so ignore it
4955 continue;
4956 }
4957 }
4958 else
4959 {
4960 if ( ent->s.pos.trType == TR_STATIONARY && !self->s.number )
4961 {//nothing you can do with a stationary missile if you're the player
4962 continue;
4963 }
4964 }
4965
4966 float dot1, dot2;
4967 //see if they're in front of me
4968 VectorSubtract( ent->currentOrigin, self->currentOrigin, dir );
4969 dist = VectorNormalize( dir );
4970 //FIXME: handle detpacks, proximity mines and tripmines
4971 if ( ent->s.weapon == WP_THERMAL )
4972 {//thermal detonator!
4973 if ( self->NPC && dist < ent->splashRadius )
4974 {
4975 if ( dist < ent->splashRadius &&
4976 ent->nextthink < level.time + 600 &&
4977 ent->count &&
4978 self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
4979 (ent->s.pos.trType == TR_STATIONARY||
4980 ent->s.pos.trType == TR_INTERPOLATE||
4981 (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE||
4982 !WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
4983 {//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!
4984 //FIXME: sometimes this might make me just jump into it...?
4985 self->client->ps.forceJumpCharge = 480;
4986 }
4987 else
4988 {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
4989 ForceThrow( self, qfalse );
4990 }
4991 }
4992 continue;
4993 }
4994 else if ( ent->splashDamage && ent->splashRadius )
4995 {//exploding missile
4996 //FIXME: handle tripmines and detpacks somehow...
4997 // maybe do a force-gesture that makes them explode?
4998 // But what if we're within it's splashradius?
4999 if ( !self->s.number )
5000 {//players don't auto-handle these at all
5001 continue;
5002 }
5003 else
5004 {
5005 if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
5006 {//a placed explosive like a tripmine or detpack
5007 if ( InFOV( ent->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) )
5008 {//in front of me
5009 if ( G_ClearLOS( self, ent ) )
5010 {//can see it
5011 vec3_t throwDir;
5012 //make the gesture
5013 ForceThrow( self, qfalse );
5014 //take it off the wall and toss it
5015 ent->s.pos.trType = TR_GRAVITY;
5016 ent->s.eType = ET_MISSILE;
5017 ent->s.eFlags &= ~EF_MISSILE_STICK;
5018 ent->s.eFlags |= EF_BOUNCE_HALF;
5019 AngleVectors( ent->currentAngles, throwDir, NULL, NULL );
5020 VectorMA( ent->currentOrigin, ent->maxs[0]+4, throwDir, ent->currentOrigin );
5021 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
5022 VectorScale( throwDir, 300, ent->s.pos.trDelta );
5023 ent->s.pos.trDelta[2] += 150;
5024 VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta );
5025 ent->s.pos.trTime = level.time; // move a bit on the very first frame
5026 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
5027 ent->owner = self;
5028 // make it explode, but with less damage
5029 ent->splashDamage /= 3;
5030 ent->splashRadius /= 3;
5031 ent->e_ThinkFunc = thinkF_WP_Explode;
5032 ent->nextthink = level.time + Q_irand( 500, 3000 );
5033 }
5034 }
5035 }
5036 else if ( dist < ent->splashRadius &&
5037 self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
5038 (DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE||
5039 !WP_ForcePowerUsable( self, FP_PUSH, 0 )) )
5040 {//NPCs try to evade it
5041 self->client->ps.forceJumpCharge = 480;
5042 }
5043 else
5044 {//else, try to force-throw it away
5045 //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]]
5046 ForceThrow( self, qfalse );
5047 }
5048 }
5049 //otherwise, can't block it, so we're screwed
5050 continue;
5051 }
5052
5053 if ( ent->s.weapon != WP_SABER )
5054 {//only block shots coming from behind
5055 if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE )
5056 continue;
5057 }
5058 else if ( !self->s.number )
5059 {//player never auto-blocks thrown sabers
5060 continue;
5061 }//NPCs always try to block sabers coming from behind!
5062
5063 //see if they're heading towards me
5064 VectorCopy( ent->s.pos.trDelta, missile_dir );
5065 VectorNormalize( missile_dir );
5066 if ( (dot2 = DotProduct( dir, missile_dir )) > 0 )
5067 continue;
5068
5069 //FIXME: must have a clear trace to me, too...
5070 if ( dist < closestDist )
5071 {
5072 VectorCopy( self->currentOrigin, traceTo );
5073 traceTo[2] = self->absmax[2] - 4;
5074 gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, G2_NOCOLLIDE, 0 );
5075 if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
5076 {//okay, try one more check
5077 VectorNormalize2( ent->s.pos.trDelta, entDir );
5078 VectorMA( ent->currentOrigin, radius, entDir, traceTo );
5079 gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask, G2_NOCOLLIDE, 0 );
5080 if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) )
5081 {//can't hit me, ignore it
5082 continue;
5083 }
5084 }
5085 if ( self->s.number != 0 )
5086 {//An NPC
5087 if ( self->NPC && !self->enemy && ent->owner )
5088 {
5089 if ( ent->owner->health >= 0 && (!ent->owner->client || ent->owner->client->playerTeam != self->client->playerTeam) )
5090 {
5091 G_SetEnemy( self, ent->owner );
5092 }
5093 }
5094 }
5095 //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?
5096 closestDist = dist;
5097 incoming = ent;
5098 }
5099 }
5100
5101 if ( incoming )
5102 {
5103 if ( self->NPC && !G_ControlledByPlayer( self ) )
5104 {
5105 if ( Jedi_WaitingAmbush( self ) )
5106 {
5107 Jedi_Ambush( self );
5108 }
5109 if ( Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming ) != EVASION_NONE )
5110 {//make sure to turn on your saber if it's not on
5111 self->client->ps.saberActive = qtrue;
5112 }
5113 }
5114 else//player
5115 {
5116 WP_SaberBlockNonRandom( self, incoming->currentOrigin, qtrue );
5117 if ( incoming->owner && incoming->owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
5118 {
5119 self->enemy = incoming->owner;
5120 NPC_SetLookTarget( self, incoming->owner->s.number, level.time+1000 );
5121 }
5122 }
5123 }
5124 }
5125
5126
5127 //GENERAL SABER============================================================================
5128 //GENERAL SABER============================================================================
5129 //GENERAL SABER============================================================================
5130 //GENERAL SABER============================================================================
5131 //GENERAL SABER============================================================================
5132
5133
WP_SetSaberMove(gentity_t * self,short blocked)5134 void WP_SetSaberMove(gentity_t *self, short blocked)
5135 {
5136 self->client->ps.saberBlocked = blocked;
5137 }
5138
5139 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)5140 void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd )
5141 {
5142 //float swap;
5143 float minsize = 16;
5144
5145 if(0)// if ( self->s.number != 0 )
5146 {//for now only the player can do this // not anymore
5147 return;
5148 }
5149
5150 if ( !self->client )
5151 {
5152 return;
5153 }
5154
5155 if ( self->client->ps.saberEntityNum < 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD )
5156 {//never got one
5157 return;
5158 }
5159
5160 // Check if we are throwing it, launch it if needed, update position if needed.
5161 WP_SaberThrow(self, ucmd);
5162
5163
5164 //vec3_t saberloc;
5165 //vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8};
5166
5167 gentity_t *saberent;
5168
5169 if (self->client->ps.saberEntityNum <= 0)
5170 {//WTF?!! We lost it?
5171 return;
5172 }
5173
5174 saberent = &g_entities[self->client->ps.saberEntityNum];
5175
5176 //FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller
5177 if ( self->client->ps.saberBlocked != BLOCKED_NONE )
5178 {//we're blocking, increase min size
5179 minsize = 32;
5180 }
5181
5182 //is our saber in flight?
5183 if ( !self->client->ps.saberInFlight )
5184 { // It isn't, which means we can update its position as we will.
5185 if ( !self->client->ps.saberActive || PM_InKnockDown( &self->client->ps ) )
5186 {//can't block if saber isn't on
5187 VectorClear(saberent->mins);
5188 VectorClear(saberent->maxs);
5189 G_SetOrigin(saberent, self->currentOrigin);
5190 }
5191 else if ( self->client->ps.saberBlocking == BLK_TIGHT || self->client->ps.saberBlocking == BLK_WIDE )
5192 {//FIXME: keep bbox in front of player, even when wide?
5193 vec3_t saberOrg;
5194 if ( ( (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)) )
5195 && self->client->ps.weaponTime <= 0 )
5196 {//full-size blocking for non-attacking player with g_saberAutoBlocking on
5197 vec3_t saberang={0,0,0}, fwd, sabermins={-8,-8,-8}, sabermaxs={8,8,8};
5198
5199 saberang[YAW] = self->client->ps.viewangles[YAW];
5200 AngleVectors( saberang, fwd, NULL, NULL );
5201
5202 VectorMA( self->currentOrigin, 12, fwd, saberOrg );
5203
5204 VectorAdd( self->mins, sabermins, saberent->mins );
5205 VectorAdd( self->maxs, sabermaxs, saberent->maxs );
5206
5207 saberent->contents = CONTENTS_LIGHTSABER;
5208
5209 G_SetOrigin( saberent, saberOrg );
5210 }
5211 else
5212 {
5213 vec3_t saberTip;
5214 VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, saberTip );
5215 VectorMA( self->client->renderInfo.muzzlePoint, self->client->ps.saberLength*0.5, self->client->renderInfo.muzzleDir, saberOrg );
5216 for ( int i = 0; i < 3; i++ )
5217 {
5218 if ( saberTip[i] > self->client->renderInfo.muzzlePoint[i] )
5219 {
5220 saberent->maxs[i] = saberTip[i] - saberOrg[i] + 8;//self->client->renderInfo.muzzlePoint[i];
5221 saberent->mins[i] = self->client->renderInfo.muzzlePoint[i] - saberOrg[i] - 8;
5222 }
5223 else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] )
5224 {
5225 saberent->maxs[i] = self->client->renderInfo.muzzlePoint[i] - saberOrg[i] + 8;
5226 saberent->mins[i] = saberTip[i] - saberOrg[i] - 8;//self->client->renderInfo.muzzlePoint[i];
5227 }
5228 if ( self->client->ps.weaponTime > 0 || self->s.number || g_saberAutoBlocking->integer || self->client->ps.saberBlockingTime > level.time )
5229 {//if attacking or blocking (or an NPC), inflate to a minimum size
5230 if ( saberent->maxs[i] < minsize )
5231 {
5232 saberent->maxs[i] = minsize;
5233 }
5234 if ( saberent->mins[i] > -minsize )
5235 {
5236 saberent->mins[i] = -minsize;
5237 }
5238 }
5239 }
5240 saberent->contents = CONTENTS_LIGHTSABER;
5241 G_SetOrigin( saberent, saberOrg );
5242 }
5243 }
5244 /*
5245 else if (self->client->ps.saberBlocking == BLK_WIDE)
5246 { // Assuming that we are not swinging, the saber's bounding box should be around the player.
5247 vec3_t saberang={0,0,0}, fwd;
5248
5249 saberang[YAW] = self->client->ps.viewangles[YAW];
5250 AngleVectors( saberang, fwd, NULL, NULL );
5251
5252 VectorMA(self->currentOrigin, 12, fwd, saberloc);
5253
5254 VectorAdd(self->mins, sabermins, saberent->mins);
5255 VectorAdd(self->maxs, sabermaxs, saberent->maxs);
5256
5257 saberent->contents = CONTENTS_LIGHTSABER;
5258
5259 G_SetOrigin( saberent, saberloc);
5260 }
5261 else if (self->client->ps.saberBlocking == BLK_TIGHT)
5262 { // If the player is swinging, the bbox is around just the saber
5263 VectorCopy(self->client->renderInfo.muzzlePoint, sabermins);
5264 // Put the limits of the bbox around the saber size.
5265 VectorMA(sabermins, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, sabermaxs);
5266
5267 // Now make the mins into mins and the maxs into maxs
5268 if (sabermins[0] > sabermaxs[0])
5269 {
5270 swap = sabermins[0];
5271 sabermins[0] = sabermaxs[0];
5272 sabermaxs[0] = swap;
5273 }
5274 if (sabermins[1] > sabermaxs[1])
5275 {
5276 swap = sabermins[1];
5277 sabermins[1] = sabermaxs[1];
5278 sabermaxs[1] = swap;
5279 }
5280 if (sabermins[2] > sabermaxs[2])
5281 {
5282 swap = sabermins[2];
5283 sabermins[2] = sabermaxs[2];
5284 sabermaxs[2] = swap;
5285 }
5286
5287 // Now the loc is halfway between the (absolute) mins and maxs
5288 VectorAdd(sabermins, sabermaxs, saberloc);
5289 VectorScale(saberloc, 0.5, saberloc);
5290
5291 // Finally, turn the mins and maxs, which are absolute, into relative mins and maxs.
5292 VectorSubtract(sabermins, saberloc, saberent->mins);
5293 VectorSubtract(sabermaxs, saberloc, saberent->maxs);
5294
5295 saberent->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP;
5296
5297 G_SetOrigin( saberent, saberloc);
5298 }
5299 */
5300 else
5301 { // Otherwise there is no blocking possible.
5302 VectorClear(saberent->mins);
5303 VectorClear(saberent->maxs);
5304 G_SetOrigin(saberent, self->currentOrigin);
5305 }
5306 saberent->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
5307 gi.linkentity(saberent);
5308 }
5309 else
5310 {
5311 WP_SaberInFlightReflectCheck( self, ucmd );
5312 }
5313 #ifndef FINAL_BUILD
5314 if ( d_saberCombat->integer > 2 )
5315 {
5316 CG_CubeOutline( saberent->absmin, saberent->absmax, 50, WPDEBUG_SaberColor( self->client->ps.saberColor ), 1 );
5317 }
5318 #endif
5319 }
5320
5321
5322 //OTHER JEDI POWERS=========================================================================
5323 //OTHER JEDI POWERS=========================================================================
5324 //OTHER JEDI POWERS=========================================================================
5325 //OTHER JEDI POWERS=========================================================================
5326 //OTHER JEDI POWERS=========================================================================
5327 extern gentity_t *TossClientItems( gentity_t *self );
5328 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
WP_DropWeapon(gentity_t * dropper,vec3_t velocity)5329 void WP_DropWeapon( gentity_t *dropper, vec3_t velocity )
5330 {
5331 if ( !dropper || !dropper->client )
5332 {
5333 return;
5334 }
5335 int replaceWeap = WP_NONE;
5336 int oldWeap = dropper->s.weapon;
5337 gentity_t *weapon = TossClientItems( dropper );
5338 if ( oldWeap == WP_THERMAL && dropper->NPC )
5339 {//Hmm, maybe all NPCs should go into melee? Not too many, though, or they mob you and look silly
5340 replaceWeap = WP_MELEE;
5341 }
5342 if (dropper->ghoul2.IsValid()&& dropper->weaponModel >= 0 )
5343 {
5344 gi.G2API_RemoveGhoul2Model( dropper->ghoul2, dropper->weaponModel );
5345 dropper->weaponModel = -1;
5346 }
5347 //FIXME: does this work on the player?
5348 dropper->client->ps.stats[STAT_WEAPONS] |= ( 1 << replaceWeap );
5349 if ( !dropper->s.number )
5350 {
5351 if ( oldWeap == WP_THERMAL )
5352 {
5353 dropper->client->ps.ammo[weaponData[oldWeap].ammoIndex] -= weaponData[oldWeap].energyPerShot;
5354 }
5355 else
5356 {
5357 dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
5358 }
5359 CG_ChangeWeapon( replaceWeap );
5360 }
5361 else
5362 {
5363 dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap );
5364 }
5365 ChangeWeapon( dropper, replaceWeap );
5366 dropper->s.weapon = replaceWeap;
5367 if ( dropper->NPC )
5368 {
5369 dropper->NPC->last_ucmd.weapon = replaceWeap;
5370 }
5371 if ( weapon != NULL && velocity && !VectorCompare( velocity, vec3_origin ) )
5372 {//weapon should have a direction to it's throw
5373 VectorScale( velocity, 3, weapon->s.pos.trDelta );//NOTE: Presumes it is moving already...?
5374 if ( weapon->s.pos.trDelta[2] < 150 )
5375 {//this is presuming you don't want them to drop the weapon down on you...
5376 weapon->s.pos.trDelta[2] = 150;
5377 }
5378 //FIXME: gets stuck inside it's former owner...
5379 weapon->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5380 }
5381 }
5382
WP_KnockdownTurret(gentity_t * self,gentity_t * pas)5383 void WP_KnockdownTurret( gentity_t *self, gentity_t *pas )
5384 {
5385 //knock it over
5386 VectorCopy( pas->currentOrigin, pas->s.pos.trBase );
5387 pas->s.pos.trType = TR_LINEAR_STOP;
5388 pas->s.pos.trDuration = 250;
5389 pas->s.pos.trTime = level.time;
5390 pas->s.pos.trDelta[2] = ( 12.0f / ( pas->s.pos.trDuration * 0.001f ) );
5391
5392 VectorCopy( pas->currentAngles, pas->s.apos.trBase );
5393 pas->s.apos.trType = TR_LINEAR_STOP;
5394 pas->s.apos.trDuration = 250;
5395 pas->s.apos.trTime = level.time;
5396 //FIXME: pick pitch/roll that always tilts it directly away from pusher
5397 pas->s.apos.trDelta[PITCH] = ( 100.0f / ( pas->s.apos.trDuration * 0.001f ) );
5398
5399 //kill it
5400 pas->count = 0;
5401 pas->nextthink = -1;
5402 G_Sound( pas, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
5403 //push effect?
5404 pas->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5405 }
5406
WP_ResistForcePush(gentity_t * self,gentity_t * pusher,qboolean noPenalty)5407 void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty )
5408 {
5409 int parts;
5410 qboolean runningResist = qfalse;
5411
5412 if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client )
5413 {
5414 return;
5415 }
5416 if ( (!self->s.number || self->client->NPC_class == CLASS_DESANN || self->client->NPC_class == CLASS_LUKE)
5417 && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) )
5418 {
5419 runningResist = qtrue;
5420 }
5421 if ( !runningResist
5422 && self->client->ps.groundEntityNum != ENTITYNUM_NONE
5423 && !PM_SpinningSaberAnim( self->client->ps.legsAnim )
5424 && !PM_FlippingAnim( self->client->ps.legsAnim )
5425 && !PM_RollingAnim( self->client->ps.legsAnim )
5426 && !PM_InKnockDown( &self->client->ps )
5427 && !PM_CrouchAnim( self->client->ps.legsAnim ))
5428 {//if on a surface and not in a spin or flip, play full body resist
5429 parts = SETANIM_BOTH;
5430 }
5431 else
5432 {//play resist just in torso
5433 parts = SETANIM_TORSO;
5434 }
5435 NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5436 if ( !noPenalty )
5437 {
5438 if ( !runningResist )
5439 {
5440 VectorClear( self->client->ps.velocity );
5441 //still stop them from attacking or moving for a bit, though
5442 //FIXME: maybe push just a little (like, slide)?
5443 self->client->ps.weaponTime = 1000;
5444 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
5445 {
5446 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
5447 }
5448 self->client->ps.pm_time = self->client->ps.weaponTime;
5449 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
5450 //play the full body push effect on me
5451 self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5452 }
5453 else
5454 {
5455 self->client->ps.weaponTime = 600;
5456 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
5457 {
5458 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
5459 }
5460 }
5461 }
5462 //play my force push effect on my hand
5463 self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
5464 Jedi_PlayBlockedPushSound( self );
5465 }
5466
WP_ForceKnockdown(gentity_t * self,gentity_t * pusher,qboolean pull,qboolean strongKnockdown,qboolean breakSaberLock)5467 void WP_ForceKnockdown( gentity_t *self, gentity_t *pusher, qboolean pull, qboolean strongKnockdown, qboolean breakSaberLock )
5468 {
5469 if ( !self || !self->client || !pusher || !pusher->client )
5470 {
5471 return;
5472 }
5473
5474 //break out of a saberLock?
5475 if ( breakSaberLock )
5476 {
5477 self->client->ps.saberLockTime = 0;
5478 self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
5479 }
5480
5481 if ( self->health > 0 )
5482 {
5483 if ( !self->s.number )
5484 {
5485 NPC_SetPainEvent( self );
5486 }
5487 else
5488 {
5489 GEntity_PainFunc( self, pusher, pusher, self->currentOrigin, 0, MOD_MELEE );
5490 }
5491 vec3_t pushDir;
5492 if ( pull )
5493 {
5494 VectorSubtract( pusher->currentOrigin, self->currentOrigin, pushDir );
5495 }
5496 else
5497 {
5498 VectorSubtract( self->currentOrigin, pusher->currentOrigin, pushDir );
5499 }
5500 G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );
5501
5502 if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
5503 && !PM_FlippingAnim( self->client->ps.legsAnim )
5504 && !PM_RollingAnim( self->client->ps.legsAnim )
5505 && !PM_InKnockDown( &self->client->ps ) )
5506 {
5507 int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
5508 if ( pusher->client->NPC_class == CLASS_DESANN && self->client->NPC_class != CLASS_LUKE )
5509 {//desann always knocks down, unless you're Luke
5510 strongKnockdown = qtrue;
5511 }
5512 if ( !self->s.number
5513 && !strongKnockdown
5514 && ( (!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)) ) )
5515 {//player only knocked down if pushed *hard*
5516 if ( self->s.weapon == WP_SABER )
5517 {//temp HACK: these are the only 2 pain anims that look good when holding a saber
5518 knockAnim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 );
5519 }
5520 else
5521 {
5522 knockAnim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN19 );
5523 }
5524 }
5525 else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
5526 {//crouched knockdown
5527 knockAnim = BOTH_KNOCKDOWN4;
5528 }
5529 else
5530 {//plain old knockdown
5531 vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
5532 vec3_t sFwd, sAngles = {0,pusher->client->ps.viewangles[YAW],0};
5533 AngleVectors( pLAngles, pLFwd, NULL, NULL );
5534 AngleVectors( sAngles, sFwd, NULL, NULL );
5535 if ( DotProduct( sFwd, pLFwd ) > 0.2f )
5536 {//pushing him from behind
5537 //FIXME: check to see if we're aiming below or above the waist?
5538 if ( pull )
5539 {
5540 knockAnim = BOTH_KNOCKDOWN1;
5541 }
5542 else
5543 {
5544 knockAnim = BOTH_KNOCKDOWN3;
5545 }
5546 }
5547 else
5548 {//pushing him from front
5549 if ( pull )
5550 {
5551 knockAnim = BOTH_KNOCKDOWN3;
5552 }
5553 else
5554 {
5555 knockAnim = BOTH_KNOCKDOWN1;
5556 }
5557 }
5558 }
5559 if ( knockAnim == BOTH_KNOCKDOWN1 && strongKnockdown )
5560 {//push *hard*
5561 knockAnim = BOTH_KNOCKDOWN2;
5562 }
5563 NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5564 if ( self->s.number )
5565 {//randomize getup times
5566 int addTime = Q_irand( -300, 1000 );
5567 self->client->ps.legsAnimTimer += addTime;
5568 self->client->ps.torsoAnimTimer += addTime;
5569 }
5570 //
5571 if ( pusher->NPC && pusher->enemy == self )
5572 {//pushed pushed down his enemy
5573 G_AddVoiceEvent( pusher, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 );
5574 pusher->NPC->blockedSpeechDebounceTime = level.time + 3000;
5575 }
5576 }
5577 }
5578 self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
5579 }
5580
ForceThrow(gentity_t * self,qboolean pull)5581 void ForceThrow( gentity_t *self, qboolean pull )
5582 {//FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent.
5583 //shove things in front of you away
5584 float dist;
5585 gentity_t *ent, *forwardEnt = NULL;
5586 gentity_t *entityList[MAX_GENTITIES];
5587 gentity_t *push_list[MAX_GENTITIES];
5588 int numListedEntities;
5589 vec3_t mins, maxs;
5590 vec3_t v;
5591 int i, e;
5592 int ent_count = 0;
5593 int radius;
5594 vec3_t center, ent_org, size, forward, right, end, dir, fwdangles = {0};
5595 float dot1, cone;
5596 trace_t tr;
5597 int anim, hold, soundIndex, cost, actualCost;
5598
5599 if ( self->health <= 0 )
5600 {
5601 return;
5602 }
5603 if ( self->client->ps.leanofs )
5604 {//can't force-throw while leaning
5605 return;
5606 }
5607 if ( self->client->ps.forcePowerDebounce[FP_PUSH] > level.time )//self->client->ps.powerups[PW_FORCE_PUSH] > level.time )
5608 {//already pushing- now you can't haul someone across the room, sorry
5609 return;
5610 }
5611 if ( !self->s.number && (cg.zoomMode || in_camera) )
5612 {//can't force throw/pull when zoomed in or in cinematic
5613 return;
5614 }
5615 if ( self->client->ps.saberLockTime > level.time )
5616 {
5617 if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
5618 {//this can be a way to break out
5619 return;
5620 }
5621 //else, I'm breaking my half of the saberlock
5622 self->client->ps.saberLockTime = 0;
5623 self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
5624 }
5625
5626 if ( self->client->ps.legsAnim == BOTH_KNOCKDOWN3
5627 || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F1 && self->client->ps.torsoAnimTimer > 400)
5628 || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F2 && self->client->ps.torsoAnimTimer > 900)
5629 || (self->client->ps.torsoAnim == BOTH_GETUP3 && self->client->ps.torsoAnimTimer > 500)
5630 || (self->client->ps.torsoAnim == BOTH_GETUP4 && self->client->ps.torsoAnimTimer > 300)
5631 || (self->client->ps.torsoAnim == BOTH_GETUP5 && self->client->ps.torsoAnimTimer > 500) )
5632 {//we're face-down, so we'd only be force-push/pulling the floor
5633 return;
5634 }
5635 if ( pull )
5636 {
5637 radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PULL]];
5638 }
5639 else
5640 {
5641 radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PUSH]];
5642 }
5643
5644 if ( !radius )
5645 {//no ability to do this yet
5646 return;
5647 }
5648
5649 if ( pull )
5650 {
5651 cost = forcePowerNeeded[FP_PULL];
5652 if ( !WP_ForcePowerUsable( self, FP_PULL, cost ) )
5653 {
5654 return;
5655 }
5656 //make sure this plays and that you cannot press fire for about 200ms after this
5657 anim = BOTH_FORCEPULL;
5658 soundIndex = G_SoundIndex( "sound/weapons/force/pull.wav" );
5659 hold = 200;
5660 }
5661 else
5662 {
5663 cost = forcePowerNeeded[FP_PUSH];
5664 if ( !WP_ForcePowerUsable( self, FP_PUSH, cost ) )
5665 {
5666 return;
5667 }
5668 //make sure this plays and that you cannot press fire for about 1 second after this
5669 anim = BOTH_FORCEPUSH;
5670 soundIndex = G_SoundIndex( "sound/weapons/force/push.wav" );
5671 hold = 650;
5672 }
5673
5674 int parts = SETANIM_TORSO;
5675 if ( !PM_InKnockDown( &self->client->ps ) )
5676 {
5677 if ( self->client->ps.saberLockTime > level.time )
5678 {
5679 self->client->ps.saberLockTime = 0;
5680 self->painDebounceTime = level.time + 2000;
5681 hold += 1000;
5682 parts = SETANIM_BOTH;
5683 }
5684 else if ( !VectorLengthSquared( self->client->ps.velocity ) && !(self->client->ps.pm_flags&PMF_DUCKED))
5685 {
5686 parts = SETANIM_BOTH;
5687 }
5688 }
5689 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART );
5690 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
5691 self->client->ps.saberBlocked = BLOCKED_NONE;
5692 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
5693 {
5694 hold = floor( hold*g_timescale->value );
5695 }
5696 self->client->ps.weaponTime = hold;//was 1000, but want to swing sooner
5697 //do effect... FIXME: build-up or delay this until in proper part of anim
5698 self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
5699
5700 G_Sound( self, soundIndex );
5701
5702 VectorCopy( self->client->ps.viewangles, fwdangles );
5703 //fwdangles[1] = self->client->ps.viewangles[1];
5704 AngleVectors( fwdangles, forward, right, NULL );
5705 VectorCopy( self->currentOrigin, center );
5706
5707 for ( i = 0 ; i < 3 ; i++ )
5708 {
5709 mins[i] = center[i] - radius;
5710 maxs[i] = center[i] + radius;
5711 }
5712
5713 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
5714
5715 if ( pull )
5716 {
5717 cone = forcePullCone[self->client->ps.forcePowerLevel[FP_PULL]];
5718 }
5719 else
5720 {
5721 cone = forcePushCone[self->client->ps.forcePowerLevel[FP_PUSH]];
5722 }
5723
5724 if ( cone >= 1.0f )
5725 {//must be pointing right at them
5726 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
5727 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, G2_NOCOLLIDE, 0 );//was MASK_SHOT, changed to match crosshair trace
5728 /*
5729 //FIXME: can't just return, need to be able to push missiles
5730 if ( tr.entityNum >= ENTITYNUM_WORLD )
5731 {//no-one right in front of self, so short out
5732 return;
5733 }
5734 */
5735 forwardEnt = &g_entities[tr.entityNum];
5736 }
5737
5738 for ( e = 0 ; e < numListedEntities ; e++ )
5739 {
5740 ent = entityList[ e ];
5741
5742 if (ent == self)
5743 continue;
5744 if ( ent->owner == self && ent->s.weapon != WP_THERMAL )//can push your own thermals
5745 continue;
5746 if ( !(ent->inuse) )
5747 continue;
5748 if ( ent->NPC && ent->NPC->scriptFlags & SCF_NO_FORCE )
5749 {
5750 if ( ent->s.weapon == WP_SABER )
5751 {//Hmm, should jedi do the resist behavior? If this is on, perhaps it's because of a cinematic?
5752 WP_ResistForcePush( ent, self, qtrue );
5753 }
5754 continue;
5755 }
5756 if ( (ent->flags&FL_FORCE_PULLABLE_ONLY) && !pull )
5757 {//simple HACK: cannot force-push ammo rack items (because they may start in solid)
5758 continue;
5759 }
5760 //FIXME: don't push it if I already pushed it a little while ago
5761 if ( ent->s.eType != ET_MISSILE )
5762 {
5763 if ( cone >= 1.0f )
5764 {//must be pointing right at them
5765 if ( ent != forwardEnt )
5766 {//must be the person I'm looking right at
5767 if ( ent->client && !pull
5768 && ent->client->ps.forceGripEntityNum == self->s.number
5769 && (self->s.eFlags&EF_FORCE_GRIPPED) )
5770 {//this is the guy that's force-gripping me, use a wider cone regardless of force power level
5771 }
5772 else
5773 {
5774 continue;
5775 }
5776 }
5777 }
5778 if ( ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )//|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items
5779 {
5780 //FIXME: need pushable objects
5781 if ( ent->s.eFlags & EF_NODRAW )
5782 {
5783 continue;
5784 }
5785 if ( !ent->client )
5786 {
5787 if ( Q_stricmp( "lightsaber", ent->classname ) != 0 )
5788 {//not a lightsaber
5789 if ( !(ent->svFlags&SVF_GLASS_BRUSH) )
5790 {//and not glass
5791 if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) )
5792 {//not a force-usable door
5793 if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) )
5794 {//not a force-usable func_static
5795 if ( Q_stricmp( "limb", ent->classname ) )
5796 {//not a limb
5797 if ( ent->s.weapon == WP_TURRET && !Q_stricmp( "PAS", ent->classname ) && ent->s.apos.trType == TR_STATIONARY )
5798 {//can knock over placed turrets
5799 if ( !self->s.number || self->enemy != ent )
5800 {//only NPCs who are actively mad at this turret can push it over
5801 continue;
5802 }
5803 }
5804 else
5805 {
5806 continue;
5807 }
5808 }
5809 }
5810 }
5811 else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 )
5812 {//not at rest
5813 continue;
5814 }
5815 }
5816 }
5817 //continue;
5818 }
5819 else if ( ent->client->NPC_class == CLASS_MARK1 )
5820 {//can't push Mark1 unless push 3
5821 if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
5822 {
5823 continue;
5824 }
5825 }
5826 else if ( ent->client->NPC_class == CLASS_GALAKMECH || ent->client->NPC_class == CLASS_ATST )
5827 {//can't push ATST or Galak
5828 continue;
5829 }
5830 else if ( ent->s.weapon == WP_EMPLACED_GUN )
5831 {//FIXME: maybe can pull them out?
5832 continue;
5833 }
5834 else if ( ent->client->playerTeam == self->client->playerTeam && self->enemy && self->enemy != ent )
5835 {//can't accidently push a teammate while in combat
5836 continue;
5837 }
5838 }
5839 else if ( ent->s.eType == ET_ITEM
5840 && ent->item
5841 && ent->item->giType == IT_HOLDABLE
5842 && ent->item->giTag == INV_SECURITY_KEY )
5843 //&& (ent->flags&FL_DROPPED_ITEM) ???
5844 {//dropped security keys can't be pushed? But placed ones can...? does this make any sense?
5845 if ( !pull || self->s.number )
5846 {//can't push, NPC's can't do anything to it
5847 continue;
5848 }
5849 else
5850 {
5851 if ( g_crosshairEntNum != ent->s.number )
5852 {//player can pull it if looking *right* at it
5853 if ( cone >= 1.0f )
5854 {//we did a forwardEnt trace
5855 if ( forwardEnt != ent )
5856 {//must be pointing right at them
5857 continue;
5858 }
5859 }
5860 else
5861 {//do a forwardEnt trace
5862 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
5863 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, G2_NOCOLLIDE, 0 );//was MASK_SHOT, changed to match crosshair trace
5864 if ( tr.entityNum != ent->s.number )
5865 {//last chance
5866 continue;
5867 }
5868 }
5869 }
5870 }
5871 }
5872 }
5873 else
5874 {
5875 if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
5876 {//can't force-push/pull stuck missiles (detpacks, tripmines)
5877 continue;
5878 }
5879 if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL )
5880 {//only thermal detonators can be pushed once stopped
5881 continue;
5882 }
5883 }
5884
5885 //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
5886 // find the distance from the edge of the bounding box
5887 for ( i = 0 ; i < 3 ; i++ )
5888 {
5889 if ( center[i] < ent->absmin[i] )
5890 {
5891 v[i] = ent->absmin[i] - center[i];
5892 } else if ( center[i] > ent->absmax[i] )
5893 {
5894 v[i] = center[i] - ent->absmax[i];
5895 } else
5896 {
5897 v[i] = 0;
5898 }
5899 }
5900
5901 VectorSubtract( ent->absmax, ent->absmin, size );
5902 VectorMA( ent->absmin, 0.5, size, ent_org );
5903
5904 //see if they're in front of me
5905 VectorSubtract( ent_org, center, dir );
5906 VectorNormalize( dir );
5907 if ( cone < 1.0f )
5908 {//must be within the forward cone
5909 if ( ent->client && !pull
5910 && ent->client->ps.forceGripEntityNum == self->s.number
5911 && self->s.eFlags&EF_FORCE_GRIPPED )
5912 {//this is the guy that's force-gripping me, use a wider cone regardless of force power level
5913 if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
5914 continue;
5915 }
5916 else if ( ent->s.eType == ET_MISSILE )//&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )
5917 {//missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects)
5918 if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f )
5919 continue;
5920 }
5921 else if ( (dot1 = DotProduct( dir, forward )) < cone )
5922 {
5923 continue;
5924 }
5925 }
5926 else if ( ent->s.eType == ET_MISSILE )
5927 {//a missile and we're at force level 1... just use a small cone, but not ridiculously small
5928 if ( (dot1 = DotProduct( dir, forward )) < 0.75f )
5929 {
5930 continue;
5931 }
5932 }//else is an NPC or brush entity that our forward trace would have to hit
5933
5934 dist = VectorLength( v );
5935
5936 //Now check and see if we can actually deflect it
5937 //method1
5938 //if within a certain range, deflect it
5939 if ( ent->s.eType == ET_MISSILE && cone >= 1.0f )
5940 {//smaller radius on missile checks at force push 1
5941 if ( dist >= 192 )
5942 {
5943 continue;
5944 }
5945 }
5946 else if ( dist >= radius )
5947 {
5948 continue;
5949 }
5950
5951 //in PVS?
5952 if ( !ent->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.eyePoint ) )
5953 {//must be in PVS
5954 continue;
5955 }
5956
5957 if ( ent != forwardEnt )
5958 {//don't need to trace against forwardEnt again
5959 //really should have a clear LOS to this thing...
5960 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_OPAQUE|CONTENTS_SOLID, G2_NOCOLLIDE, 0 );//was MASK_SHOT, but changed to match above trace and crosshair trace
5961 if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number )
5962 {//must have clear LOS
5963 continue;
5964 }
5965 }
5966
5967 // ok, we are within the radius, add us to the incoming list
5968 push_list[ent_count] = ent;
5969 ent_count++;
5970 }
5971
5972 if ( ent_count )
5973 {
5974 for ( int x = 0; x < ent_count; x++ )
5975 {
5976 if ( push_list[x]->client )
5977 {
5978 vec3_t pushDir;
5979 float knockback = pull?0:200;
5980
5981 //FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!!
5982
5983 //First, if this is the player we're push/pulling, see if he can counter it
5984 if ( !push_list[x]->s.number )
5985 {//player
5986 if ( push_list[x]->health > 0 //alive
5987 && push_list[x]->client //client
5988 && push_list[x]->client->ps.torsoAnim != BOTH_FORCEGRIP_HOLD// BOTH_FORCEGRIP1//wasn't trying to grip anyone
5989 && (self->client->NPC_class != CLASS_DESANN || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
5990 && push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE//on the ground
5991 && !PM_InKnockDown( &push_list[x]->client->ps )//not knocked down already
5992 && push_list[x]->client->ps.saberLockTime < level.time//not involved in a saberLock
5993 && push_list[x]->client->ps.weaponTime < level.time//not attacking or otherwise busy
5994 && (push_list[x]->client->ps.weapon == WP_SABER||push_list[x]->client->ps.weapon == WP_MELEE) )//using saber or fists
5995 {//trying to push or pull the player!
5996 if ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time//player was pushing/pulling too
5997 ||( pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PULL] - self->client->ps.forcePowerLevel[FP_PULL])*2+1 ) > 0 )//player's pull is high enough
5998 ||( !pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PUSH] - self->client->ps.forcePowerLevel[FP_PUSH])*2+1 ) > 0 ) )//player's push is high enough
5999 {//player's force push/pull is high enough to try to stop me
6000 if ( InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) )
6001 {//I'm in front of player
6002 WP_ResistForcePush( push_list[x], self, qfalse );
6003 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
6004 push_list[x]->client->ps.saberBlocked = BLOCKED_NONE;
6005 continue;
6006 }
6007 }
6008 }
6009 }
6010 else if ( push_list[x]->client && Jedi_WaitingAmbush( push_list[x] ) )
6011 {
6012 WP_ForceKnockdown( push_list[x], self, pull, qtrue, qfalse );
6013 continue;
6014 }
6015
6016
6017 //okay, everyone else (or player who couldn't resist it)...
6018 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
6019 && push_list[x]->client->ps.weapon == WP_SABER //Jedi
6020 && push_list[x]->health > 0 //alive
6021 && (self->client->NPC_class != CLASS_DESANN || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push
6022 && push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE //on the ground
6023 && InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.3f ) //I'm in front of him
6024 && ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time ||//he's pushing too
6025 (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)
6026 )
6027 )
6028 {//Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground
6029 if ( push_list[x]->client->ps.saberLockTime > level.time )
6030 {//they're in a lock
6031 if ( push_list[x]->client->ps.saberLockEnemy != self->s.number )
6032 {//they're not in a lock with me
6033 continue;
6034 }
6035 else if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ||
6036 push_list[x]->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6037 {//they're in a lock with me, but my push is too weak
6038 continue;
6039 }
6040 else
6041 {//we will knock them down
6042 self->painDebounceTime = 0;
6043 self->client->ps.weaponTime = 500;
6044 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6045 {
6046 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6047 }
6048 }
6049 }
6050 if ( !pull && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 && !Q_irand(0,2) &&
6051 push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 )
6052 {//a level 3 push can even knock down a jedi
6053 if ( PM_InKnockDown( &push_list[x]->client->ps ) )
6054 {//can't knock them down again
6055 continue;
6056 }
6057 WP_ForceKnockdown( push_list[x], self, pull, qfalse, qtrue );
6058 }
6059 else
6060 {
6061 WP_ResistForcePush( push_list[x], self, qfalse );
6062 }
6063 }
6064 else
6065 {
6066 //UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)!
6067 //shove them
6068 if ( push_list[x]->s.number && push_list[x]->message )
6069 {//an NPC who has a key
6070 //don't push me... FIXME: maybe can pull the key off me?
6071 WP_ForceKnockdown( push_list[x], self, pull, qfalse, qfalse );
6072 continue;
6073 }
6074 if ( pull )
6075 {
6076 VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
6077 if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_1
6078 && push_list[x]->s.weapon != WP_SABER
6079 && push_list[x]->s.weapon != WP_MELEE
6080 && push_list[x]->s.weapon != WP_THERMAL )
6081 {//yank the weapon - NOTE: level 1 just knocks them down, not take weapon
6082 //FIXME: weapon yank anim if not a knockdown?
6083 if ( InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.0f ) )
6084 {//enemy has to be facing me, too...
6085 WP_DropWeapon( push_list[x], pushDir );
6086 }
6087 }
6088 knockback += VectorNormalize( pushDir );
6089 if ( knockback > 200 )
6090 {
6091 knockback = 200;
6092 }
6093 if ( self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_3 )
6094 {//maybe just knock them down
6095 knockback /= 3;
6096 }
6097 }
6098 else
6099 {
6100 VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
6101 knockback -= VectorNormalize( pushDir );
6102 if ( knockback < 100 )
6103 {
6104 knockback = 100;
6105 }
6106 //scale for push level
6107 if ( self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 )
6108 {//maybe just knock them down
6109 knockback /= 3;
6110 }
6111 else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6112 {//super-hard push
6113 //Hmm, maybe in this case can even nudge/knockdown a jedi? Especially if close?
6114 //knockback *= 5;
6115 }
6116 }
6117 //actually push/pull the enemy
6118 G_Throw( push_list[x], pushDir, knockback );
6119 //make it so they don't actually hurt me when pulled at me...
6120 push_list[x]->forcePuller = self->s.number;
6121
6122 if ( push_list[x]->client->ps.velocity[2] < knockback )
6123 {
6124 push_list[x]->client->ps.velocity[2] = knockback;
6125 }
6126
6127 if ( push_list[x]->health > 0 )
6128 {//target is still alive
6129 if ( (push_list[x]->s.number||(cg.renderingThirdPerson&&!cg.zoomMode)) //NPC or 3rd person player
6130 && ((!pull&&self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1) //level 1 push
6131 || (pull && self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_1)) )//level 1 pull
6132 {//NPC or third person player (without force push/pull skill), and force push/pull level is at 1
6133 WP_ForceKnockdown(
6134 push_list[x], self, pull,
6135 (qboolean)(!pull && knockback > 150), qfalse );
6136 }
6137 else if ( !push_list[x]->s.number )
6138 {//player, have to force an anim on him
6139 WP_ForceKnockdown(
6140 push_list[x], self, pull,
6141 (qboolean)(!pull && knockback > 150), qfalse );
6142 }
6143 else
6144 {//NPC and force-push/pull at level 2 or higher
6145 WP_ForceKnockdown(
6146 push_list[x], self, pull,
6147 (qboolean)(!pull && knockback > 100), qfalse );
6148 }
6149 }
6150 push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
6151 }
6152 }
6153 else if ( push_list[x]->s.weapon == WP_SABER && (push_list[x]->contents&CONTENTS_LIGHTSABER) )
6154 {//a thrown saber, just send it back
6155 /*
6156 if ( pull )
6157 {//steal it?
6158 }
6159 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 )
6160 {//it's on and being controlled
6161 //FIXME: prevent it from damaging me?
6162 if ( self->s.number == 0 || Q_irand( 0, 2 ) )
6163 {//certain chance of throwing it aside and turning it off?
6164 //give it some velocity away from me
6165 //FIXME: maybe actually push or pull it?
6166 if ( Q_irand( 0, 1 ) )
6167 {
6168 VectorScale( right, -1, right );
6169 }
6170 G_ReflectMissile( self, push_list[x], right );
6171 //FIXME: isn't turning off!!!
6172 WP_SaberDrop( push_list[x]->owner, push_list[x] );
6173 }
6174 else
6175 {
6176 WP_SaberReturn( push_list[x]->owner, push_list[x] );
6177 }
6178 //different effect?
6179 }
6180 }
6181 else if ( push_list[x]->s.eType == ET_MISSILE
6182 && push_list[x]->s.pos.trType != TR_STATIONARY
6183 && (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below
6184 {
6185 vec3_t dir2Me;
6186 VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, dir2Me );
6187 float dot = DotProduct( push_list[x]->s.pos.trDelta, dir2Me );
6188 if ( pull )
6189 {//deflect rather than reflect?
6190 }
6191 else
6192 {
6193 if ( push_list[x]->s.eFlags&EF_MISSILE_STICK )
6194 {//caught a sticky in-air
6195 push_list[x]->s.eType = ET_MISSILE;
6196 push_list[x]->s.eFlags &= ~EF_MISSILE_STICK;
6197 push_list[x]->s.eFlags |= EF_BOUNCE_HALF;
6198 push_list[x]->splashDamage /= 3;
6199 push_list[x]->splashRadius /= 3;
6200 push_list[x]->e_ThinkFunc = thinkF_WP_Explode;
6201 push_list[x]->nextthink = level.time + Q_irand( 500, 3000 );
6202 }
6203 if ( dot >= 0 )
6204 {//it's heading towards me
6205 G_ReflectMissile( self, push_list[x], forward );
6206 }
6207 else
6208 {
6209 VectorScale( push_list[x]->s.pos.trDelta, 1.25f, push_list[x]->s.pos.trDelta );
6210 }
6211 //deflect sound
6212 //G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) );
6213 //push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
6214 }
6215 }
6216 else if ( push_list[x]->svFlags & SVF_GLASS_BRUSH )
6217 {//break the glass
6218 trace_t tr;
6219 vec3_t pushDir;
6220 float damage = 800;
6221
6222 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6223 VectorNormalize( forward );
6224 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
6225 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6226 if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6227 {//must be pointing right at it
6228 continue;
6229 }
6230
6231 if ( pull )
6232 {
6233 VectorSubtract( self->client->renderInfo.eyePoint, tr.endpos, pushDir );
6234 }
6235 else
6236 {
6237 VectorSubtract( tr.endpos, self->client->renderInfo.eyePoint, pushDir );
6238 }
6239 /*
6240 VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
6241 VectorMA( push_list[x]->absmin, 0.5, size, center );
6242 if ( pull )
6243 {
6244 VectorSubtract( self->client->renderInfo.eyePoint, center, pushDir );
6245 }
6246 else
6247 {
6248 VectorSubtract( center, self->client->renderInfo.eyePoint, pushDir );
6249 }
6250 */
6251 damage -= VectorNormalize( pushDir );
6252 if ( damage < 200 )
6253 {
6254 damage = 200;
6255 }
6256 VectorScale( pushDir, damage, pushDir );
6257
6258 G_Damage( push_list[x], self, self, pushDir, tr.endpos, damage, 0, MOD_UNKNOWN );
6259 }
6260 else if ( !Q_stricmp( "func_static", push_list[x]->classname ) )
6261 {//force-usable func_static
6262 if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) )
6263 {
6264 GEntity_UseFunc( push_list[x], self, self );
6265 }
6266 else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) )
6267 {
6268 GEntity_UseFunc( push_list[x], self, self );
6269 }
6270 }
6271 else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2/*MOVER_FORCE_ACTIVATE*/) )
6272 {//push/pull the door
6273 vec3_t pos1, pos2;
6274
6275 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6276 VectorNormalize( forward );
6277 VectorMA( self->client->renderInfo.eyePoint, radius, forward, end );
6278 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6279 if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6280 {//must be pointing right at it
6281 continue;
6282 }
6283
6284 if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) )
6285 {//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center
6286 VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size );
6287 VectorMA( push_list[x]->absmin, 0.5, size, center );
6288 if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 )
6289 {//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
6290 VectorSubtract( center, push_list[x]->pos1, center );
6291 }
6292 else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 )
6293 {//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
6294 VectorSubtract( center, push_list[x]->pos2, center );
6295 }
6296 VectorAdd( center, push_list[x]->pos1, pos1 );
6297 VectorAdd( center, push_list[x]->pos2, pos2 );
6298 }
6299 else
6300 {//actually has an origin, pos1 and pos2 are absolute
6301 VectorCopy( push_list[x]->currentOrigin, center );
6302 VectorCopy( push_list[x]->pos1, pos1 );
6303 VectorCopy( push_list[x]->pos2, pos2 );
6304 }
6305
6306 if ( Distance( pos1, self->client->renderInfo.eyePoint ) < Distance( pos2, self->client->renderInfo.eyePoint ) )
6307 {//pos1 is closer
6308 if ( push_list[x]->moverState == MOVER_POS1 )
6309 {//at the closest pos
6310 if ( pull )
6311 {//trying to pull, but already at closest point, so screw it
6312 continue;
6313 }
6314 }
6315 else if ( push_list[x]->moverState == MOVER_POS2 )
6316 {//at farthest pos
6317 if ( !pull )
6318 {//trying to push, but already at farthest point, so screw it
6319 continue;
6320 }
6321 }
6322 }
6323 else
6324 {//pos2 is closer
6325 if ( push_list[x]->moverState == MOVER_POS1 )
6326 {//at the farthest pos
6327 if ( !pull )
6328 {//trying to push, but already at farthest point, so screw it
6329 continue;
6330 }
6331 }
6332 else if ( push_list[x]->moverState == MOVER_POS2 )
6333 {//at closest pos
6334 if ( pull )
6335 {//trying to pull, but already at closest point, so screw it
6336 continue;
6337 }
6338 }
6339 }
6340 GEntity_UseFunc( push_list[x], self, self );
6341 }
6342 else if ( push_list[x]->s.eType == ET_MISSILE/*thermal resting on ground*/
6343 || push_list[x]->s.eType == ET_ITEM
6344 || push_list[x]->e_ThinkFunc == thinkF_G_RunObject || Q_stricmp( "limb", push_list[x]->classname ) == 0 )
6345 {//general object, toss it
6346 vec3_t pushDir, kvel;
6347 float knockback = pull?0:200;
6348 float mass = 200;
6349 if ( pull )
6350 {
6351 if ( push_list[x]->s.eType == ET_ITEM )
6352 {//pull it to a little higher point
6353 vec3_t adjustedOrg;
6354 VectorCopy( self->currentOrigin, adjustedOrg );
6355 adjustedOrg[2] += self->maxs[2]/3;
6356 VectorSubtract( adjustedOrg, push_list[x]->currentOrigin, pushDir );
6357 }
6358 else
6359 {
6360 VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir );
6361 }
6362 knockback += VectorNormalize( pushDir );
6363 if ( knockback > 200 )
6364 {
6365 knockback = 200;
6366 }
6367 if ( push_list[x]->s.eType == ET_ITEM
6368 && push_list[x]->item
6369 && push_list[x]->item->giType == IT_HOLDABLE
6370 && push_list[x]->item->giTag == INV_SECURITY_KEY )
6371 {//security keys are pulled with less enthusiasm
6372 if ( knockback > 100 )
6373 {
6374 knockback = 100;
6375 }
6376 }
6377 else if ( knockback > 200 )
6378 {
6379 knockback = 200;
6380 }
6381 }
6382 else
6383 {
6384 //HMM, if I have an auto-enemy & he's in front of me, push it toward him?
6385 VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir );
6386 knockback -= VectorNormalize( pushDir );
6387 if ( knockback < 100 )
6388 {
6389 knockback = 100;
6390 }
6391 }
6392 //FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid? or check?
6393 VectorCopy( push_list[x]->currentOrigin, push_list[x]->s.pos.trBase );
6394 push_list[x]->s.pos.trTime = level.time; // move a bit on the very first frame
6395 if ( push_list[x]->s.pos.trType != TR_INTERPOLATE )
6396 {//don't do this to rolling missiles
6397 push_list[x]->s.pos.trType = TR_GRAVITY;
6398 }
6399
6400 if ( push_list[x]->e_ThinkFunc == thinkF_G_RunObject && push_list[x]->physicsBounce )
6401 {
6402 mass = push_list[x]->physicsBounce;
6403 }
6404 if ( mass < 50 )
6405 {//???
6406 mass = 50;
6407 }
6408 if ( g_gravity->value > 0 )
6409 {
6410 VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
6411 kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
6412 }
6413 else
6414 {
6415 VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
6416 }
6417 VectorAdd( push_list[x]->s.pos.trDelta, kvel, push_list[x]->s.pos.trDelta );
6418 if ( g_gravity->value > 0 )
6419 {
6420 if ( push_list[x]->s.pos.trDelta[2] < knockback )
6421 {
6422 push_list[x]->s.pos.trDelta[2] = knockback;
6423 }
6424 }
6425 //no trDuration?
6426 if ( push_list[x]->e_ThinkFunc != thinkF_G_RunObject )
6427 {//objects spin themselves?
6428 //spin it
6429 //FIXME: messing with roll ruins the rotational center???
6430 push_list[x]->s.apos.trTime = level.time;
6431 push_list[x]->s.apos.trType = TR_LINEAR;
6432 VectorClear( push_list[x]->s.apos.trDelta );
6433 push_list[x]->s.apos.trDelta[1] = Q_irand( -800, 800 );
6434 }
6435
6436 if ( Q_stricmp( "limb", push_list[x]->classname ) == 0 )
6437 {//make sure it runs it's physics
6438 push_list[x]->e_ThinkFunc = thinkF_LimbThink;
6439 push_list[x]->nextthink = level.time + FRAMETIME;
6440 }
6441 push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms
6442 if ( push_list[x]->item && push_list[x]->item->giTag == INV_SECURITY_KEY )
6443 {
6444 AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
6445 }
6446 else
6447 {
6448 AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
6449 }
6450 }
6451 else if ( push_list[x]->s.weapon == WP_TURRET
6452 && !Q_stricmp( "PAS", push_list[x]->classname )
6453 && push_list[x]->s.apos.trType == TR_STATIONARY )
6454 {//a portable turret
6455 WP_KnockdownTurret( self, push_list[x] );
6456 }
6457 }
6458 if ( pull )
6459 {
6460 if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6461 {//at level 3, can pull multiple, so it costs more
6462 actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
6463 if ( actualCost > 50 )
6464 {
6465 actualCost = 50;
6466 }
6467 else if ( actualCost < cost )
6468 {
6469 actualCost = cost;
6470 }
6471 }
6472 else
6473 {
6474 actualCost = cost;
6475 }
6476 WP_ForcePowerStart( self, FP_PULL, actualCost );
6477 }
6478 else
6479 {
6480 if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 )
6481 {//at level 3, can push multiple, so costs more
6482 actualCost = forcePowerNeeded[FP_PUSH]*ent_count;
6483 if ( actualCost > 50 )
6484 {
6485 actualCost = 50;
6486 }
6487 else if ( actualCost < cost )
6488 {
6489 actualCost = cost;
6490 }
6491 }
6492 else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_1 )
6493 {//at level 3, can push multiple, so costs more
6494 actualCost = floor(forcePowerNeeded[FP_PUSH]*ent_count/1.5f);
6495 if ( actualCost > 50 )
6496 {
6497 actualCost = 50;
6498 }
6499 else if ( actualCost < cost )
6500 {
6501 actualCost = cost;
6502 }
6503 }
6504 else
6505 {
6506 actualCost = cost;
6507 }
6508 WP_ForcePowerStart( self, FP_PUSH, actualCost );
6509 }
6510 }
6511 else
6512 {//didn't push or pull anything? don't penalize them too much
6513 if ( pull )
6514 {
6515 WP_ForcePowerStart( self, FP_PULL, 5 );
6516 }
6517 else
6518 {
6519 WP_ForcePowerStart( self, FP_PUSH, 5 );
6520 }
6521 }
6522 if ( self->NPC )
6523 {//NPCs can push more often
6524 //FIXME: vary by rank and game skill?
6525 self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + 200;
6526 }
6527 else
6528 {
6529 self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500;
6530 }
6531 }
6532
ForceSpeed(gentity_t * self,int duration)6533 void ForceSpeed( gentity_t *self, int duration )
6534 {
6535 if ( self->health <= 0 )
6536 {
6537 return;
6538 }
6539 if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) )
6540 {
6541 return;
6542 }
6543 if ( self->client->ps.saberLockTime > level.time )
6544 {//FIXME: can this be a way to break out?
6545 return;
6546 }
6547 if ( !self->s.number && in_camera )
6548 {//player can't use force powers in cinematic
6549 return;
6550 }
6551 WP_ForcePowerStart( self, FP_SPEED, 0 );
6552 if ( duration )
6553 {
6554 self->client->ps.forcePowerDuration[FP_SPEED] = level.time + duration;
6555 }
6556 G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) );
6557 }
6558
ForceHeal(gentity_t * self)6559 void ForceHeal( gentity_t *self )
6560 {
6561 if ( self->health <= 0 || self->client->ps.stats[STAT_MAX_HEALTH] <= self->health )
6562 {
6563 return;
6564 }
6565
6566 if ( !WP_ForcePowerUsable( self, FP_HEAL, 20 ) )
6567 {//must have enough force power for at least 5 points of health
6568 return;
6569 }
6570
6571 if ( self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) )
6572 {//can't initiate a heal while taking pain or attacking
6573 return;
6574 }
6575
6576 if ( self->client->ps.saberLockTime > level.time )
6577 {//FIXME: can this be a way to break out?
6578 return;
6579 }
6580 if ( !self->s.number && in_camera )
6581 {//player can't use force powers in cinematic
6582 return;
6583 }
6584 /*
6585 if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
6586 {//instant heal
6587 //no more than available force power
6588 int max = self->client->ps.forcePower;
6589 if ( max > MAX_FORCE_HEAL )
6590 {//no more than max allowed
6591 max = MAX_FORCE_HEAL;
6592 }
6593 if ( max > self->client->ps.stats[STAT_MAX_HEALTH] - self->health )
6594 {//no more than what's missing
6595 max = self->client->ps.stats[STAT_MAX_HEALTH] - self->health;
6596 }
6597 self->health += max;
6598 WP_ForcePowerDrain( self, FP_HEAL, max );
6599 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
6600 }
6601 else
6602 */
6603 {
6604 //start health going up
6605 //NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE );
6606 WP_ForcePowerStart( self, FP_HEAL, 0 );
6607 if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
6608 {//must meditate
6609 //FIXME: holster weapon (select WP_NONE?)
6610 //FIXME: BOTH_FORCEHEAL_START
6611 NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6612 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6613 self->client->ps.saberBlocked = BLOCKED_NONE;
6614 self->client->ps.torsoAnimTimer = self->client->ps.legsAnimTimer = FORCE_HEAL_INTERVAL*MAX_FORCE_HEAL + 2000;//???
6615 if ( self->client->ps.saberActive )
6616 {
6617 self->client->ps.saberActive = qfalse;//turn off saber when meditating
6618 if ( self->client->playerTeam == TEAM_PLAYER )
6619 {
6620 G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/saber/saberoff.wav" );
6621 }
6622 else
6623 {
6624 G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/saber/enemy_saber_off.wav" );
6625 }
6626 }
6627 }
6628 else
6629 {//just a quick gesture
6630 /*
6631 //Can't get an anim that looks good...
6632 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_QUICK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6633 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6634 self->client->ps.saberBlocked = BLOCKED_NONE;
6635 */
6636 }
6637 }
6638
6639 //FIXME: always play healing effect
6640 G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/heal.mp3" );
6641 }
6642
6643 extern void NPC_PlayConfusionSound( gentity_t *self );
6644 extern void NPC_Jedi_PlayConfusionSound( gentity_t *self );
WP_CheckBreakControl(gentity_t * self)6645 qboolean WP_CheckBreakControl( gentity_t *self )
6646 {
6647 if ( !self )
6648 {
6649 return qfalse;
6650 }
6651 if ( !self->s.number )
6652 {//player
6653 if ( self->client && self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
6654 {//control-level
6655 if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
6656 {//we are in a viewentity
6657 gentity_t *controlled = &g_entities[self->client->ps.viewEntity];
6658 if ( controlled->NPC && controlled->NPC->controlledTime > level.time )
6659 {//it is an NPC we controlled
6660 //clear it and return
6661 G_ClearViewEntity( self );
6662 return qtrue;
6663 }
6664 }
6665 }
6666 }
6667 else
6668 {//NPC
6669 if ( self->NPC && self->NPC->controlledTime > level.time )
6670 {//being controlled
6671 gentity_t *controller = &g_entities[0];
6672 if ( controller->client && controller->client->ps.viewEntity == self->s.number )
6673 {//we are being controlled by player
6674 if ( controller->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
6675 {//control-level mind trick
6676 //clear the control and return
6677 G_ClearViewEntity( controller );
6678 return qtrue;
6679 }
6680 }
6681 }
6682 }
6683 return qfalse;
6684 }
6685
ForceTelepathy(gentity_t * self)6686 void ForceTelepathy( gentity_t *self )
6687 {
6688 trace_t tr;
6689 vec3_t end, forward;
6690 gentity_t *traceEnt;
6691 qboolean targetLive = qfalse;
6692
6693 if ( WP_CheckBreakControl( self ) )
6694 {
6695 return;
6696 }
6697 if ( self->health <= 0 )
6698 {
6699 return;
6700 }
6701 //FIXME: if mind trick 3 and aiming at an enemy need more force power
6702 if ( !WP_ForcePowerUsable( self, FP_TELEPATHY, 0 ) )
6703 {
6704 return;
6705 }
6706
6707 if ( self->client->ps.weaponTime >= 800 )
6708 {//just did one!
6709 return;
6710 }
6711 if ( self->client->ps.saberLockTime > level.time )
6712 {//FIXME: can this be a way to break out?
6713 return;
6714 }
6715 if ( !self->s.number && in_camera )
6716 {//player can't use force powers in cinematic
6717 return;
6718 }
6719
6720 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6721 VectorNormalize( forward );
6722 VectorMA( self->client->renderInfo.eyePoint, 2048, forward, end );
6723
6724 //Cause a distraction if enemy is not fighting
6725 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_BODY, G2_NOCOLLIDE, 0 );
6726 if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6727 {
6728 return;
6729 }
6730
6731 traceEnt = &g_entities[tr.entityNum];
6732
6733 if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
6734 {
6735 return;
6736 }
6737
6738 if ( traceEnt && traceEnt->client )
6739 {
6740 switch ( traceEnt->client->NPC_class )
6741 {
6742 case CLASS_GALAKMECH://cant grip him, he's in armor
6743 case CLASS_ATST://much too big to grip!
6744 //no droids either
6745 case CLASS_PROBE:
6746 case CLASS_GONK:
6747 case CLASS_R2D2:
6748 case CLASS_R5D2:
6749 case CLASS_MARK1:
6750 case CLASS_MARK2:
6751 case CLASS_MOUSE:
6752 case CLASS_SEEKER:
6753 case CLASS_REMOTE:
6754 case CLASS_PROTOCOL:
6755 break;
6756 default:
6757 targetLive = qtrue;
6758 break;
6759 }
6760 }
6761 if ( targetLive && traceEnt->NPC )
6762 {//hit an organic non-player
6763 if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) )
6764 {//activated a script on him
6765 //FIXME: do the visual sparkles effect on their heads, still?
6766 WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
6767 }
6768 else if ( traceEnt->client->playerTeam != self->client->playerTeam )
6769 {//an enemy
6770 int override = 0;
6771 if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) )
6772 {
6773 if ( traceEnt->client->NPC_class == CLASS_GALAKMECH )
6774 {
6775 G_AddVoiceEvent( NPC, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), Q_irand( 3000, 5000 ) );
6776 }
6777 }
6778 else if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
6779 {//control them, even jedi
6780 G_SetViewEntity( self, traceEnt );
6781 traceEnt->NPC->controlledTime = level.time + 30000;
6782 }
6783 else if ( traceEnt->s.weapon != WP_SABER )
6784 {//haha! Jedi aren't easily confused!
6785 if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2 )
6786 {//turn them to our side
6787 //if mind trick 3 and aiming at an enemy need more force power
6788 override = 50;
6789 if ( self->client->ps.forcePower < 50 )
6790 {
6791 return;
6792 }
6793 if ( traceEnt->s.weapon != WP_NONE )
6794 {//don't charm people who aren't capable of fighting... like ugnaughts and droids
6795 if ( traceEnt->enemy )
6796 {
6797 G_ClearEnemy( traceEnt );
6798 }
6799 if ( traceEnt->NPC )
6800 {
6801 //traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER;
6802 traceEnt->client->leader = self;
6803 }
6804 //FIXME: maybe pick an enemy right here?
6805 team_t saveTeam = traceEnt->client->enemyTeam;
6806 traceEnt->client->enemyTeam = traceEnt->client->playerTeam;
6807 traceEnt->client->playerTeam = saveTeam;
6808 //FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done?
6809 traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];
6810 }
6811 }
6812 else
6813 {//just confuse them
6814 //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?
6815 traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds
6816 NPC_PlayConfusionSound( traceEnt );
6817 if ( traceEnt->enemy )
6818 {
6819 G_ClearEnemy( traceEnt );
6820 }
6821 }
6822 }
6823 else
6824 {
6825 NPC_Jedi_PlayConfusionSound( traceEnt );
6826 }
6827 WP_ForcePowerStart( self, FP_TELEPATHY, override );
6828 }
6829 else if ( traceEnt->client->playerTeam == self->client->playerTeam )
6830 {//an ally
6831 //maybe just have him look at you? Respond? Take your enemy?
6832 if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) )
6833 {
6834 NPC_UseResponse( traceEnt, self, qfalse );
6835 WP_ForcePowerStart( self, FP_TELEPATHY, 1 );
6836 }
6837 }//NOTE: no effect on TEAM_NEUTRAL?
6838 vec3_t eyeDir;
6839 AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL );
6840 VectorNormalize( eyeDir );
6841 G_PlayEffect( "force_touch", traceEnt->client->renderInfo.eyePoint, eyeDir );
6842
6843 //make sure this plays and that you cannot press fire for about 1 second after this
6844 //FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT
6845 NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
6846 //FIXME: build-up or delay this until in proper part of anim
6847 }
6848 else
6849 {
6850 if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr.fraction * 2048 > 64 )
6851 {//don't create a diversion less than 64 from you of if at power level 1
6852 //use distraction anim instead
6853 G_PlayEffect( G_EffectIndex( "force_touch" ), tr.endpos, tr.plane.normal );
6854 //FIXME: these events don't seem to always be picked up...?
6855 AddSoundEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue );
6856 AddSightEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, 50 );
6857 WP_ForcePowerStart( self, FP_TELEPATHY, 0 );
6858 }
6859 NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD );
6860 }
6861 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6862 self->client->ps.saberBlocked = BLOCKED_NONE;
6863 self->client->ps.weaponTime = 1000;
6864 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6865 {
6866 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6867 }
6868 }
6869
ForceGrip(gentity_t * self)6870 void ForceGrip( gentity_t *self )
6871 {//FIXME: make enemy Jedi able to use this
6872 trace_t tr;
6873 vec3_t end, forward;
6874 gentity_t *traceEnt = NULL;
6875
6876 if ( self->health <= 0 )
6877 {
6878 return;
6879 }
6880 if ( !self->s.number && (cg.zoomMode || in_camera) )
6881 {//can't force grip when zoomed in or in cinematic
6882 return;
6883 }
6884 if ( self->client->ps.leanofs )
6885 {//can't force-grip while leaning
6886 return;
6887 }
6888
6889 if ( self->client->ps.forceGripEntityNum <= ENTITYNUM_WORLD )
6890 {//already gripping
6891 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
6892 {
6893 self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 100;
6894 self->client->ps.weaponTime = 1000;
6895 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6896 {
6897 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6898 }
6899 }
6900 return;
6901 }
6902
6903 if ( !WP_ForcePowerUsable( self, FP_GRIP, 0 ) )
6904 {//can't use it right now
6905 return;
6906 }
6907
6908 if ( self->client->ps.forcePower < 26 )
6909 {//need 20 to start, 6 to hold it for any decent amount of time...
6910 return;
6911 }
6912
6913 if ( self->client->ps.weaponTime )
6914 {//busy
6915 return;
6916 }
6917
6918 if ( self->client->ps.saberLockTime > level.time )
6919 {//FIXME: can this be a way to break out?
6920 return;
6921 }
6922 //Cause choking anim + health drain in ent in front of me
6923 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
6924 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
6925 self->client->ps.saberBlocked = BLOCKED_NONE;
6926
6927 self->client->ps.weaponTime = 1000;
6928 if ( self->client->ps.forcePowersActive&(1<<FP_SPEED) )
6929 {
6930 self->client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value );
6931 }
6932
6933 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
6934 VectorNormalize( forward );
6935 VectorMA( self->client->renderInfo.handLPoint, FORCE_GRIP_DIST, forward, end );
6936
6937 if ( self->enemy && (self->s.number || InFront( self->enemy->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 0.2f ) ) )
6938 {//NPCs can always lift enemy since we assume they're looking at them, players need to be facing the enemy
6939 if ( gi.inPVS( self->enemy->currentOrigin, self->client->renderInfo.eyePoint ) )
6940 {//must be in PVS
6941 gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, self->enemy->currentOrigin, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6942 if ( tr.fraction == 1.0f || tr.entityNum == self->enemy->s.number )
6943 {//must have clear LOS
6944 traceEnt = self->enemy;
6945 }
6946 }
6947 }
6948 if ( !traceEnt )
6949 {//okay, trace straight ahead and see what's there
6950 gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
6951 if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
6952 {
6953 return;
6954 }
6955
6956 traceEnt = &g_entities[tr.entityNum];
6957 }
6958
6959 if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) )
6960 {
6961 return;
6962 }
6963
6964 if ( traceEnt->client )
6965 {
6966 if ( traceEnt->client->ps.forceJumpZStart )
6967 {//can't catch them in mid force jump - FIXME: maybe base it on velocity?
6968 return;
6969 }
6970 switch ( traceEnt->client->NPC_class )
6971 {
6972 case CLASS_GALAKMECH://cant grip him, he's in armor
6973 G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) );
6974 return;
6975 break;
6976 case CLASS_ATST://much too big to grip!
6977 return;
6978 //no droids either...?
6979 case CLASS_GONK:
6980 case CLASS_R2D2:
6981 case CLASS_R5D2:
6982 case CLASS_MARK1:
6983 case CLASS_MARK2:
6984 case CLASS_MOUSE://?
6985 case CLASS_PROTOCOL:
6986 //*sigh*... in JK3, you'll be able to grab and move *anything*...
6987 return;
6988 break;
6989 case CLASS_PROBE:
6990 case CLASS_SEEKER:
6991 case CLASS_REMOTE:
6992 case CLASS_SENTRY:
6993 case CLASS_INTERROGATOR:
6994 //*sigh*... in JK3, you'll be able to grab and move *anything*...
6995 return;
6996 break;
6997 case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly
6998 Jedi_PlayDeflectSound( traceEnt );
6999 ForceThrow( traceEnt, qfalse );
7000 return;
7001 break;
7002 case CLASS_REBORN:
7003 case CLASS_SHADOWTROOPER:
7004 case CLASS_TAVION:
7005 case CLASS_JEDI:
7006 case CLASS_LUKE:
7007 if ( traceEnt->NPC && traceEnt->NPC->rank > RANK_CIVILIAN && self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
7008 {
7009 Jedi_PlayDeflectSound( traceEnt );
7010 ForceThrow( traceEnt, qfalse );
7011 return;
7012 }
7013 break;
7014 default:
7015 break;
7016 }
7017 if ( traceEnt->s.weapon == WP_EMPLACED_GUN )
7018 {//FIXME: maybe can pull them out?
7019 return;
7020 }
7021 if ( self->enemy && traceEnt != self->enemy && traceEnt->client->playerTeam == self->client->playerTeam )
7022 {//can't accidently grip your teammate in combat
7023 return;
7024 }
7025 }
7026 else
7027 {//can't grip non-clients... right?
7028 return;
7029 }
7030 WP_ForcePowerStart( self, FP_GRIP, 20 );
7031 //FIXME: rule out other things?
7032 //FIXME: Jedi resist, like the push and pull?
7033 self->client->ps.forceGripEntityNum = traceEnt->s.number;
7034 //if ( traceEnt->client )
7035 {
7036 G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
7037 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 || traceEnt->s.weapon == WP_SABER )
7038 {//if we pick up & carry, drop their weap
7039 if ( traceEnt->s.weapon )
7040 {
7041 if ( traceEnt->s.weapon != WP_SABER )
7042 {
7043 WP_DropWeapon( traceEnt, NULL );
7044 }
7045 else
7046 {
7047 //turn it off?
7048 traceEnt->client->ps.saberActive = qfalse;
7049 G_SoundOnEnt( traceEnt, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" );
7050 }
7051 }
7052 }
7053 //else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun
7054 VectorCopy( traceEnt->client->renderInfo.headPoint, self->client->ps.forceGripOrg );
7055 }
7056 /*
7057 else
7058 {
7059 VectorCopy( traceEnt->currentOrigin, self->client->ps.forceGripOrg );
7060 }
7061 */
7062 self->client->ps.forceGripOrg[2] += 48;//FIXME: define?
7063 if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
7064 {//just a duration
7065 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
7066 self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 5000;
7067 traceEnt->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
7068 }
7069 else
7070 {
7071 if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 )
7072 {//lifting sound? or always?
7073 }
7074 //if ( traceEnt->s.number )
7075 {//picks them up for a second first
7076 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 1000;
7077 }
7078 /*
7079 else
7080 {//player should take damage right away
7081 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250;
7082 }
7083 */
7084 self->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" );
7085 }
7086 }
7087
ForceLightning(gentity_t * self)7088 void ForceLightning( gentity_t *self )
7089 {
7090 if ( self->health <= 0 )
7091 {
7092 return;
7093 }
7094 if ( !self->s.number && (cg.zoomMode || in_camera) )
7095 {//can't force lightning when zoomed in or in cinematic
7096 return;
7097 }
7098 if ( self->client->ps.leanofs )
7099 {//can't force-lightning while leaning
7100 return;
7101 }
7102 if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING, 0 ) )
7103 {
7104 return;
7105 }
7106 if ( self->client->ps.forcePowerDebounce[FP_LIGHTNING] > level.time )
7107 {//stops it while using it and also after using it, up to 3 second delay
7108 return;
7109 }
7110 if ( self->client->ps.saberLockTime > level.time )
7111 {//FIXME: can this be a way to break out?
7112 return;
7113 }
7114 //Shoot lightning from hand
7115 //make sure this plays and that you cannot press fire for about 1 second after this
7116 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
7117 {
7118 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7119 }
7120 else
7121 {
7122 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7123 }
7124 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
7125 self->client->ps.saberBlocked = BLOCKED_NONE;
7126
7127 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
7128 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
7129 {//short burst
7130 //G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" );
7131 }
7132 else
7133 {//holding it
7134 self->s.loopSound = G_SoundIndex( "sound/weapons/force/lightning2.wav" );
7135 }
7136
7137 //FIXME: build-up or delay this until in proper part of anim
7138 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
7139 WP_ForcePowerStart( self, FP_LIGHTNING, self->client->ps.torsoAnimTimer );
7140 }
7141
ForceLightningDamage(gentity_t * self,gentity_t * traceEnt,vec3_t dir,float dist,float dot,vec3_t impactPoint)7142 void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, float dist, float dot, vec3_t impactPoint )
7143 {
7144 if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE )
7145 {
7146 return;
7147 }
7148
7149 if ( traceEnt && traceEnt->takedamage )
7150 {
7151 if ( !traceEnt->client || traceEnt->client->playerTeam != self->client->playerTeam || self->enemy == traceEnt || traceEnt->enemy == self )
7152 {//an enemy or object
7153 int dmg;
7154 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
7155 {//more damage if closer and more in front
7156 dmg = 1;
7157 if ( dist < 100 )
7158 {
7159 dmg += 2;
7160 }
7161 else if ( dist < 200 )
7162 {
7163 dmg += 1;
7164 }
7165 if ( dot > 0.9f )
7166 {
7167 dmg += 2;
7168 }
7169 else if ( dot > 0.7f )
7170 {
7171 dmg += 1;
7172 }
7173 }
7174 else
7175 {
7176 dmg = Q_irand( 1, 3 );//*self->client->ps.forcePowerLevel[FP_LIGHTNING];
7177 }
7178 if ( traceEnt->client && traceEnt->health > 0 && ( traceEnt->client->NPC_class == CLASS_DESANN || traceEnt->client->NPC_class == CLASS_LUKE ) )
7179 {//Luke and Desann can shield themselves from the attack
7180 //FIXME: shield effect or something?
7181 int parts;
7182 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 ) )
7183 {//if on a surface and not in a spin or flip, play full body resist
7184 parts = SETANIM_BOTH;
7185 }
7186 else
7187 {//play resist just in torso
7188 parts = SETANIM_TORSO;
7189 }
7190 NPC_SetAnim( traceEnt, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7191 Jedi_PlayDeflectSound( traceEnt );
7192 dmg = 0;
7193 }
7194 else if ( traceEnt->s.weapon == WP_SABER )
7195 {
7196 if ( Q_irand( 0, 1 ) )
7197 {//jedi less likely to be damaged
7198 dmg = 0;
7199 }
7200 else
7201 {
7202 dmg = 1;
7203 }
7204 }
7205 if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAK )
7206 {
7207 if ( traceEnt->client->ps.powerups[PW_GALAK_SHIELD] )
7208 {//has shield up
7209 dmg = 0;
7210 }
7211 }
7212 G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_ELECTROCUTE );
7213 if ( traceEnt->client )
7214 {
7215 if ( !Q_irand( 0, 2 ) )
7216 {
7217 G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/force/lightninghit%d.wav", Q_irand( 1, 3 ) ) ) );
7218 }
7219 traceEnt->s.powerups |= ( 1 << PW_SHOCKED );
7220
7221 // If we are dead or we are a bot, we can do the full version
7222 class_t npc_class = traceEnt->client->NPC_class;
7223 if ( traceEnt->health <= 0 || ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE ||
7224 npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_REMOTE ||
7225 npc_class == CLASS_R5D2 || npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 ||
7226 npc_class == CLASS_MARK2 || npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST ) ||
7227 npc_class == CLASS_SENTRY )
7228 {
7229 traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 4000;
7230 }
7231 else //short version
7232 {
7233 traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 500;
7234 }
7235 }
7236 }
7237 }
7238 }
7239
ForceShootLightning(gentity_t * self)7240 void ForceShootLightning( gentity_t *self )
7241 {
7242 trace_t tr;
7243 vec3_t end, forward;
7244 gentity_t *traceEnt;
7245
7246 if ( self->health <= 0 )
7247 {
7248 return;
7249 }
7250 if ( !self->s.number && cg.zoomMode )
7251 {//can't force lightning when zoomed in
7252 return;
7253 }
7254
7255 AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
7256 VectorNormalize( forward );
7257
7258 //FIXME: if lightning hits water, do water-only-flagged radius damage from that point
7259 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 )
7260 {//arc
7261 vec3_t center, mins, maxs, dir, ent_org, size, v;
7262 float radius = 512, dot, dist;
7263 gentity_t *entityList[MAX_GENTITIES];
7264 int e, numListedEntities, i;
7265
7266 VectorCopy( self->currentOrigin, center );
7267 for ( i = 0 ; i < 3 ; i++ )
7268 {
7269 mins[i] = center[i] - radius;
7270 maxs[i] = center[i] + radius;
7271 }
7272 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
7273
7274 for ( e = 0 ; e < numListedEntities ; e++ )
7275 {
7276 traceEnt = entityList[e];
7277
7278 if ( !traceEnt )
7279 continue;
7280 if ( traceEnt == self )
7281 continue;
7282 if ( traceEnt->owner == self && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals
7283 continue;
7284 if ( !traceEnt->inuse )
7285 continue;
7286 if ( !traceEnt->takedamage )
7287 continue;
7288 if ( traceEnt->health <= 0 )//no torturing corpses
7289 continue;
7290 //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter
7291 // find the distance from the edge of the bounding box
7292 for ( i = 0 ; i < 3 ; i++ )
7293 {
7294 if ( center[i] < traceEnt->absmin[i] )
7295 {
7296 v[i] = traceEnt->absmin[i] - center[i];
7297 } else if ( center[i] > traceEnt->absmax[i] )
7298 {
7299 v[i] = center[i] - traceEnt->absmax[i];
7300 } else
7301 {
7302 v[i] = 0;
7303 }
7304 }
7305
7306 VectorSubtract( traceEnt->absmax, traceEnt->absmin, size );
7307 VectorMA( traceEnt->absmin, 0.5, size, ent_org );
7308
7309 //see if they're in front of me
7310 //must be within the forward cone
7311 VectorSubtract( ent_org, center, dir );
7312 VectorNormalize( dir );
7313 if ( (dot = DotProduct( dir, forward )) < 0.5 )
7314 continue;
7315
7316 //must be close enough
7317 dist = VectorLength( v );
7318 if ( dist >= radius )
7319 {
7320 continue;
7321 }
7322
7323 //in PVS?
7324 if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) )
7325 {//must be in PVS
7326 continue;
7327 }
7328
7329 //Now check and see if we can actually hit it
7330 gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
7331 if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number )
7332 {//must have clear LOS
7333 continue;
7334 }
7335
7336 // ok, we are within the radius, add us to the incoming list
7337 //FIXME: maybe add up the ents and do more damage the less ents there are
7338 // as if we're spreading out the damage?
7339 ForceLightningDamage( self, traceEnt, dir, dist, dot, ent_org );
7340 }
7341
7342 }
7343 else
7344 {//trace-line
7345 int ignore = self->s.number;
7346 int traces = 0;
7347 vec3_t start;
7348
7349 VectorCopy( self->client->renderInfo.handLPoint, start );
7350 VectorMA( self->client->renderInfo.handLPoint, 2048, forward, end );
7351
7352 while ( traces < 10 )
7353 {//need to loop this in case we hit a Jedi who dodges the shot
7354 gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 0 );
7355 if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid )
7356 {
7357 return;
7358 }
7359
7360 traceEnt = &g_entities[tr.entityNum];
7361 //NOTE: only NPCs do this auto-dodge
7362 if ( traceEnt && traceEnt->s.number && traceEnt->s.weapon == WP_SABER )//&& traceEnt->NPC
7363 {//FIXME: need a more reliable way to know we hit a jedi?
7364 if ( !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) )
7365 {//act like we didn't even hit him
7366 VectorCopy( tr.endpos, start );
7367 ignore = tr.entityNum;
7368 traces++;
7369 continue;
7370 }
7371 }
7372 //a Jedi is not dodging this shot
7373 break;
7374 }
7375
7376 traceEnt = &g_entities[tr.entityNum];
7377 ForceLightningDamage( self, traceEnt, forward, 0, 0, tr.endpos );
7378 }
7379 }
7380
ForceJumpCharge(gentity_t * self,usercmd_t * ucmd)7381 void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd )
7382 {
7383 float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME);
7384
7385 if ( self->health <= 0 )
7386 {
7387 return;
7388 }
7389 if ( !self->s.number && cg.zoomMode )
7390 {//can't force jump when zoomed in
7391 return;
7392 }
7393
7394 //need to play sound
7395 if ( !self->client->ps.forceJumpCharge )
7396 {//FIXME: this should last only as long as the actual charge-up
7397 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jumpbuild.wav" );
7398 }
7399 //Increment
7400 self->client->ps.forceJumpCharge += forceJumpChargeInterval;
7401
7402 //clamp to max strength for current level
7403 if ( self->client->ps.forceJumpCharge > forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]] )
7404 {
7405 self->client->ps.forceJumpCharge = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]];
7406 }
7407
7408 //clamp to max available force power
7409 if ( self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] > self->client->ps.forcePower )
7410 {//can't use more than you have
7411 self->client->ps.forceJumpCharge = self->client->ps.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
7412 }
7413 //FIXME: a simple tap should always do at least a normal height's jump?
7414 }
7415
WP_GetVelocityForForceJump(gentity_t * self,vec3_t jumpVel,usercmd_t * ucmd)7416 int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd )
7417 {
7418 float pushFwd = 0, pushRt = 0;
7419 vec3_t view, forward, right;
7420 VectorCopy( self->client->ps.viewangles, view );
7421 view[0] = 0;
7422 AngleVectors( view, forward, right, NULL );
7423 if ( ucmd->forwardmove && ucmd->rightmove )
7424 {
7425 if ( ucmd->forwardmove > 0 )
7426 {
7427 pushFwd = 50;
7428 }
7429 else
7430 {
7431 pushFwd = -50;
7432 }
7433 if ( ucmd->rightmove > 0 )
7434 {
7435 pushRt = 50;
7436 }
7437 else
7438 {
7439 pushRt = -50;
7440 }
7441 }
7442 else if ( ucmd->forwardmove || ucmd->rightmove )
7443 {
7444 if ( ucmd->forwardmove > 0 )
7445 {
7446 pushFwd = 100;
7447 }
7448 else if ( ucmd->forwardmove < 0 )
7449 {
7450 pushFwd = -100;
7451 }
7452 else if ( ucmd->rightmove > 0 )
7453 {
7454 pushRt = 100;
7455 }
7456 else if ( ucmd->rightmove < 0 )
7457 {
7458 pushRt = -100;
7459 }
7460 }
7461 VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel );
7462 VectorMA( self->client->ps.velocity, pushRt, right, jumpVel );
7463 jumpVel[2] += self->client->ps.forceJumpCharge;//forceJumpStrength;
7464 if ( pushFwd > 0 && self->client->ps.forceJumpCharge > 200 )
7465 {
7466 return FJ_FORWARD;
7467 }
7468 else if ( pushFwd < 0 && self->client->ps.forceJumpCharge > 200 )
7469 {
7470 return FJ_BACKWARD;
7471 }
7472 else if ( pushRt > 0 && self->client->ps.forceJumpCharge > 200 )
7473 {
7474 return FJ_RIGHT;
7475 }
7476 else if ( pushRt < 0 && self->client->ps.forceJumpCharge > 200 )
7477 {
7478 return FJ_LEFT;
7479 }
7480 else
7481 {//FIXME: jump straight up anim
7482 return FJ_UP;
7483 }
7484 }
7485
ForceJump(gentity_t * self,usercmd_t * ucmd)7486 void ForceJump( gentity_t *self, usercmd_t *ucmd )
7487 {
7488 if ( self->client->ps.forcePowerDuration[FP_LEVITATION] > level.time )
7489 {
7490 return;
7491 }
7492 if ( !WP_ForcePowerUsable( self, FP_LEVITATION, 0 ) )
7493 {
7494 return;
7495 }
7496 if ( self->s.groundEntityNum == ENTITYNUM_NONE )
7497 {
7498 return;
7499 }
7500 if ( self->client->ps.pm_flags&PMF_JUMP_HELD )
7501 {
7502 return;
7503 }
7504 if ( self->health <= 0 )
7505 {
7506 return;
7507 }
7508 if ( !self->s.number && (cg.zoomMode || in_camera) )
7509 {//can't force jump when zoomed in or in cinematic
7510 return;
7511 }
7512 if ( self->client->ps.saberLockTime > level.time )
7513 {//FIXME: can this be a way to break out?
7514 return;
7515 }
7516
7517 G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" );
7518
7519 float forceJumpChargeInterval = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME);
7520
7521 int anim;
7522 vec3_t jumpVel;
7523
7524 switch( WP_GetVelocityForForceJump( self, jumpVel, ucmd ) )
7525 {
7526 case FJ_FORWARD:
7527 if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7528 {//can't do acrobatics
7529 anim = BOTH_FORCEJUMP1;
7530 }
7531 else
7532 {
7533 anim = BOTH_FLIP_F;
7534 }
7535 break;
7536 case FJ_BACKWARD:
7537 if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7538 {//can't do acrobatics
7539 anim = BOTH_FORCEJUMPBACK1;
7540 }
7541 else
7542 {
7543 anim = BOTH_FLIP_B;
7544 }
7545 break;
7546 case FJ_RIGHT:
7547 if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7548 {//can't do acrobatics
7549 anim = BOTH_FORCEJUMPRIGHT1;
7550 }
7551 else
7552 {
7553 anim = BOTH_FLIP_R;
7554 }
7555 break;
7556 case FJ_LEFT:
7557 if ( self->NPC && self->NPC->rank != RANK_CREWMAN && self->NPC->rank <= RANK_LT_JG )
7558 {//can't do acrobatics
7559 anim = BOTH_FORCEJUMPLEFT1;
7560 }
7561 else
7562 {
7563 anim = BOTH_FLIP_L;
7564 }
7565 break;
7566 default:
7567 case FJ_UP:
7568 anim = BOTH_JUMP1;
7569 break;
7570 }
7571
7572 int parts = SETANIM_BOTH;
7573 if ( self->client->ps.weaponTime )
7574 {//FIXME: really only care if we're in a saber attack anim.. maybe trail length?
7575 parts = SETANIM_LEGS;
7576 }
7577
7578 NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7579
7580 //FIXME: sound effect
7581 self->client->ps.forceJumpZStart = self->currentOrigin[2];//remember this for when we land
7582 VectorCopy( jumpVel, self->client->ps.velocity );
7583 //wasn't allowing them to attack when jumping, but that was annoying
7584 //self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
7585
7586 WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] );
7587 //self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime;
7588 self->client->ps.forceJumpCharge = 0;
7589 }
7590
WP_ForcePowerRegenerate(gentity_t * self,int overrideAmt)7591 void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt )
7592 {
7593 if ( !self->client )
7594 {
7595 return;
7596 }
7597
7598 if ( self->client->ps.forcePower < self->client->ps.forcePowerMax )
7599 {
7600 if ( overrideAmt )
7601 {
7602 self->client->ps.forcePower += overrideAmt;
7603 }
7604 else
7605 {
7606 self->client->ps.forcePower++;
7607 }
7608 if ( self->client->ps.forcePower > self->client->ps.forcePowerMax )
7609 {
7610 self->client->ps.forcePower = self->client->ps.forcePowerMax;
7611 }
7612 }
7613 }
7614
WP_ForcePowerDrain(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7615 void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7616 {
7617 if ( self->NPC )
7618 {//For now, NPCs have infinite force power
7619 return;
7620 }
7621 //take away the power
7622 int drain = overrideAmt;
7623 if ( !drain )
7624 {
7625 drain = forcePowerNeeded[forcePower];
7626 }
7627 if ( !drain )
7628 {
7629 return;
7630 }
7631 self->client->ps.forcePower -= drain;
7632 if ( self->client->ps.forcePower < 0 )
7633 {
7634 self->client->ps.forcePower = 0;
7635 }
7636 }
7637
WP_ForcePowerStart(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7638 void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7639 {
7640 int duration = 0;
7641
7642 //FIXME: debounce some of these
7643
7644 //and it in
7645 //set up duration time
7646 switch( (int)forcePower )
7647 {
7648 case FP_HEAL:
7649 self->client->ps.forcePowersActive |= ( 1 << forcePower );
7650 self->client->ps.forceHealCount = 0;
7651 break;
7652 case FP_LEVITATION:
7653 self->client->ps.forcePowersActive |= ( 1 << forcePower );
7654 break;
7655 case FP_SPEED:
7656 //duration is always 5 seconds, player time
7657 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...
7658 self->client->ps.forcePowersActive |= ( 1 << forcePower );
7659 self->s.loopSound = G_SoundIndex( "sound/weapons/force/speedloop.wav" );
7660 break;
7661 case FP_PUSH:
7662 break;
7663 case FP_PULL:
7664 break;
7665 case FP_TELEPATHY:
7666 break;
7667 case FP_GRIP:
7668 duration = 1000;
7669 self->client->ps.forcePowersActive |= ( 1 << forcePower );
7670 break;
7671 case FP_LIGHTNING:
7672 duration = overrideAmt;
7673 overrideAmt = 0;
7674 self->client->ps.forcePowersActive |= ( 1 << forcePower );
7675 break;
7676 default:
7677 break;
7678 }
7679 if ( duration )
7680 {
7681 self->client->ps.forcePowerDuration[forcePower] = level.time + duration;
7682 }
7683 else
7684 {
7685 self->client->ps.forcePowerDuration[forcePower] = 0;
7686 }
7687 self->client->ps.forcePowerDebounce[forcePower] = 0;
7688
7689 WP_ForcePowerDrain( self, forcePower, overrideAmt );
7690
7691 if ( !self->s.number )
7692 {
7693 self->client->sess.missionStats.forceUsed[(int)forcePower]++;
7694 }
7695 }
7696
WP_ForcePowerAvailable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7697 qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7698 {
7699 if ( forcePower == FP_LEVITATION )
7700 {
7701 return qtrue;
7702 }
7703 int drain = overrideAmt?overrideAmt:forcePowerNeeded[forcePower];
7704 if ( !drain )
7705 {
7706 return qtrue;
7707 }
7708 if ( self->client->ps.forcePower < drain )
7709 {
7710 //G_AddEvent( self, EV_NOAMMO, 0 );
7711 return qfalse;
7712 }
7713 return qtrue;
7714 }
7715
7716 extern void CG_PlayerLockedWeaponSpeech( int jumping );
WP_ForcePowerUsable(gentity_t * self,forcePowers_t forcePower,int overrideAmt)7717 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt )
7718 {
7719
7720 if ( !(self->client->ps.forcePowersKnown & ( 1 << forcePower )) )
7721 {//don't know this power
7722 return qfalse;
7723 }
7724
7725 if ( self->client->ps.forcePowerLevel[forcePower] <= 0 )
7726 {//can't use this power
7727 return qfalse;
7728 }
7729
7730 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
7731 {
7732 CG_PlayerLockedWeaponSpeech( qfalse );
7733 return qfalse;
7734 }
7735
7736 if ( (self->client->ps.forcePowersActive & ( 1 << forcePower )) )
7737 {//already using this power
7738 return qfalse;
7739 }
7740 /*
7741 if ( !self->client->ps.forcePowerLevel[(int)(forcePower)] )
7742 {
7743 return qfalse;
7744 }
7745 */
7746 if ( self->client->NPC_class == CLASS_ATST )
7747 {//Doh! No force powers in an AT-ST!
7748 return qfalse;
7749 }
7750 if ( self->client->ps.vehicleModel != 0 )
7751 {//Doh! No force powers when flying a vehicle!
7752 return qfalse;
7753 }
7754 if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD )
7755 {//Doh! No force powers when controlling an NPC
7756 return qfalse;
7757 }
7758 if ( self->client->ps.eFlags & EF_LOCKED_TO_WEAPON )
7759 {//Doh! No force powers when in an emplaced gun!
7760 return qfalse;
7761 }
7762
7763 return WP_ForcePowerAvailable( self, forcePower, overrideAmt );
7764 }
7765
WP_ForcePowerStop(gentity_t * self,forcePowers_t forcePower)7766 void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower )
7767 {
7768 gentity_t *gripEnt;
7769
7770 self->client->ps.forcePowersActive &= ~( 1 << forcePower );
7771
7772 switch( (int)forcePower )
7773 {
7774 case FP_HEAL:
7775 //if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 )
7776 {//wasn't an instant heal and heal is now done
7777 if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 )
7778 {//if in meditation pose, must come out of it
7779 //FIXME: BOTH_FORCEHEAL_STOP
7780 if ( self->client->ps.legsAnim == BOTH_FORCEHEAL_START )
7781 {
7782 NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7783 }
7784 if ( self->client->ps.torsoAnim == BOTH_FORCEHEAL_START )
7785 {
7786 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7787 }
7788 self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in
7789 self->client->ps.saberBlocked = BLOCKED_NONE;
7790 }
7791 }
7792 break;
7793 case FP_LEVITATION:
7794 self->client->ps.forcePowerDebounce[FP_LEVITATION] = 0;
7795 break;
7796 case FP_SPEED:
7797 if ( !self->s.number )
7798 {//player using force speed
7799 if ( g_timescale->value != 1.0 )
7800 {
7801 gi.cvar_set("timescale", "1");
7802 }
7803 }
7804 //FIXME: reset my current anim, keeping current frame, but with proper anim speed
7805 // otherwise, the anim will continue playing at high speed
7806 self->s.loopSound = 0;
7807 break;
7808 case FP_PUSH:
7809 break;
7810 case FP_PULL:
7811 break;
7812 case FP_TELEPATHY:
7813 break;
7814 case FP_GRIP:
7815 if ( self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
7816 {
7817 gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
7818 if ( gripEnt )
7819 {
7820 gripEnt->s.loopSound = 0;
7821 if ( gripEnt->client )
7822 {
7823 gripEnt->client->ps.eFlags &= ~EF_FORCE_GRIPPED;
7824 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
7825 {//sanity-cap the velocity
7826 float gripVel = VectorNormalize( gripEnt->client->ps.velocity );
7827 if ( gripVel > 500.0f )
7828 {
7829 gripVel = 500.0f;
7830 }
7831 VectorScale( gripEnt->client->ps.velocity, gripVel, gripEnt->client->ps.velocity );
7832 }
7833
7834 //FIXME: they probably dropped their weapon, should we make them flee? Or should AI handle no-weapon behavior?
7835 if ( gripEnt->health > 0 )
7836 {
7837 int holdTime = 500;
7838 G_AddEvent( gripEnt, EV_WATER_CLEAR, 0 );
7839 if ( gripEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time )
7840 {//they probably pushed out of it
7841 holdTime = 0;
7842 }
7843 else if ( gripEnt->s.weapon == WP_SABER )
7844 {//jedi recover faster
7845 holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*200;
7846 }
7847 else
7848 {
7849 holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*500;
7850 }
7851 //stop the anims soon, keep them locked in place for a bit
7852 if ( gripEnt->client->ps.torsoAnim == BOTH_CHOKE1 || gripEnt->client->ps.torsoAnim == BOTH_CHOKE3 )
7853 {//stop choking anim on torso
7854 if ( gripEnt->client->ps.torsoAnimTimer > holdTime )
7855 {
7856 gripEnt->client->ps.torsoAnimTimer = holdTime;
7857 }
7858 }
7859 if ( gripEnt->client->ps.legsAnim == BOTH_CHOKE1 || gripEnt->client->ps.legsAnim == BOTH_CHOKE3 )
7860 {//stop choking anim on legs
7861 gripEnt->client->ps.legsAnimTimer = 0;
7862 if ( holdTime )
7863 {
7864 //lock them in place for a bit
7865 gripEnt->client->ps.pm_time = gripEnt->client->ps.torsoAnimTimer;
7866 gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
7867 if ( gripEnt->s.number )
7868 {//NPC
7869 gripEnt->painDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
7870 }
7871 else
7872 {//player
7873 gripEnt->aimDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer;
7874 }
7875 }
7876 }
7877 if ( gripEnt->NPC )
7878 {
7879 if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
7880 {//not falling to their death
7881 gripEnt->NPC->nextBStateThink = level.time + holdTime;
7882 }
7883 //if still alive after stopped gripping, let them wake others up
7884 G_AngerAlert( gripEnt );
7885 }
7886 }
7887 }
7888 else
7889 {
7890 gripEnt->s.eFlags &= ~EF_FORCE_GRIPPED;
7891 if ( gripEnt->s.eType == ET_MISSILE )
7892 {//continue normal movement
7893 if ( gripEnt->s.weapon == WP_THERMAL )
7894 {
7895 gripEnt->s.pos.trType = TR_INTERPOLATE;
7896 }
7897 else
7898 {
7899 gripEnt->s.pos.trType = TR_LINEAR;//FIXME: what about gravity-effected projectiles?
7900 }
7901 VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
7902 gripEnt->s.pos.trTime = level.time;
7903 }
7904 else
7905 {//drop it
7906 gripEnt->e_ThinkFunc = thinkF_G_RunObject;
7907 gripEnt->nextthink = level.time + FRAMETIME;
7908 gripEnt->s.pos.trType = TR_GRAVITY;
7909 VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
7910 gripEnt->s.pos.trTime = level.time;
7911 }
7912 }
7913 }
7914 self->s.loopSound = 0;
7915 self->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
7916 }
7917 if ( self->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD )
7918 {
7919 NPC_SetAnim( self, BOTH_FORCEGRIP_RELEASE, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7920 }
7921 break;
7922 case FP_LIGHTNING:
7923 if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_HOLD
7924 || self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
7925 {
7926 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
7927 }
7928 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 )
7929 {//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal)
7930 self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;//FIXME: define?
7931 }
7932 else
7933 {//stop the looping sound
7934 self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 1000;//FIXME: define?
7935 self->s.loopSound = 0;
7936 }
7937 break;
7938 default:
7939 break;
7940 }
7941 }
7942
7943 extern qboolean PM_ForceJumpingUp( gentity_t *gent );
WP_ForcePowerRun(gentity_t * self,forcePowers_t forcePower,usercmd_t * cmd)7944 static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd )
7945 {
7946 float speed, newSpeed;
7947 gentity_t *gripEnt;
7948 vec3_t angles, dir, gripOrg, gripEntOrg;
7949 float dist;
7950 extern usercmd_t ucmd;
7951
7952 switch( (int)forcePower )
7953 {
7954 case FP_HEAL:
7955 if ( self->client->ps.forceHealCount >= MAX_FORCE_HEAL || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] )
7956 {//fully healed or used up all 25
7957 if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
7958 {
7959 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) );
7960 }
7961 WP_ForcePowerStop( self, forcePower );
7962 }
7963 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) ) )
7964 {//attacked or was hit while healing...
7965 //stop healing
7966 WP_ForcePowerStop( self, forcePower );
7967 }
7968 else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) )
7969 {//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used
7970 //stop healing
7971 WP_ForcePowerStop( self, forcePower );
7972 }
7973 else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time )
7974 {//time to heal again
7975 if ( WP_ForcePowerAvailable( self, forcePower, 4 ) )
7976 {//have available power
7977 self->health++;
7978 self->client->ps.forceHealCount++;
7979 if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 )
7980 {
7981 self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + 50;
7982 }
7983 else
7984 {
7985 self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + FORCE_HEAL_INTERVAL;
7986 }
7987 WP_ForcePowerDrain( self, forcePower, 4 );
7988 }
7989 else
7990 {//stop
7991 WP_ForcePowerStop( self, forcePower );
7992 }
7993 }
7994 break;
7995 case FP_LEVITATION:
7996 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart )
7997 {//done with jump
7998 WP_ForcePowerStop( self, forcePower );
7999 }
8000 else
8001 {
8002 if ( PM_ForceJumpingUp( self ) )
8003 {//holding jump in air
8004 if ( cmd->upmove > 10 )
8005 {//still trying to go up
8006 if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) )
8007 {
8008 if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time )
8009 {
8010 WP_ForcePowerDrain( self, FP_LEVITATION, 5 );
8011 self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100;
8012 }
8013 self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION );
8014 self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands
8015 }
8016 else
8017 {//cut the jump short
8018 WP_ForcePowerStop( self, forcePower );
8019 }
8020 }
8021 else
8022 {//cut the jump short
8023 WP_ForcePowerStop( self, forcePower );
8024 }
8025 }
8026 else
8027 {
8028 WP_ForcePowerStop( self, forcePower );
8029 }
8030 }
8031 break;
8032 case FP_SPEED:
8033 speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]];
8034 if ( !self->s.number )
8035 {//player using force speed
8036 gi.cvar_set("timescale", va("%4.2f", speed));
8037 if ( g_timescale->value > speed )
8038 {
8039 newSpeed = g_timescale->value - 0.05;
8040 if ( newSpeed < speed )
8041 {
8042 newSpeed = speed;
8043 }
8044 gi.cvar_set("timescale", va("%4.2f", newSpeed));
8045 }
8046 }
8047 break;
8048 case FP_PUSH:
8049 break;
8050 case FP_PULL:
8051 break;
8052 case FP_TELEPATHY:
8053 break;
8054 case FP_GRIP:
8055 if ( !WP_ForcePowerAvailable( self, FP_GRIP, 0 )
8056 || (self->client->ps.forcePowerLevel[FP_GRIP]>FORCE_LEVEL_1&&!self->s.number&&!(cmd->buttons&BUTTON_FORCEGRIP)) )
8057 {
8058 WP_ForcePowerStop( self, FP_GRIP );
8059 return;
8060 }
8061 else if ( self->client->ps.forceGripEntityNum >= 0 && self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD )
8062 {
8063 gripEnt = &g_entities[self->client->ps.forceGripEntityNum];
8064
8065 if ( !gripEnt || (gripEnt->health <= 0&&gripEnt->takedamage) )//FIXME: what about things that never had health or lose takedamage when they die?
8066 {//either invalid ent, or dead ent
8067 WP_ForcePowerStop( self, FP_GRIP );
8068 return;
8069 }
8070 else if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_1
8071 && gripEnt->client
8072 && gripEnt->client->ps.groundEntityNum == ENTITYNUM_NONE )
8073 {
8074 WP_ForcePowerStop( self, FP_GRIP );
8075 return;
8076 }
8077 else if ( gripEnt->s.weapon == WP_SABER && gripEnt->NPC && gripEnt->client && gripEnt->client->ps.forcePowersKnown&(1<<FP_PUSH) && !Q_irand( 0, 100-(gripEnt->NPC->stats.evasion*10)-(g_spskill->integer*10) ) )
8078 {//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time?
8079 ForceThrow( gripEnt, qfalse );
8080 //FIXME: I need to go into some pushed back anim...
8081 WP_ForcePowerStop( self, FP_GRIP );
8082 return;
8083 }
8084 else
8085 {
8086 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8087 {//holding it
8088 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8089 }
8090 //get their org
8091 VectorCopy( self->client->ps.viewangles, angles );
8092 angles[0] -= 10;
8093 AngleVectors( angles, dir, NULL, NULL );
8094 if ( gripEnt->client )
8095 {//move
8096 VectorCopy( gripEnt->client->renderInfo.headPoint, gripEntOrg );
8097 }
8098 else
8099 {
8100 VectorCopy( gripEnt->currentOrigin, gripEntOrg );
8101 }
8102
8103 //how far are they
8104 dist = Distance( self->client->renderInfo.handLPoint, gripEntOrg );
8105 if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 &&
8106 (!InFront( gripEntOrg, self->client->renderInfo.handLPoint, self->client->ps.viewangles, 0.3f ) ||
8107 DistanceSquared( gripEntOrg, self->client->renderInfo.handLPoint ) > FORCE_GRIP_DIST_SQUARED))
8108 {//must face them
8109 WP_ForcePowerStop( self, FP_GRIP );
8110 return;
8111 }
8112
8113 //check for lift or carry
8114 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8115 {//carry
8116 //cap dist
8117 if ( dist > 256 )
8118 {
8119 dist = 256;
8120 }
8121 else if ( dist < 128 )
8122 {
8123 dist = 128;
8124 }
8125 VectorMA( self->client->renderInfo.handLPoint, dist, dir, gripOrg );
8126 }
8127 else if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8128 {//just lift
8129 VectorCopy( self->client->ps.forceGripOrg, gripOrg );
8130 }
8131 else
8132 {
8133 VectorCopy( gripEnt->currentOrigin, gripOrg );
8134 }
8135 //now move them
8136 if ( gripEnt->client )
8137 {
8138 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8139 {//level 1 just holds them
8140 VectorSubtract( gripOrg, gripEntOrg, gripEnt->client->ps.velocity );
8141 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8142 {//level 2 just lifts them
8143 float gripDist = VectorNormalize( gripEnt->client->ps.velocity )/3.0f;
8144 if ( gripDist < 5.0f )
8145 {
8146 gripDist = 5.0f;
8147 }
8148 VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity );
8149 }
8150 }
8151 //stop them from thinking
8152 gripEnt->client->ps.pm_time = 2000;
8153 gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
8154 if ( gripEnt->NPC )
8155 {
8156 if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
8157 {//not falling to their death
8158 gripEnt->NPC->nextBStateThink = level.time + 2000;
8159 }
8160 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8161 {//level 1 just holds them
8162 vectoangles( dir, angles );
8163 gripEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180);
8164 gripEnt->NPC->desiredPitch = -angles[PITCH];
8165 SaveNPCGlobals();
8166 SetNPCGlobals( gripEnt );
8167 NPC_UpdateAngles( qtrue, qtrue );
8168 gripEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0];
8169 gripEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1];
8170 gripEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2];
8171 RestoreNPCGlobals();
8172 //FIXME: why does he turn back to his original angles once he dies or is let go?
8173 }
8174 }
8175 else if ( !gripEnt->s.number )
8176 {
8177 //vectoangles( dir, angles );
8178 //gripEnt->client->ps.viewangles[0] = -angles[0];
8179 //gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180);
8180 gripEnt->enemy = self;
8181 NPC_SetLookTarget( gripEnt, self->s.number, level.time+1000 );
8182 }
8183
8184 gripEnt->client->ps.eFlags |= EF_FORCE_GRIPPED;
8185 //dammit! Make sure that saber stays off!
8186 gripEnt->client->ps.saberActive = qfalse;
8187 }
8188 else
8189 {//move
8190 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 )
8191 {//level 1 just holds them
8192 VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase );
8193 VectorSubtract( gripOrg, gripEntOrg, gripEnt->s.pos.trDelta );
8194 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8195 {//level 2 just lifts them
8196 VectorScale( gripEnt->s.pos.trDelta, 10, gripEnt->s.pos.trDelta );
8197 }
8198 gripEnt->s.pos.trType = TR_LINEAR;
8199 gripEnt->s.pos.trTime = level.time;
8200 }
8201
8202 gripEnt->s.eFlags |= EF_FORCE_GRIPPED;
8203 }
8204
8205 //Shouldn't this be discovered?
8206 //AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 );
8207 AddSightEvent( self, gripOrg, 128, AEL_DISCOVERED, 20 );
8208
8209 if ( self->client->ps.forcePowerDebounce[FP_GRIP] < level.time )
8210 {
8211 //GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH );
8212 gripEnt->painDebounceTime = 0;
8213 G_Damage( gripEnt, self, self, dir, gripOrg, forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]], DAMAGE_NO_ARMOR, MOD_CRUSH );//MOD_???
8214 if ( gripEnt->s.number )
8215 {
8216 if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 )
8217 {//do damage faster at level 3
8218 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 150, 750 );
8219 }
8220 else
8221 {
8222 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 250, 1000 );
8223 }
8224 }
8225 else
8226 {//player takes damage faster
8227 self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 100, 600 );
8228 }
8229 if ( forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]] > 0 )
8230 {//no damage at level 1
8231 WP_ForcePowerDrain( self, FP_GRIP, 3 );
8232 }
8233 }
8234 else
8235 {
8236 //WP_ForcePowerDrain( self, FP_GRIP, 0 );
8237 if ( !gripEnt->enemy )
8238 {
8239 G_SetEnemy( gripEnt, self );
8240 }
8241 }
8242 if ( gripEnt->client && gripEnt->health > 0 )
8243 {
8244 int anim = BOTH_CHOKE3; //left-handed choke
8245 if ( gripEnt->client->ps.weapon == WP_NONE || gripEnt->client->ps.weapon == WP_MELEE )
8246 {
8247 anim = BOTH_CHOKE1; //two-handed choke
8248 }
8249 if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 )
8250 {//still on ground, only set anim on torso
8251 NPC_SetAnim( gripEnt, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8252 }
8253 else
8254 {//in air, set on whole body
8255 NPC_SetAnim( gripEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8256 }
8257 gripEnt->painDebounceTime = level.time + 2000;
8258 }
8259 }
8260 }
8261 break;
8262 case FP_LIGHTNING:
8263 if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 )
8264 {//higher than level 1
8265 if ( cmd->buttons & BUTTON_FORCE_LIGHTNING )
8266 {//holding it keeps it going
8267 self->client->ps.forcePowerDuration[FP_LIGHTNING] = level.time + 500;
8268 if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START )
8269 {
8270 if ( !self->client->ps.torsoAnimTimer )
8271 {
8272 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8273 }
8274 else
8275 {
8276 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8277 }
8278 }
8279 else
8280 {
8281 NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
8282 }
8283 }
8284 }
8285 if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) )
8286 {
8287 WP_ForcePowerStop( self, forcePower );
8288 }
8289 else
8290 {
8291 ForceShootLightning( self );
8292 WP_ForcePowerDrain( self, forcePower, 0 );
8293 }
8294 break;
8295 default:
8296 break;
8297 }
8298 }
8299
WP_ForcePowersUpdate(gentity_t * self,usercmd_t * ucmd)8300 void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd )
8301 {
8302 qboolean usingForce = qfalse;
8303 int i;
8304 //see if any force powers are running
8305 if ( !self )
8306 {
8307 return;
8308 }
8309 if ( !self->client )
8310 {
8311 return;
8312 }
8313
8314 if ( self->health <= 0 )
8315 {//if dead, deactivate any active force powers
8316 for ( i = 0; i < NUM_FORCE_POWERS; i++ )
8317 {
8318 if ( self->client->ps.forcePowerDuration[i] || (self->client->ps.forcePowersActive&( 1 << i )) )
8319 {
8320 WP_ForcePowerStop( self, (forcePowers_t)i );
8321 self->client->ps.forcePowerDuration[i] = 0;
8322 }
8323 }
8324 return;
8325 }
8326
8327 if ( !self->s.number )
8328 {//player uses different kind of force-jump
8329 }
8330 else
8331 {
8332 /*
8333 if ( ucmd->buttons & BUTTON_FORCEJUMP )
8334 {//just charging up
8335 ForceJumpCharge( self, ucmd );
8336 }
8337 else */
8338 if ( self->client->ps.forceJumpCharge )
8339 {//let go of charge button, have charge
8340 //if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land.
8341 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE
8342 && !PM_SwimmingAnim( self->client->ps.legsAnim ) )
8343 {//FIXME: stop sound?
8344 //self->client->ps.forceJumpCharge = 0;
8345 //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.
8346 }
8347 else
8348 {//still on ground, so jump
8349 ForceJump( self, ucmd );
8350 return;
8351 }
8352 }
8353 }
8354
8355 if ( ucmd->buttons & BUTTON_FORCEGRIP )
8356 {
8357 ForceGrip( self );
8358 }
8359
8360 if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING )
8361 {
8362 ForceLightning( self );
8363 }
8364
8365 for ( i = 0; i < NUM_FORCE_POWERS; i++ )
8366 {
8367 if ( self->client->ps.forcePowerDuration[i] )
8368 {
8369 if ( self->client->ps.forcePowerDuration[i] < level.time )
8370 {
8371 if ( (self->client->ps.forcePowersActive&( 1 << i )) )
8372 {//turn it off
8373 WP_ForcePowerStop( self, (forcePowers_t)i );
8374 }
8375 self->client->ps.forcePowerDuration[i] = 0;
8376 }
8377 }
8378 if ( (self->client->ps.forcePowersActive&( 1 << i )) )
8379 {
8380 usingForce = qtrue;
8381 WP_ForcePowerRun( self, (forcePowers_t)i, ucmd );
8382 }
8383 }
8384 if ( self->client->ps.saberInFlight )
8385 {//don't regen force power while throwing saber
8386 if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0
8387 {//
8388 if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR )
8389 {//fell to the ground and we're trying to pull it back
8390 usingForce = qtrue;
8391 }
8392 }
8393 }
8394 if ( !usingForce )
8395 {//when not using the force, regenerate at 10 points per second
8396 if ( self->client->ps.forcePowerRegenDebounceTime < level.time )
8397 {
8398 WP_ForcePowerRegenerate( self, 0 );
8399 self->client->ps.forcePowerRegenDebounceTime = level.time + 100;
8400 }
8401 }
8402 }
8403
WP_InitForcePowers(gentity_t * ent)8404 void WP_InitForcePowers( gentity_t *ent )
8405 {
8406 if ( !ent || !ent->client )
8407 {
8408 return;
8409 }
8410
8411 if( ent->client->NPC_class == CLASS_TAVION ||
8412 ent->client->NPC_class == CLASS_REBORN ||
8413 ent->client->NPC_class == CLASS_DESANN ||
8414 ent->client->NPC_class == CLASS_SHADOWTROOPER ||
8415 ent->client->NPC_class == CLASS_JEDI ||
8416 ent->client->NPC_class == CLASS_LUKE )
8417 {//an NPC jedi
8418 ent->client->ps.forcePower = ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
8419 ent->client->ps.forcePowerRegenDebounceTime = 0;
8420 ent->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
8421
8422 if ( ent->client->NPC_class == CLASS_DESANN )
8423 {
8424 ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8425 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8426 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8427 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_3;
8428 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
8429 ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_3;
8430 ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_3;
8431 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8432 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8433 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8434 }
8435 else if ( ent->client->NPC_class == CLASS_LUKE )
8436 {
8437 ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8438 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8439 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8440 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_3;
8441 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
8442 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8443 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8444 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8445 }
8446 else if ( ent->client->NPC_class == CLASS_TAVION || ( ent->client->NPC_class == CLASS_JEDI && ent->NPC->rank == RANK_COMMANDER ) )
8447 {//Tavia or trainer Jedi
8448 ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8449 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8450 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8451 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_2;
8452 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3;
8453 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8454 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8455 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8456 if ( ent->client->NPC_class == CLASS_TAVION )
8457 {
8458 ent->client->ps.forcePowersKnown |= ( 1 << FP_LIGHTNING)|( 1 << FP_GRIP );
8459 ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_2;
8460 ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8461 }
8462 }
8463 else if ( ent->client->NPC_class == CLASS_SHADOWTROOPER )
8464 {//Shadow Trooper
8465 ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8466 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8467 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3;
8468 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3;
8469 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_2;
8470 ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8471 ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
8472 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3;
8473 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8474 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8475 }
8476 else if ( ent->NPC->rank == RANK_LT || ent->client->NPC_class == CLASS_JEDI )
8477 {//Reborn Boss or ally Jedi
8478 ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SABERTHROW)|( 1 << FP_GRIP )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8479 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8480 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8481 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;
8482 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
8483 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
8484 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8485 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8486 if ( ent->client->NPC_class != CLASS_JEDI )
8487 {
8488 ent->client->ps.forcePowersKnown |= ( 1 << FP_GRIP );
8489 ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8490 }
8491 }
8492 else if ( ent->NPC->rank == RANK_LT_JG )
8493 {//Reborn Fencer
8494 ent->client->ps.forcePowersKnown = ( 1 << FP_PUSH )|( 1 << FP_SABERTHROW )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8495 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;//FIXME: maybe make him only use it in defense- to throw away grenades
8496 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8497 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8498 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8499 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_2;
8500 }
8501 else if ( ent->NPC->rank == RANK_ENSIGN )
8502 {//Force User
8503 ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8504 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_2;
8505 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
8506 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8507 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
8508 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
8509 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1;
8510 }
8511 else if ( ent->NPC->rank == RANK_CREWMAN )
8512 {//Acrobat
8513 ent->client->ps.forcePowersKnown = ( 1 << FP_LEVITATION )|( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8514 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8515 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8516 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
8517 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
8518 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8519 }
8520 else if ( ent->NPC->rank == RANK_CIVILIAN )
8521 {//Grunt (NOTE: grunt turns slower and has less health)
8522 ent->client->ps.forcePowersKnown = ( 1 << FP_SPEED)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE );
8523 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1;
8524 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_1;
8525 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1;
8526 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1;
8527 }
8528 }
8529 else
8530 {//player
8531 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 );
8532 ent->client->ps.forcePower = ent->client->ps.forcePowerMax = FORCE_POWER_MAX;
8533 ent->client->ps.forcePowerRegenDebounceTime = 0;
8534 ent->client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2;
8535 ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2;
8536 ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1;
8537 ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1;
8538 ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2;
8539 ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2;
8540 ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1;
8541 ent->client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2;
8542 ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3;
8543 ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3;
8544 if ( ent->NPC )
8545 {//???
8546 ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_3;
8547 }
8548 else
8549 {
8550 ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2;
8551 }
8552 ent->client->ps.forceGripEntityNum = ENTITYNUM_NONE;
8553 }
8554 }
8555