1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 // g_weapon.c
25 // perform the server side effects of a weapon firing
26
27 #include "g_local.h"
28 #include "g_functions.h"
29 #include "anims.h"
30 #include "b_local.h"
31 #include "wp_saber.h"
32 #include "g_vehicles.h"
33 #include "w_local.h"
34 #include "../cgame/cg_local.h"
35
36 vec3_t forwardVec, vrightVec, up;
37 vec3_t muzzle;
38
39 gentity_t *ent_list[MAX_GENTITIES];
40 extern cvar_t *g_debugMelee;
41
42 // some naughty little things that are used cg side
43 int g_rocketLockEntNum = ENTITYNUM_NONE;
44 int g_rocketLockTime = 0;
45 int g_rocketSlackTime = 0;
46
47 // Weapon Helper Functions
48 float weaponSpeed[WP_NUM_WEAPONS][2] =
49 {
50 { 0,0 },//WP_NONE,
51 { 0,0 },//WP_SABER, // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste.
52 { BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL },//WP_BLASTER_PISTOL,
53 { BLASTER_VELOCITY,BLASTER_VELOCITY },//WP_BLASTER,
54 { Q3_INFINITE,Q3_INFINITE },//WP_DISRUPTOR,
55 { BOWCASTER_VELOCITY,BOWCASTER_VELOCITY },//WP_BOWCASTER,
56 { REPEATER_VELOCITY,REPEATER_ALT_VELOCITY },//WP_REPEATER,
57 { DEMP2_VELOCITY,DEMP2_ALT_RANGE },//WP_DEMP2,
58 { FLECHETTE_VEL,FLECHETTE_MINE_VEL },//WP_FLECHETTE,
59 { ROCKET_VELOCITY,ROCKET_ALT_VELOCITY },//WP_ROCKET_LAUNCHER,
60 { TD_VELOCITY,TD_ALT_VELOCITY },//WP_THERMAL,
61 { 0,0 },//WP_TRIP_MINE,
62 { 0,0 },//WP_DET_PACK,
63 { CONC_VELOCITY,Q3_INFINITE },//WP_CONCUSSION,
64 { 0,0 },//WP_MELEE, // Any ol' melee attack
65 { 0,0 },//WP_STUN_BATON,
66 { BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL },//WP_BRYAR_PISTOL,
67 { EMPLACED_VEL,EMPLACED_VEL },//WP_EMPLACED_GUN,
68 { BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL },//WP_BOT_LASER, // Probe droid - Laser blast
69 { 0,0 },//WP_TURRET, // turret guns
70 { ATST_MAIN_VEL,ATST_MAIN_VEL },//WP_ATST_MAIN,
71 { ATST_SIDE_MAIN_VELOCITY,ATST_SIDE_ALT_NPC_VELOCITY },//WP_ATST_SIDE,
72 { EMPLACED_VEL,EMPLACED_VEL },//WP_TIE_FIGHTER,
73 { EMPLACED_VEL,REPEATER_ALT_VELOCITY },//WP_RAPID_FIRE_CONC,
74 { 0,0 },//WP_JAWA,
75 { TUSKEN_RIFLE_VEL,TUSKEN_RIFLE_VEL },//WP_TUSKEN_RIFLE,
76 { 0,0 },//WP_TUSKEN_STAFF,
77 { 0,0 },//WP_SCEPTER,
78 { 0,0 },//WP_NOGHRI_STICK,
79 };
80
WP_SpeedOfMissileForWeapon(int wp,qboolean alt_fire)81 float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire )
82 {
83 if ( alt_fire )
84 {
85 return weaponSpeed[wp][1];
86 }
87 return weaponSpeed[wp][0];
88 }
89
90 //-----------------------------------------------------------------------------
WP_TraceSetStart(const gentity_t * ent,vec3_t start,const vec3_t mins,const vec3_t maxs)91 void WP_TraceSetStart( const gentity_t *ent, vec3_t start, const vec3_t mins, const vec3_t maxs )
92 //-----------------------------------------------------------------------------
93 {
94 //make sure our start point isn't on the other side of a wall
95 trace_t tr;
96 vec3_t entMins, newstart;
97 vec3_t entMaxs;
98
99 VectorSet( entMaxs, 5, 5, 5 );
100 VectorScale( entMaxs, -1, entMins );
101
102 if ( !ent->client )
103 {
104 return;
105 }
106
107 VectorCopy( ent->currentOrigin, newstart );
108 newstart[2] = start[2]; // force newstart to be on the same plane as the muzzle ( start )
109
110 gi.trace( &tr, newstart, entMins, entMaxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP, (EG2_Collision)0, 0 );
111
112 if ( tr.startsolid || tr.allsolid )
113 {
114 // there is a problem here..
115 return;
116 }
117
118 if ( tr.fraction < 1.0f )
119 {
120 VectorCopy( tr.endpos, start );
121 }
122 }
123
124 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
125 //-----------------------------------------------------------------------------
CreateMissile(vec3_t org,vec3_t dir,float vel,int life,gentity_t * owner,qboolean altFire)126 gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire )
127 //-----------------------------------------------------------------------------
128 {
129 gentity_t *missile;
130
131 missile = G_Spawn();
132
133 missile->nextthink = level.time + life;
134 missile->e_ThinkFunc = thinkF_G_FreeEntity;
135 missile->s.eType = ET_MISSILE;
136 missile->owner = owner;
137
138 Vehicle_t* pVeh = G_IsRidingVehicle(owner);
139
140 missile->alt_fire = altFire;
141
142 missile->s.pos.trType = TR_LINEAR;
143 missile->s.pos.trTime = level.time;// - 10; // move a bit on the very first frame
144 VectorCopy( org, missile->s.pos.trBase );
145 VectorScale( dir, vel, missile->s.pos.trDelta );
146 if (pVeh)
147 {
148 missile->s.eFlags |= EF_USE_ANGLEDELTA;
149 vectoangles(missile->s.pos.trDelta, missile->s.angles);
150 VectorMA(missile->s.pos.trDelta, 2.0f, pVeh->m_pParentEntity->client->ps.velocity, missile->s.pos.trDelta);
151 }
152
153 VectorCopy( org, missile->currentOrigin);
154 gi.linkentity( missile );
155
156 return missile;
157 }
158
159
160 //-----------------------------------------------------------------------------
WP_Stick(gentity_t * missile,trace_t * trace,float fudge_distance)161 void WP_Stick( gentity_t *missile, trace_t *trace, float fudge_distance )
162 //-----------------------------------------------------------------------------
163 {
164 vec3_t org, ang;
165
166 // not moving or rotating
167 missile->s.pos.trType = TR_STATIONARY;
168 VectorClear( missile->s.pos.trDelta );
169 VectorClear( missile->s.apos.trDelta );
170
171 // so we don't stick into the wall
172 VectorMA( trace->endpos, fudge_distance, trace->plane.normal, org );
173 G_SetOrigin( missile, org );
174
175 vectoangles( trace->plane.normal, ang );
176 G_SetAngles( missile, ang );
177
178 // I guess explode death wants me as the normal?
179 // VectorCopy( trace->plane.normal, missile->pos1 );
180 gi.linkentity( missile );
181 }
182
183 // This version shares is in the thinkFunc format
184 //-----------------------------------------------------------------------------
WP_Explode(gentity_t * self)185 void WP_Explode( gentity_t *self )
186 //-----------------------------------------------------------------------------
187 {
188 gentity_t *attacker = self;
189 vec3_t forwardVec={0,0,1};
190
191 // stop chain reaction runaway loops
192 self->takedamage = qfalse;
193
194 self->s.loopSound = 0;
195
196 // VectorCopy( self->currentOrigin, self->s.pos.trBase );
197 if ( !self->client )
198 {
199 AngleVectors( self->s.angles, forwardVec, NULL, NULL );
200 }
201
202 if ( self->fxID > 0 )
203 {
204 G_PlayEffect( self->fxID, self->currentOrigin, forwardVec );
205 }
206
207 if ( self->owner )
208 {
209 attacker = self->owner;
210 }
211 else if ( self->activator )
212 {
213 attacker = self->activator;
214 }
215
216 if ( self->splashDamage > 0 && self->splashRadius > 0 )
217 {
218 G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, 0/*don't ignore attacker*/, MOD_EXPLOSIVE_SPLASH );
219 }
220
221 if ( self->target )
222 {
223 G_UseTargets( self, attacker );
224 }
225
226 G_SetOrigin( self, self->currentOrigin );
227
228 self->nextthink = level.time + 50;
229 self->e_ThinkFunc = thinkF_G_FreeEntity;
230 }
231
232 // We need to have a dieFunc, otherwise G_Damage won't actually make us die. I could modify G_Damage, but that entails too many changes
233 //-----------------------------------------------------------------------------
WP_ExplosiveDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)234 void WP_ExplosiveDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
235 //-----------------------------------------------------------------------------
236 {
237 self->enemy = attacker;
238
239 if ( attacker && !attacker->s.number )
240 {
241 // less damage when shot by player
242 self->splashDamage /= 3;
243 self->splashRadius /= 3;
244 }
245
246 self->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead
247
248 WP_Explode( self );
249 }
250
WP_MissileTargetHint(gentity_t * shooter,vec3_t start,vec3_t out)251 bool WP_MissileTargetHint(gentity_t* shooter, vec3_t start, vec3_t out)
252 {
253 return false;
254 }
255
G_GetHitLocFromTrace(trace_t * trace,int mod)256 int G_GetHitLocFromTrace( trace_t *trace, int mod )
257 {
258 int hitLoc = HL_NONE;
259 for (int i=0; i < MAX_G2_COLLISIONS; i++)
260 {
261 if ( trace->G2CollisionMap[i].mEntityNum == -1 )
262 {
263 break;
264 }
265
266 CCollisionRecord &coll = trace->G2CollisionMap[i];
267 if ( (coll.mFlags & G2_FRONTFACE) )
268 {
269 G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &hitLoc, coll.mCollisionPosition, NULL, NULL, mod );
270 //we only want the first "entrance wound", so break
271 break;
272 }
273 }
274 return hitLoc;
275 }
276
277 //---------------------------------------------------------
AddLeanOfs(const gentity_t * const ent,vec3_t point)278 void AddLeanOfs(const gentity_t *const ent, vec3_t point)
279 //---------------------------------------------------------
280 {
281 if(ent->client)
282 {
283 if(ent->client->ps.leanofs)
284 {
285 vec3_t right;
286 //add leaning offset
287 AngleVectors(ent->client->ps.viewangles, NULL, right, NULL);
288 VectorMA(point, (float)ent->client->ps.leanofs, right, point);
289 }
290 }
291 }
292
293 //---------------------------------------------------------
SubtractLeanOfs(const gentity_t * const ent,vec3_t point)294 void SubtractLeanOfs(const gentity_t *const ent, vec3_t point)
295 //---------------------------------------------------------
296 {
297 if(ent->client)
298 {
299 if(ent->client->ps.leanofs)
300 {
301 vec3_t right;
302 //add leaning offset
303 AngleVectors( ent->client->ps.viewangles, NULL, right, NULL );
304 VectorMA( point, ent->client->ps.leanofs*-1, right, point );
305 }
306 }
307 }
308
309 //---------------------------------------------------------
ViewHeightFix(const gentity_t * const ent)310 void ViewHeightFix(const gentity_t *const ent)
311 //---------------------------------------------------------
312 {
313 //FIXME: this is hacky and doesn't need to be here. Was only put here to make up
314 //for the times a crouch anim would be used but not actually crouching.
315 //When we start calcing eyepos (SPOT_HEAD) from the tag_eyes, we won't need
316 //this (or viewheight at all?)
317 if ( !ent )
318 return;
319
320 if ( !ent->client || !ent->NPC )
321 return;
322
323 if ( ent->client->ps.stats[STAT_HEALTH] <= 0 )
324 return;//dead
325
326 if ( ent->client->ps.legsAnim == BOTH_CROUCH1IDLE || ent->client->ps.legsAnim == BOTH_CROUCH1 || ent->client->ps.legsAnim == BOTH_CROUCH1WALK )
327 {
328 if ( ent->client->ps.viewheight!=ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET )
329 ent->client->ps.viewheight = ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET;
330 }
331 else
332 {
333 if ( ent->client->ps.viewheight!=ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET )
334 ent->client->ps.viewheight = ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET;
335 }
336 }
337
W_AccuracyLoggableWeapon(int weapon,qboolean alt_fire,int mod)338 qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod )
339 {
340 if ( mod != MOD_UNKNOWN )
341 {
342 switch( mod )
343 {
344 //standard weapons
345 case MOD_BRYAR:
346 case MOD_BRYAR_ALT:
347 case MOD_BLASTER:
348 case MOD_BLASTER_ALT:
349 case MOD_DISRUPTOR:
350 case MOD_SNIPER:
351 case MOD_BOWCASTER:
352 case MOD_BOWCASTER_ALT:
353 case MOD_ROCKET:
354 case MOD_ROCKET_ALT:
355 case MOD_CONC:
356 case MOD_CONC_ALT:
357 return qtrue;
358 break;
359 //non-alt standard
360 case MOD_REPEATER:
361 case MOD_DEMP2:
362 case MOD_FLECHETTE:
363 return qtrue;
364 break;
365 //emplaced gun
366 case MOD_EMPLACED:
367 return qtrue;
368 break;
369 //atst
370 case MOD_ENERGY:
371 case MOD_EXPLOSIVE:
372 if ( weapon == WP_ATST_MAIN || weapon == WP_ATST_SIDE )
373 {
374 return qtrue;
375 }
376 break;
377 }
378 }
379 else if ( weapon != WP_NONE )
380 {
381 switch( weapon )
382 {
383 case WP_BRYAR_PISTOL:
384 case WP_BLASTER_PISTOL:
385 case WP_BLASTER:
386 case WP_DISRUPTOR:
387 case WP_BOWCASTER:
388 case WP_ROCKET_LAUNCHER:
389 case WP_CONCUSSION:
390 return qtrue;
391 break;
392 //non-alt standard
393 case WP_REPEATER:
394 case WP_DEMP2:
395 case WP_FLECHETTE:
396 if ( !alt_fire )
397 {
398 return qtrue;
399 }
400 break;
401 //emplaced gun
402 case WP_EMPLACED_GUN:
403 return qtrue;
404 break;
405 //atst
406 case WP_ATST_MAIN:
407 case WP_ATST_SIDE:
408 return qtrue;
409 break;
410 }
411 }
412 return qfalse;
413 }
414
415 /*
416 ===============
417 LogAccuracyHit
418 ===============
419 */
LogAccuracyHit(gentity_t * target,gentity_t * attacker)420 qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) {
421 if( !target->takedamage ) {
422 return qfalse;
423 }
424
425 if ( target == attacker ) {
426 return qfalse;
427 }
428
429 if( !target->client ) {
430 return qfalse;
431 }
432
433 if( !attacker->client ) {
434 return qfalse;
435 }
436
437 if( target->client->ps.stats[STAT_HEALTH] <= 0 ) {
438 return qfalse;
439 }
440
441 if ( OnSameTeam( target, attacker ) ) {
442 return qfalse;
443 }
444
445 return qtrue;
446 }
447
448 //---------------------------------------------------------
CalcMuzzlePoint(gentity_t * const ent,vec3_t forwardVec,vec3_t right,vec3_t up,vec3_t muzzlePoint,float lead_in)449 void CalcMuzzlePoint( gentity_t *const ent, vec3_t forwardVec, vec3_t right, vec3_t up, vec3_t muzzlePoint, float lead_in )
450 //---------------------------------------------------------
451 {
452 vec3_t org;
453 mdxaBone_t boltMatrix;
454
455 if( !lead_in ) //&& ent->s.number != 0
456 {//Not players or melee
457 if( ent->client )
458 {
459 if ( ent->client->renderInfo.mPCalcTime >= level.time - FRAMETIME*2 )
460 {//Our muzz point was calced no more than 2 frames ago
461 VectorCopy(ent->client->renderInfo.muzzlePoint, muzzlePoint);
462 return;
463 }
464 }
465 }
466
467 VectorCopy( ent->currentOrigin, muzzlePoint );
468
469 switch( ent->s.weapon )
470 {
471 case WP_BRYAR_PISTOL:
472 case WP_BLASTER_PISTOL:
473 ViewHeightFix(ent);
474 muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
475 muzzlePoint[2] -= 16;
476 VectorMA( muzzlePoint, 28, forwardVec, muzzlePoint );
477 VectorMA( muzzlePoint, 6, vrightVec, muzzlePoint );
478 break;
479
480 case WP_ROCKET_LAUNCHER:
481 case WP_CONCUSSION:
482 case WP_THERMAL:
483 ViewHeightFix(ent);
484 muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
485 muzzlePoint[2] -= 2;
486 break;
487
488 case WP_BLASTER:
489 ViewHeightFix(ent);
490 muzzlePoint[2] += ent->client->ps.viewheight;//By eyes
491 muzzlePoint[2] -= 1;
492 if ( ent->s.number == 0 )
493 VectorMA( muzzlePoint, 12, forwardVec, muzzlePoint ); // player, don't set this any lower otherwise the projectile will impact immediately when your back is to a wall
494 else
495 VectorMA( muzzlePoint, 2, forwardVec, muzzlePoint ); // NPC, don't set too far forwardVec otherwise the projectile can go through doors
496
497 VectorMA( muzzlePoint, 1, vrightVec, muzzlePoint );
498 break;
499
500 case WP_SABER:
501 if(ent->NPC!=NULL &&
502 (ent->client->ps.torsoAnim == TORSO_WEAPONREADY2 ||
503 ent->client->ps.torsoAnim == BOTH_ATTACK2))//Sniper pose
504 {
505 ViewHeightFix(ent);
506 muzzle[2] += ent->client->ps.viewheight;//By eyes
507 }
508 else
509 {
510 muzzlePoint[2] += 16;
511 }
512 VectorMA( muzzlePoint, 8, forwardVec, muzzlePoint );
513 VectorMA( muzzlePoint, 16, vrightVec, muzzlePoint );
514 break;
515
516 case WP_BOT_LASER:
517 muzzlePoint[2] -= 16; //
518 break;
519 case WP_ATST_MAIN:
520
521 if (ent->count > 0)
522 {
523 ent->count = 0;
524 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
525 ent->handLBolt,
526 &boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
527 NULL, ent->s.modelScale );
528 }
529 else
530 {
531 ent->count = 1;
532 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
533 ent->handRBolt,
534 &boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time),
535 NULL, ent->s.modelScale );
536 }
537
538 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
539
540 VectorCopy(org,muzzlePoint);
541
542 break;
543 }
544
545 AddLeanOfs(ent, muzzlePoint);
546 }
547
548 // Muzzle point table...
549 vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS] =
550 {// Fwd, right, up.
551 {0, 0, 0 }, // WP_NONE,
552 {8 , 16, 0 }, // WP_SABER,
553 {12, 6, -6 }, // WP_BLASTER_PISTOL,
554 {12, 6, -6 }, // WP_BLASTER,
555 {12, 6, -6 }, // WP_DISRUPTOR,
556 {12, 2, -6 }, // WP_BOWCASTER,
557 {12, 4.5, -6 }, // WP_REPEATER,
558 {12, 6, -6 }, // WP_DEMP2,
559 {12, 6, -6 }, // WP_FLECHETTE,
560 {12, 8, -4 }, // WP_ROCKET_LAUNCHER,
561 {12, 0, -4 }, // WP_THERMAL,
562 {12, 0, -10 }, // WP_TRIP_MINE,
563 {12, 0, -4 }, // WP_DET_PACK,
564 {12, 8, -4 }, // WP_CONCUSSION,
565 {0 , 8, 0 }, // WP_MELEE,
566 {0, 0, 0 }, // WP_ATST_MAIN,
567 {0, 0, 0 }, // WP_ATST_SIDE,
568 {0 , 8, 0 }, // WP_STUN_BATON,
569 {12, 6, -6 }, // WP_BRYAR_PISTOL,
570 };
571
WP_RocketLock(gentity_t * ent,float lockDist)572 void WP_RocketLock( gentity_t *ent, float lockDist )
573 {
574 // Not really a charge weapon, but we still want to delay fire until the button comes up so that we can
575 // implement our alt-fire locking stuff
576 vec3_t ang;
577 trace_t tr;
578
579 vec3_t muzzleOffPoint, muzzlePoint, forwardVec, right, up;
580
581 AngleVectors( ent->client->ps.viewangles, forwardVec, right, up );
582
583 AngleVectors(ent->client->ps.viewangles, ang, NULL, NULL);
584
585 VectorCopy( ent->client->ps.origin, muzzlePoint );
586 VectorCopy(WP_MuzzlePoint[WP_ROCKET_LAUNCHER], muzzleOffPoint);
587
588 VectorMA(muzzlePoint, muzzleOffPoint[0], forwardVec, muzzlePoint);
589 VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint);
590 muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2];
591
592 ang[0] = muzzlePoint[0] + ang[0]*lockDist;
593 ang[1] = muzzlePoint[1] + ang[1]*lockDist;
594 ang[2] = muzzlePoint[2] + ang[2]*lockDist;
595
596 gi.trace(&tr, muzzlePoint, NULL, NULL, ang, ent->client->ps.clientNum, MASK_PLAYERSOLID, (EG2_Collision)0, 0);
597
598 if (tr.fraction != 1 && tr.entityNum < ENTITYNUM_NONE && tr.entityNum != ent->client->ps.clientNum)
599 {
600 gentity_t *bgEnt = &g_entities[tr.entityNum];
601 if ( bgEnt && (bgEnt->s.powerups&PW_CLOAKED) )
602 {
603 ent->client->rocketLockIndex = ENTITYNUM_NONE;
604 ent->client->rocketLockTime = 0;
605 }
606 else if (bgEnt && bgEnt->s.eType == ET_PLAYER )
607 {
608 if (ent->client->rocketLockIndex == ENTITYNUM_NONE)
609 {
610 ent->client->rocketLockIndex = tr.entityNum;
611 ent->client->rocketLockTime = level.time;
612 }
613 else if (ent->client->rocketLockIndex != tr.entityNum && ent->client->rocketTargetTime < level.time)
614 {
615 ent->client->rocketLockIndex = tr.entityNum;
616 ent->client->rocketLockTime = level.time;
617 }
618 else if (ent->client->rocketLockIndex == tr.entityNum)
619 {
620 if (ent->client->rocketLockTime == -1)
621 {
622 ent->client->rocketLockTime = ent->client->rocketLastValidTime;
623 }
624 }
625
626 if (ent->client->rocketLockIndex == tr.entityNum)
627 {
628 ent->client->rocketTargetTime = level.time + 500;
629 }
630 }
631 }
632 else if (ent->client->rocketTargetTime < level.time)
633 {
634 ent->client->rocketLockIndex = ENTITYNUM_NONE;
635 ent->client->rocketLockTime = 0;
636 }
637 else
638 {
639 if (ent->client->rocketLockTime != -1)
640 {
641 ent->client->rocketLastValidTime = ent->client->rocketLockTime;
642 }
643 ent->client->rocketLockTime = -1;
644 }
645 }
646
647 #define VEH_HOMING_MISSILE_THINK_TIME 100
WP_FireVehicleWeapon(gentity_t * ent,vec3_t start,vec3_t dir,vehWeaponInfo_t * vehWeapon)648 void WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon )
649 {
650 if ( !vehWeapon )
651 {//invalid vehicle weapon
652 return;
653 }
654 else if ( vehWeapon->bIsProjectile )
655 {//projectile entity
656 gentity_t *missile;
657 vec3_t mins, maxs;
658
659 VectorSet( maxs, vehWeapon->fWidth/2.0f,vehWeapon->fWidth/2.0f,vehWeapon->fHeight/2.0f );
660 VectorScale( maxs, -1, mins );
661
662 //make sure our start point isn't on the other side of a wall
663 WP_TraceSetStart( ent, start, mins, maxs );
664
665 //QUERY: alt_fire true or not? Does it matter?
666 missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse );
667 if ( vehWeapon->bHasGravity )
668 {//TESTME: is this all we need to do?
669 missile->s.pos.trType = TR_GRAVITY;
670 }
671
672 missile->classname = "vehicle_proj";
673
674 missile->damage = vehWeapon->iDamage;
675 missile->splashDamage = vehWeapon->iSplashDamage;
676 missile->splashRadius = vehWeapon->fSplashRadius;
677
678 // HUGE HORRIBLE HACK
679 if (ent->owner && ent->owner->s.number==0)
680 {
681 //Should only be for speeders - mainly for t2_trip
682 if (ent->m_pVehicle->m_pVehicleInfo && ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER)
683 {
684 missile->damage *= 20.0f;
685 missile->splashDamage *= 20.0f;
686 missile->splashRadius *= 20.0f;
687 }
688 }
689
690 //FIXME: externalize some of these properties?
691 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
692 missile->clipmask = MASK_SHOT;
693 //Maybe by checking flags...?
694 if ( vehWeapon->bSaberBlockable )
695 {
696 missile->clipmask |= CONTENTS_LIGHTSABER;
697 }
698 /*
699 if ( (vehWeapon->iFlags&VWF_KNOCKBACK) )
700 {
701 missile->dflags &= ~DAMAGE_DEATH_KNOCKBACK;
702 }
703 if ( (vehWeapon->iFlags&VWF_DISTORTION_TRAIL) )
704 {
705 missile->s.eFlags |= EF_DISTORTION_TRAIL;
706 }
707 if ( (vehWeapon->iFlags&VWF_RADAR) )
708 {
709 missile->s.eFlags |= EF_RADAROBJECT;
710 }
711 */
712 missile->s.weapon = WP_BLASTER;//does this really matter?
713
714 // Make it easier to hit things
715 VectorCopy( mins, missile->mins );
716 VectorCopy( maxs, missile->maxs );
717 //some slightly different stuff for things with bboxes
718 if ( vehWeapon->fWidth || vehWeapon->fHeight )
719 {//we assume it's a rocket-like thing
720 missile->methodOfDeath = MOD_ROCKET;
721 missile->splashMethodOfDeath = MOD_ROCKET;// ?SPLASH;
722
723 // we don't want it to ever bounce
724 missile->bounceCount = 0;
725
726 missile->mass = 10;
727 }
728 else
729 {//a blaster-laser-like thing
730 missile->s.weapon = WP_BLASTER;//does this really matter?
731 missile->methodOfDeath = MOD_EMPLACED;//MOD_TURBLAST; //count as a heavy weap
732 missile->splashMethodOfDeath = MOD_EMPLACED;//MOD_TURBLAST;// ?SPLASH;
733 // we don't want it to bounce forever
734 missile->bounceCount = 8;
735 }
736
737 if ( vehWeapon->iHealth )
738 {//the missile can take damage
739 missile->health = vehWeapon->iHealth;
740 missile->takedamage = qtrue;
741 missile->contents = MASK_SHOT;
742 missile->e_DieFunc = dieF_WP_ExplosiveDie;//dieF_RocketDie;
743 }
744
745 //set veh as cgame side owner for purpose of fx overrides
746 if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot)
747 {
748 missile->owner = ent->m_pVehicle->m_pPilot;
749 }
750 else
751 {
752 missile->owner = ent;
753 }
754 missile->s.otherEntityNum = ent->s.number;
755 missile->s.otherEntityNum2 = (vehWeapon-&g_vehWeaponInfo[0]);
756
757 if ( vehWeapon->iLifeTime )
758 {//expire after a time
759 if ( vehWeapon->bExplodeOnExpire )
760 {//blow up when your lifetime is up
761 missile->e_ThinkFunc = thinkF_WP_Explode;//FIXME: custom func?
762 }
763 else
764 {//just remove yourself
765 missile->e_ThinkFunc = thinkF_G_FreeEntity;//FIXME: custom func?
766 }
767 missile->nextthink = level.time + vehWeapon->iLifeTime;
768 }
769 if ( vehWeapon->fHoming )
770 {//homing missile
771 //crap, we need to set up the homing stuff like it is in MP...
772 WP_RocketLock( ent, 16384 );
773 if ( ent->client && ent->client->rocketLockIndex != ENTITYNUM_NONE )
774 {
775 int dif = 0;
776 float rTime;
777 rTime = ent->client->rocketLockTime;
778
779 if (rTime == -1)
780 {
781 rTime = ent->client->rocketLastValidTime;
782 }
783
784 if ( !vehWeapon->iLockOnTime )
785 {//no minimum lock-on time
786 dif = 10;//guaranteed lock-on
787 }
788 else
789 {
790 float lockTimeInterval = vehWeapon->iLockOnTime/16.0f;
791 dif = ( level.time - rTime ) / lockTimeInterval;
792 }
793
794 if (dif < 0)
795 {
796 dif = 0;
797 }
798
799 //It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client
800 if ( dif >= 10 && rTime != -1 )
801 {
802 missile->enemy = &g_entities[ent->client->rocketLockIndex];
803
804 if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy))
805 { //if enemy became invalid, died, or is on the same team, then don't seek it
806 missile->spawnflags |= 1;//just to let it know it should be faster... FIXME: EXTERNALIZE
807 missile->speed = vehWeapon->fSpeed;
808 missile->angle = vehWeapon->fHoming;
809 if ( vehWeapon->iLifeTime )
810 {//expire after a time
811 missile->disconnectDebounceTime = level.time + vehWeapon->iLifeTime;
812 missile->lockCount = (int)(vehWeapon->bExplodeOnExpire);
813 }
814 missile->e_ThinkFunc = thinkF_rocketThink;
815 missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME;
816 //FIXME: implement radar in SP?
817 //missile->s.eFlags |= EF_RADAROBJECT;
818 }
819 }
820
821 ent->client->rocketLockIndex = ENTITYNUM_NONE;
822 ent->client->rocketLockTime = 0;
823 ent->client->rocketTargetTime = 0;
824
825 VectorCopy( dir, missile->movedir );
826 missile->random = 1.0f;//FIXME: externalize?
827 }
828 }
829 }
830 else
831 {//traceline
832 //FIXME: implement
833 }
834 }
835
WP_VehLeadCrosshairVeh(gentity_t * camTraceEnt,vec3_t newEnd,const vec3_t dir,const vec3_t shotStart,vec3_t shotDir)836 void WP_VehLeadCrosshairVeh( gentity_t *camTraceEnt, vec3_t newEnd, const vec3_t dir, const vec3_t shotStart, vec3_t shotDir )
837 {
838 //FIXME: implement from MP?
839 }
840
WP_VehCheckTraceFromCamPos(gentity_t * ent,const vec3_t shotStart,vec3_t shotDir)841 qboolean WP_VehCheckTraceFromCamPos( gentity_t *ent, const vec3_t shotStart, vec3_t shotDir )
842 {
843 //FIXME: implement from MP?
844 return qfalse;
845 }
846
847 //---------------------------------------------------------
FireVehicleWeapon(gentity_t * ent,qboolean alt_fire)848 void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire )
849 //---------------------------------------------------------
850 {
851 Vehicle_t *pVeh = ent->m_pVehicle;
852
853 if ( !pVeh )
854 {
855 return;
856 }
857
858 if (pVeh->m_iRemovedSurfaces)
859 { //can't fire when the thing is breaking apart
860 return;
861 }
862
863
864 if (ent->owner && ent->owner->client && ent->owner->client->ps.weapon!=WP_NONE)
865 {
866 return;
867 }
868
869 // TODO?: If possible (probably not enough time), it would be nice if secondary fire was actually a mode switch/toggle
870 // so that, for instance, an x-wing can have 4-gun fire, or individual muzzle fire. If you wanted a different weapon, you
871 // would actually have to press the 2 key or something like that (I doubt I'd get a graphic for it anyways though). -AReis
872
873 // If this is not the alternate fire, fire a normal blaster shot...
874 if ( pVeh->m_pVehicleInfo &&
875 (pVeh->m_pVehicleInfo->type != VH_FIGHTER || (pVeh->m_ulFlags&VEH_WINGSOPEN)) ) // NOTE: Wings open also denotes that it has already launched.
876 {//fighters can only fire when wings are open
877 int weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE;
878 int delay = 1000;
879 qboolean aimCorrect = qfalse;
880 qboolean linkedFiring = qfalse;
881
882 if ( !alt_fire )
883 {
884 weaponNum = 0;
885 }
886 else
887 {
888 weaponNum = 1;
889 }
890
891 vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID;
892
893 if ( pVeh->weaponStatus[weaponNum].ammo <= 0 )
894 {//no ammo for this weapon
895 if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
896 {// let the client know he's out of ammo
897 int i;
898 //but only if one of the vehicle muzzles is actually ready to fire this weapon
899 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
900 {
901 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
902 {//this muzzle doesn't match the weapon we're trying to use
903 continue;
904 }
905 if ( pVeh->m_iMuzzleTag[i] != -1
906 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
907 {//this one would have fired, send the no ammo message
908 G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
909 break;
910 }
911 }
912 }
913 return;
914 }
915
916 delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay;
917 aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect;
918 if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2//always linked
919 || ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable
920 && pVeh->weaponStatus[weaponNum].linked ) )//linked
921 {//we're linking the primary or alternate weapons, so we'll do *all* the muzzles
922 linkedFiring = qtrue;
923 }
924
925 if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS )
926 {//invalid vehicle weapon
927 return;
928 }
929 else
930 {
931 int i, numMuzzles = 0, numMuzzlesReady = 0, cumulativeDelay = 0, cumulativeAmmo = 0;
932 qboolean sentAmmoWarning = qfalse;
933
934 vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[vehWeaponIndex];
935
936 if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 2 )
937 {//always linked weapons don't accumulate delay, just use specified delay
938 cumulativeDelay = delay;
939 }
940 //find out how many we've got for this weapon
941 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
942 {
943 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
944 {//this muzzle doesn't match the weapon we're trying to use
945 continue;
946 }
947 if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
948 {
949 numMuzzlesReady++;
950 }
951 if ( pVeh->m_pVehicleInfo->weapMuzzle[pVeh->weaponStatus[weaponNum].nextMuzzle] != vehWeaponIndex )
952 {//Our designated next muzzle for this weapon isn't valid for this weapon (happens when ships fire for the first time)
953 //set the next to this one
954 pVeh->weaponStatus[weaponNum].nextMuzzle = i;
955 }
956 if ( linkedFiring )
957 {
958 cumulativeAmmo += vehWeapon->iAmmoPerShot;
959 if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable != 2 )
960 {//always linked weapons don't accumulate delay, just use specified delay
961 cumulativeDelay += delay;
962 }
963 }
964 numMuzzles++;
965 }
966
967 if ( linkedFiring )
968 {//firing all muzzles at once
969 if ( numMuzzlesReady != numMuzzles )
970 {//can't fire all linked muzzles yet
971 return;
972 }
973 else
974 {//can fire all linked muzzles, check ammo
975 if ( pVeh->weaponStatus[weaponNum].ammo < cumulativeAmmo )
976 {//can't fire, not enough ammo
977 if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
978 {// let the client know he's out of ammo
979 G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
980 }
981 return;
982 }
983 }
984 }
985
986 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
987 {
988 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
989 {//this muzzle doesn't match the weapon we're trying to use
990 continue;
991 }
992 if ( !linkedFiring
993 && i != pVeh->weaponStatus[weaponNum].nextMuzzle )
994 {//we're only firing one muzzle and this isn't it
995 continue;
996 }
997
998 // Fire this muzzle.
999 if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time )
1000 {
1001 vec3_t start, dir;
1002
1003 if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot )
1004 {//out of ammo!
1005 if ( !sentAmmoWarning )
1006 {
1007 sentAmmoWarning = qtrue;
1008 if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS )
1009 {// let the client know he's out of ammo
1010 G_AddEvent( (gentity_t*)pVeh->m_pPilot, EV_NOAMMO, weaponNum );
1011 }
1012 }
1013 }
1014 else
1015 {//have enough ammo to shoot
1016 //do the firing
1017 //WP_CalcVehMuzzle(ent, i);
1018 VectorCopy( pVeh->m_Muzzles[i].m_vMuzzlePos, start );
1019 VectorCopy( pVeh->m_Muzzles[i].m_vMuzzleDir, dir );
1020 if ( WP_VehCheckTraceFromCamPos( ent, start, dir ) )
1021 {//auto-aim at whatever crosshair would be over from camera's point of view (if closer)
1022 }
1023 else if ( aimCorrect )
1024 {//auto-aim the missile at the crosshair if there's anything there
1025 trace_t trace;
1026 vec3_t end;
1027 vec3_t ang;
1028 vec3_t fixedDir;
1029
1030 if (pVeh->m_pVehicleInfo->type == VH_SPEEDER)
1031 {
1032 VectorSet(ang, 0.0f, pVeh->m_vOrientation[1], 0.0f);
1033 }
1034 else
1035 {
1036 VectorCopy(pVeh->m_vOrientation, ang);
1037 }
1038 AngleVectors( ang, fixedDir, NULL, NULL );
1039 //VectorMA( ent->currentOrigin, 32768, dir, end );
1040 VectorMA( ent->currentOrigin, 8192, dir, end );
1041 gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1042 if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid )
1043 {
1044 vec3_t newEnd;
1045 VectorCopy( trace.endpos, newEnd );
1046 WP_VehLeadCrosshairVeh( &g_entities[trace.entityNum], newEnd, fixedDir, start, dir );
1047 }
1048 }
1049
1050 //play the weapon's muzzle effect if we have one
1051 if ( vehWeapon->iMuzzleFX )
1052 {
1053 G_PlayEffect( vehWeapon->iMuzzleFX, pVeh->m_Muzzles[i].m_vMuzzlePos, pVeh->m_Muzzles[i].m_vMuzzleDir );
1054 }
1055 WP_FireVehicleWeapon( ent, start, dir, vehWeapon );
1056 }
1057
1058 if ( linkedFiring )
1059 {//we're linking the weapon, so continue on and fire all appropriate muzzles
1060 continue;
1061 }
1062 //else just firing one
1063 //take the ammo, set the next muzzle and set the delay on it
1064 if ( numMuzzles > 1 )
1065 {//more than one, look for it
1066 int nextMuzzle = pVeh->weaponStatus[weaponNum].nextMuzzle;
1067 while ( 1 )
1068 {
1069 nextMuzzle++;
1070 if ( nextMuzzle >= MAX_VEHICLE_MUZZLES )
1071 {
1072 nextMuzzle = 0;
1073 }
1074 if ( nextMuzzle == pVeh->weaponStatus[weaponNum].nextMuzzle )
1075 {//WTF? Wrapped without finding another valid one!
1076 break;
1077 }
1078 if ( pVeh->m_pVehicleInfo->weapMuzzle[nextMuzzle] == vehWeaponIndex )
1079 {//this is the next muzzle for this weapon
1080 pVeh->weaponStatus[weaponNum].nextMuzzle = nextMuzzle;
1081 break;
1082 }
1083 }
1084 }//else, just stay on the one we just fired
1085 //set the delay on the next muzzle
1086 pVeh->m_Muzzles[pVeh->weaponStatus[weaponNum].nextMuzzle].m_iMuzzleWait = level.time + delay;
1087 //take away the ammo
1088 pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot;
1089 //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
1090 if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
1091 {
1092 ((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
1093 }
1094 //done!
1095 //we'll get in here again next frame and try the next muzzle...
1096 //return;
1097 return;
1098 }
1099 }
1100 //we went through all the muzzles, so apply the cumulative delay and ammo cost
1101 if ( cumulativeAmmo )
1102 {//taking ammo one shot at a time
1103 //take the ammo
1104 pVeh->weaponStatus[weaponNum].ammo -= cumulativeAmmo;
1105 //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array
1106 if ( pVeh->m_pParentEntity && ((gentity_t*)(pVeh->m_pParentEntity))->client )
1107 {
1108 ((gentity_t*)(pVeh->m_pParentEntity))->client->ps.ammo[weaponNum] = pVeh->weaponStatus[weaponNum].ammo;
1109 }
1110 }
1111 if ( cumulativeDelay )
1112 {//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles
1113 for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
1114 {
1115 if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex )
1116 {//this muzzle doesn't match the weapon we're trying to use
1117 continue;
1118 }
1119 //apply the cumulative delay
1120 pVeh->m_Muzzles[i].m_iMuzzleWait = level.time + cumulativeDelay;
1121 }
1122 }
1123 }
1124 }
1125 }
1126
WP_FireScepter(gentity_t * ent,qboolean alt_fire)1127 void WP_FireScepter( gentity_t *ent, qboolean alt_fire )
1128 {//just a straight beam
1129 int damage = 1;
1130 vec3_t start, end;
1131 trace_t tr;
1132 gentity_t *traceEnt = NULL, *tent;
1133 float shotRange = 8192;
1134 qboolean render_impact = qtrue;
1135
1136 VectorCopy( muzzle, start );
1137 WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );
1138
1139 WP_MissileTargetHint(ent, start, forwardVec);
1140 VectorMA( start, shotRange, forwardVec, end );
1141
1142 gi.trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 10 );
1143 traceEnt = &g_entities[tr.entityNum];
1144
1145 if ( tr.surfaceFlags & SURF_NOIMPACT )
1146 {
1147 render_impact = qfalse;
1148 }
1149
1150 // always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
1151 tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
1152 tent->svFlags |= SVF_BROADCAST;
1153 VectorCopy( muzzle, tent->s.origin2 );
1154
1155 if ( render_impact )
1156 {
1157 if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
1158 {
1159 // Create a simple impact type mark that doesn't last long in the world
1160 G_PlayEffect( G_EffectIndex( "disruptor/flesh_impact" ), tr.endpos, tr.plane.normal );
1161
1162 int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR );
1163 G_Damage( traceEnt, ent, ent, forwardVec, tr.endpos, damage, DAMAGE_EXTRA_KNOCKBACK, MOD_DISRUPTOR, hitLoc );
1164 }
1165 else
1166 {
1167 G_PlayEffect( G_EffectIndex( "disruptor/wall_impact" ), tr.endpos, tr.plane.normal );
1168 }
1169 }
1170
1171 /*
1172 shotDist = shotRange * tr.fraction;
1173
1174 for ( dist = 0; dist < shotDist; dist += 64 )
1175 {
1176 //FIXME: on a really long shot, this could make a LOT of alerts in one frame...
1177 VectorMA( start, dist, forwardVec, spot );
1178 AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
1179 }
1180 VectorMA( start, shotDist-4, forwardVec, spot );
1181 AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 );
1182 */
1183 }
1184
1185 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
1186 //---------------------------------------------------------
FireWeapon(gentity_t * ent,qboolean alt_fire)1187 void FireWeapon( gentity_t *ent, qboolean alt_fire )
1188 //---------------------------------------------------------
1189 {
1190 float alert = 256;
1191 Vehicle_t *pVeh = NULL;
1192
1193 // track shots taken for accuracy tracking.
1194 ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++;
1195
1196 // If this is a vehicle, fire it's weapon and we're done.
1197 if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE )
1198 {
1199 FireVehicleWeapon( ent, alt_fire );
1200 return;
1201 }
1202
1203 // set aiming directions
1204 if ( ent->s.weapon == WP_DISRUPTOR && alt_fire )
1205 {
1206 if ( ent->NPC )
1207 {
1208 //snipers must use the angles they actually did their shot trace with
1209 AngleVectors( ent->lastAngles, forwardVec, vrightVec, up );
1210 }
1211 }
1212 else if ( ent->s.weapon == WP_ATST_SIDE || ent->s.weapon == WP_ATST_MAIN )
1213 {
1214 vec3_t delta1, enemy_org1, muzzle1;
1215 vec3_t angleToEnemy1;
1216
1217 VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle1 );
1218
1219 if ( !ent->s.number )
1220 {//player driving an AT-ST
1221 //SIGH... because we can't anticipate alt-fire, must calc muzzle here and now
1222 mdxaBone_t boltMatrix;
1223 int bolt;
1224
1225 if ( ent->client->ps.weapon == WP_ATST_MAIN )
1226 {//FIXME: alt_fire should fire both barrels, but slower?
1227 if ( ent->alt_fire )
1228 {
1229 bolt = ent->handRBolt;
1230 }
1231 else
1232 {
1233 bolt = ent->handLBolt;
1234 }
1235 }
1236 else
1237 {// ATST SIDE weapons
1238 if ( ent->alt_fire )
1239 {
1240 if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_light_blaster_cann" ) )
1241 {//don't have it!
1242 return;
1243 }
1244 bolt = ent->genericBolt2;
1245 }
1246 else
1247 {
1248 if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_concussion_charger" ) )
1249 {//don't have it!
1250 return;
1251 }
1252 bolt = ent->genericBolt1;
1253 }
1254 }
1255
1256 vec3_t yawOnlyAngles = {0, ent->currentAngles[YAW], 0};
1257 if ( ent->currentAngles[YAW] != ent->client->ps.legsYaw )
1258 {
1259 yawOnlyAngles[YAW] = ent->client->ps.legsYaw;
1260 }
1261 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, bolt, &boltMatrix, yawOnlyAngles, ent->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale );
1262
1263 // work the matrix axis stuff into the original axis and origins used.
1264 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->renderInfo.muzzlePoint );
1265 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent->client->renderInfo.muzzleDir );
1266 ent->client->renderInfo.mPCalcTime = level.time;
1267
1268 AngleVectors( ent->client->ps.viewangles, forwardVec, vrightVec, up );
1269 //CalcMuzzlePoint( ent, forwardVec, vrightVec, up, muzzle, 0 );
1270 }
1271 else if ( !ent->enemy )
1272 {//an NPC with no enemy to auto-aim at
1273 VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec );
1274 }
1275 else
1276 {//NPC, auto-aim at enemy
1277 CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
1278
1279 VectorSubtract (enemy_org1, muzzle1, delta1);
1280
1281 vectoangles ( delta1, angleToEnemy1 );
1282 AngleVectors (angleToEnemy1, forwardVec, vrightVec, up);
1283 }
1284 }
1285 else if ( ent->s.weapon == WP_BOT_LASER && ent->enemy )
1286 {
1287 vec3_t delta1, enemy_org1, muzzle1;
1288 vec3_t angleToEnemy1;
1289
1290 CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 );
1291 CalcEntitySpot( ent, SPOT_WEAPON, muzzle1 );
1292
1293 VectorSubtract (enemy_org1, muzzle1, delta1);
1294
1295 vectoangles ( delta1, angleToEnemy1 );
1296 AngleVectors (angleToEnemy1, forwardVec, vrightVec, up);
1297 }
1298 else
1299 {
1300 if ( (pVeh = G_IsRidingVehicle( ent )) != NULL) //riding a vehicle
1301 {//use our muzzleDir, can't use viewangles or vehicle m_vOrientation because we may be animated to shoot left or right...
1302 if ((ent->s.eFlags&EF_NODRAW))//we're inside it
1303 {
1304 vec3_t aimAngles;
1305 VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec );
1306 vectoangles( forwardVec, aimAngles );
1307 //we're only keeping the yaw
1308 aimAngles[PITCH] = ent->client->ps.viewangles[PITCH];
1309 aimAngles[ROLL] = 0;
1310 AngleVectors( aimAngles, forwardVec, vrightVec, up );
1311 }
1312 else
1313 {
1314 vec3_t actorRight;
1315 vec3_t actorFwd;
1316
1317 VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle );
1318 AngleVectors(ent->currentAngles, actorFwd, actorRight, 0);
1319
1320 // Aiming Left
1321 //-------------
1322 if (ent->client->ps.torsoAnim==BOTH_VT_ATL_G || ent->client->ps.torsoAnim==BOTH_VS_ATL_G)
1323 {
1324 VectorScale(actorRight, -1.0f, forwardVec);
1325 }
1326
1327 // Aiming Right
1328 //--------------
1329 else if (ent->client->ps.torsoAnim==BOTH_VT_ATR_G || ent->client->ps.torsoAnim==BOTH_VS_ATR_G)
1330 {
1331 VectorCopy(actorRight, forwardVec);
1332 }
1333
1334 // Aiming Forward
1335 //----------------
1336 else
1337 {
1338 VectorCopy(actorFwd, forwardVec);
1339 }
1340
1341 // If We Have An Enemy, Fudge The Aim To Hit The Enemy
1342 if (ent->enemy)
1343 {
1344 vec3_t toEnemy;
1345 VectorSubtract(ent->enemy->currentOrigin, ent->currentOrigin, toEnemy);
1346 VectorNormalize(toEnemy);
1347 if (DotProduct(toEnemy, forwardVec)>0.75f &&
1348 ((ent->s.number==0 && !Q_irand(0,2)) || // the player has a 1 in 3 chance
1349 (ent->s.number!=0 && !Q_irand(0,5)))) // other guys have a 1 in 6 chance
1350 {
1351 VectorCopy(toEnemy, forwardVec);
1352 }
1353 else
1354 {
1355 forwardVec[0] += Q_flrand(-0.1f, 0.1f);
1356 forwardVec[1] += Q_flrand(-0.1f, 0.1f);
1357 forwardVec[2] += Q_flrand(-0.1f, 0.1f);
1358 }
1359 }
1360 }
1361 }
1362 else
1363 {
1364 AngleVectors( ent->client->ps.viewangles, forwardVec, vrightVec, up );
1365 }
1366 }
1367
1368 ent->alt_fire = alt_fire;
1369 if (!pVeh)
1370 {
1371 if (ent->NPC && (ent->NPC->scriptFlags&SCF_FIRE_WEAPON_NO_ANIM))
1372 {
1373 VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle );
1374 VectorCopy( ent->client->renderInfo.muzzleDir, forwardVec );
1375 MakeNormalVectors(forwardVec, vrightVec, up);
1376 }
1377 else
1378 {
1379 CalcMuzzlePoint ( ent, forwardVec, vrightVec, up, muzzle , 0);
1380 }
1381 }
1382
1383 // fire the specific weapon
1384 switch( ent->s.weapon )
1385 {
1386 // Player weapons
1387 //-----------------
1388 case WP_SABER:
1389 return;
1390 break;
1391
1392 case WP_BRYAR_PISTOL:
1393 case WP_BLASTER_PISTOL:
1394 WP_FireBryarPistol( ent, alt_fire );
1395 break;
1396
1397 case WP_BLASTER:
1398 WP_FireBlaster( ent, alt_fire );
1399 break;
1400
1401 case WP_TUSKEN_RIFLE:
1402 if ( alt_fire )
1403 {
1404 WP_FireTuskenRifle( ent );
1405 }
1406 else
1407 {
1408 WP_Melee( ent );
1409 }
1410 break;
1411
1412 case WP_DISRUPTOR:
1413 alert = 50; // if you want it to alert enemies, remove this
1414 WP_FireDisruptor( ent, alt_fire );
1415 break;
1416
1417 case WP_BOWCASTER:
1418 WP_FireBowcaster( ent, alt_fire );
1419 break;
1420
1421 case WP_REPEATER:
1422 WP_FireRepeater( ent, alt_fire );
1423 break;
1424
1425 case WP_DEMP2:
1426 WP_FireDEMP2( ent, alt_fire );
1427 break;
1428
1429 case WP_FLECHETTE:
1430 WP_FireFlechette( ent, alt_fire );
1431 break;
1432
1433 case WP_ROCKET_LAUNCHER:
1434 WP_FireRocket( ent, alt_fire );
1435 break;
1436
1437 case WP_CONCUSSION:
1438 WP_Concussion( ent, alt_fire );
1439 break;
1440
1441 case WP_THERMAL:
1442 WP_FireThermalDetonator( ent, alt_fire );
1443 break;
1444
1445 case WP_TRIP_MINE:
1446 alert = 0; // if you want it to alert enemies, remove this
1447 WP_PlaceLaserTrap( ent, alt_fire );
1448 break;
1449
1450 case WP_DET_PACK:
1451 alert = 0; // if you want it to alert enemies, remove this
1452 WP_FireDetPack( ent, alt_fire );
1453 break;
1454
1455 case WP_BOT_LASER:
1456 WP_BotLaser( ent );
1457 break;
1458
1459 case WP_EMPLACED_GUN:
1460 // doesn't care about whether it's alt-fire or not. We can do an alt-fire if needed
1461 WP_EmplacedFire( ent );
1462 break;
1463
1464 case WP_MELEE:
1465 alert = 0; // if you want it to alert enemies, remove this
1466 if ( !alt_fire || !g_debugMelee->integer )
1467 {
1468 WP_Melee( ent );
1469 }
1470 break;
1471
1472 case WP_ATST_MAIN:
1473 WP_ATSTMainFire( ent );
1474 break;
1475
1476 case WP_ATST_SIDE:
1477
1478 // TEMP
1479 if ( alt_fire )
1480 {
1481 // WP_FireRocket( ent, qfalse );
1482 WP_ATSTSideAltFire(ent);
1483 }
1484 else
1485 {
1486 // FIXME!
1487 /* if ( ent->s.number == 0
1488 && ent->client->NPC_class == CLASS_VEHICLE
1489 && vehicleData[((CVehicleNPC *)ent->NPC)->m_iVehicleTypeID].type == VH_FIGHTER )
1490 {
1491 WP_ATSTMainFire( ent );
1492 }
1493 else*/
1494 {
1495 WP_ATSTSideFire(ent);
1496 }
1497 }
1498 break;
1499
1500 case WP_TIE_FIGHTER:
1501 // TEMP
1502 WP_EmplacedFire( ent );
1503 break;
1504
1505 case WP_RAPID_FIRE_CONC:
1506 // TEMP
1507 if ( alt_fire )
1508 {
1509 WP_FireRepeater( ent, alt_fire );
1510 }
1511 else
1512 {
1513 WP_EmplacedFire( ent );
1514 }
1515 break;
1516
1517 case WP_STUN_BATON:
1518 WP_FireStunBaton( ent, alt_fire );
1519 break;
1520
1521 // case WP_BLASTER_PISTOL:
1522 case WP_JAWA:
1523 WP_FireBryarPistol( ent, qfalse ); // never an alt-fire?
1524 break;
1525
1526 case WP_SCEPTER:
1527 WP_FireScepter( ent, alt_fire );
1528 break;
1529
1530 case WP_NOGHRI_STICK:
1531 if ( !alt_fire )
1532 {
1533 WP_FireNoghriStick( ent );
1534 }
1535 //else does melee attack/damage/func
1536 break;
1537
1538 case WP_TUSKEN_STAFF:
1539 default:
1540 return;
1541 break;
1542 }
1543
1544 if ( !ent->s.number )
1545 {
1546 if ( ent->s.weapon == WP_FLECHETTE || (ent->s.weapon == WP_BOWCASTER && !alt_fire) )
1547 {//these can fire multiple shots, count them individually within the firing functions
1548 }
1549 else if ( W_AccuracyLoggableWeapon( ent->s.weapon, alt_fire, MOD_UNKNOWN ) )
1550 {
1551 ent->client->sess.missionStats.shotsFired++;
1552 }
1553 }
1554 // We should probably just use this as a default behavior, in special cases, just set alert to false.
1555 if ( ent->s.number == 0 && alert > 0 )
1556 {
1557 if ( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD//FIXME: check for sand contents type?
1558 && ent->s.weapon != WP_STUN_BATON
1559 && ent->s.weapon != WP_MELEE
1560 && ent->s.weapon != WP_TUSKEN_STAFF
1561 && ent->s.weapon != WP_THERMAL
1562 && ent->s.weapon != WP_TRIP_MINE
1563 && ent->s.weapon != WP_DET_PACK )
1564 {//the vibration of the shot carries through your feet into the ground
1565 AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED, qfalse, qtrue );
1566 }
1567 else
1568 {//an in-air alert
1569 AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED );
1570 }
1571 AddSightEvent( ent, muzzle, alert*2, AEL_DISCOVERED, 20 );
1572 }
1573 }
1574
1575 //NOTE: Emplaced gun moved to g_emplaced.cpp
1576
1577 /*QUAKED misc_weapon_shooter (1 0 0) (-8 -8 -8) (8 8 8) ALTFIRE TOGGLE
1578 ALTFIRE - fire the alt-fire of the chosen weapon
1579 TOGGLE - keep firing until used again (fires at intervals of "wait")
1580
1581 "wait" - debounce time between refires (defaults to 500)
1582 "delay" - speed of WP_THERMAL (default is 900)
1583 "random" - ranges from 0 to random, added to wait (defaults to 0)
1584
1585 "target" - what to aim at (will update aim every frame if it's a moving target)
1586
1587 "weapon" - specify the weapon to use (default is WP_BLASTER)
1588 WP_BRYAR_PISTOL
1589 WP_BLASTER
1590 WP_DISRUPTOR
1591 WP_BOWCASTER
1592 WP_REPEATER
1593 WP_DEMP2
1594 WP_FLECHETTE
1595 WP_ROCKET_LAUNCHER
1596 WP_CONCUSSION
1597 WP_THERMAL
1598 WP_TRIP_MINE
1599 WP_DET_PACK
1600 WP_STUN_BATON
1601 WP_EMPLACED_GUN
1602 WP_BOT_LASER
1603 WP_TURRET
1604 WP_ATST_MAIN
1605 WP_ATST_SIDE
1606 WP_TIE_FIGHTER
1607 WP_RAPID_FIRE_CONC
1608 WP_BLASTER_PISTOL
1609 */
misc_weapon_shooter_fire(gentity_t * self)1610 void misc_weapon_shooter_fire( gentity_t *self )
1611 {
1612 FireWeapon( self, (qboolean)((self->spawnflags&1) != 0) );
1613 if ( (self->spawnflags&2) )
1614 {//repeat
1615 self->e_ThinkFunc = thinkF_misc_weapon_shooter_fire;
1616 if (self->random)
1617 {
1618 self->nextthink = level.time + self->wait + (int)(Q_flrand(0.0f, 1.0f)*self->random);
1619 }
1620 else
1621 {
1622 self->nextthink = level.time + self->wait;
1623 }
1624 }
1625 }
1626
misc_weapon_shooter_use(gentity_t * self,gentity_t * other,gentity_t * activator)1627 void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
1628 {
1629 if ( self->e_ThinkFunc == thinkF_misc_weapon_shooter_fire )
1630 {//repeating fire, stop
1631 self->e_ThinkFunc = thinkF_NULL;
1632 self->nextthink = -1;
1633 return;
1634 }
1635 //otherwise, fire
1636 misc_weapon_shooter_fire( self );
1637 }
1638
misc_weapon_shooter_aim(gentity_t * self)1639 void misc_weapon_shooter_aim( gentity_t *self )
1640 {
1641 //update my aim
1642 if ( self->target )
1643 {
1644 gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
1645 if ( targ )
1646 {
1647 self->enemy = targ;
1648 VectorSubtract( targ->currentOrigin, self->currentOrigin, self->client->renderInfo.muzzleDir );
1649 VectorCopy( targ->currentOrigin, self->pos1 );
1650 vectoangles( self->client->renderInfo.muzzleDir, self->client->ps.viewangles );
1651 SetClientViewAngle( self, self->client->ps.viewangles );
1652 //FIXME: don't keep doing this unless target is a moving target?
1653 self->nextthink = level.time + FRAMETIME;
1654 }
1655 else
1656 {
1657 self->enemy = NULL;
1658 }
1659 }
1660 }
1661
1662 extern stringID_table_t WPTable[];
SP_misc_weapon_shooter(gentity_t * self)1663 void SP_misc_weapon_shooter( gentity_t *self )
1664 {
1665 //alloc a client just for the weapon code to use
1666 self->client = (gclient_t *)gi.Malloc(sizeof(gclient_t), TAG_G_ALLOC, qtrue);
1667
1668 //set weapon
1669 self->s.weapon = self->client->ps.weapon = WP_BLASTER;
1670 if ( self->paintarget )
1671 {//use a different weapon
1672 self->s.weapon = self->client->ps.weapon = GetIDForString( WPTable, self->paintarget );
1673 }
1674
1675 //set where our muzzle is
1676 VectorCopy( self->s.origin, self->client->renderInfo.muzzlePoint );
1677 //permanently updated
1678 self->client->renderInfo.mPCalcTime = Q3_INFINITE;
1679
1680 //set up to link
1681 if ( self->target )
1682 {
1683 self->e_ThinkFunc = thinkF_misc_weapon_shooter_aim;
1684 self->nextthink = level.time + START_TIME_LINK_ENTS;
1685 }
1686 else
1687 {//just set aim angles
1688 VectorCopy( self->s.angles, self->client->ps.viewangles );
1689 AngleVectors( self->s.angles, self->client->renderInfo.muzzleDir, NULL, NULL );
1690 }
1691
1692 //set up to fire when used
1693 self->e_UseFunc = useF_misc_weapon_shooter_use;
1694
1695 if ( !self->wait )
1696 {
1697 self->wait = 500;
1698 }
1699 }
1700