1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 #include "g_local.h"
24 #include "g_functions.h"
25 #include "anims.h"
26 #include "wp_saber.h"
27 #include "../cgame/cg_local.h"
28 #include "b_local.h"
29 #include "g_navigator.h"
30
31 extern Vehicle_t *G_IsRidingVehicle( gentity_t *pEnt );
32
33 //lock the owner into place relative to the cannon pos
EWebPositionUser(gentity_t * owner,gentity_t * eweb)34 void EWebPositionUser(gentity_t *owner, gentity_t *eweb)
35 {
36 mdxaBone_t boltMatrix;
37 vec3_t p, p2, d;
38 trace_t tr;
39 qboolean traceOver = qtrue;
40
41 if ( owner->s.number < MAX_CLIENTS )
42 {//extra checks
43 gi.trace(&tr, owner->currentOrigin, owner->mins, owner->maxs, owner->currentOrigin, owner->s.number, owner->clipmask, (EG2_Collision)0, 0);
44 if ( tr.startsolid || tr.allsolid )
45 {//crap, they're already in solid somehow, don't bother tracing over
46 traceOver = qfalse;
47 }
48 }
49 if ( traceOver )
50 {//trace up
51 VectorCopy( owner->currentOrigin, p2 );
52 p2[2] += STEPSIZE;
53 gi.trace(&tr, owner->currentOrigin, owner->mins, owner->maxs, p2, owner->s.number, owner->clipmask, (EG2_Collision)0, 0);
54 if (!tr.startsolid && !tr.allsolid )
55 {
56 VectorCopy( tr.endpos, p2 );
57 }
58 else
59 {
60 VectorCopy( owner->currentOrigin, p2 );
61 }
62 }
63 //trace over
64 gi.G2API_GetBoltMatrix( eweb->ghoul2, 0, eweb->headBolt, &boltMatrix,
65 eweb->s.apos.trBase, eweb->currentOrigin,
66 (cg.time?cg.time:level.time), NULL, eweb->s.modelScale );
67 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, p );
68 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, d );
69 d[2] = 0;
70 VectorNormalize( d );
71 VectorMA( p, -44.0f, d, p );
72 if ( !traceOver )
73 {
74 VectorCopy( p, tr.endpos );
75 tr.allsolid = tr.startsolid = qfalse;
76 }
77 else
78 {
79 p[2] = p2[2];
80 if ( owner->s.number < MAX_CLIENTS )
81 {//extra checks
82 //just see if end point is not in solid
83 gi.trace(&tr, p, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask, (EG2_Collision)0, 0);
84 if ( tr.startsolid || tr.allsolid )
85 {//would be in solid there, so just trace over, I guess?
86 gi.trace(&tr, p2, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask, (EG2_Collision)0, 0);
87 }
88 }
89 else
90 {//trace over
91 gi.trace(&tr, p2, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask, (EG2_Collision)0, 0);
92 }
93 }
94 if (!tr.startsolid && !tr.allsolid )
95 {
96 //trace down
97 VectorCopy( tr.endpos, p );
98 VectorCopy( p, p2 );
99 p2[2] -= STEPSIZE;
100 gi.trace(&tr, p, owner->mins, owner->maxs, p2, owner->s.number, owner->clipmask, (EG2_Collision)0, 0);
101
102 if (!tr.startsolid && !tr.allsolid )//&& tr.fraction == 1.0f)
103 { //all clear, we can move there
104 vec3_t moveDir;
105 float moveDist;
106 VectorCopy( tr.endpos, p );
107 VectorSubtract( p, eweb->pos4, moveDir );
108 moveDist = VectorNormalize( moveDir );
109 if ( moveDist > 4.0f )
110 {//moved past the threshold from last position
111 vec3_t oRight;
112 int strafeAnim;
113
114 VectorCopy( p, eweb->pos4 );//update the position
115 //find out what direction he moved in
116 AngleVectors( owner->currentAngles, NULL, oRight, NULL );
117 if ( DotProduct( moveDir, oRight ) > 0 )
118 {//moved to his right, play right strafe
119 strafeAnim = BOTH_STRAFE_RIGHT1;
120 }
121 else
122 {//moved left, play left strafe
123 strafeAnim = BOTH_STRAFE_LEFT1;
124 }
125 NPC_SetAnim( owner, SETANIM_LEGS, strafeAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
126 }
127
128 G_SetOrigin(owner, p);
129 VectorCopy(p, owner->client->ps.origin);
130 gi.linkentity( owner );
131 }
132 }
133 //FIXME: IK the hands to the handles of the gun?
134 }
135
136 //===============================================
137 //End E-Web
138 //===============================================
139
140 //----------------------------------------------------------
141
142 //===============================================
143 //Emplaced Gun
144 //===============================================
145
146 // spawnflag
147 #define EMPLACED_INACTIVE 1
148 #define EMPLACED_FACING 2
149 #define EMPLACED_VULNERABLE 4
150 #define EWEB_INVULNERABLE 4
151 #define EMPLACED_PLAYERUSE 8
152
153 /*QUAKED emplaced_eweb (0 0 1) (-12 -12 -24) (12 12 24) INACTIVE FACING INVULNERABLE PLAYERUSE
154
155 INACTIVE cannot be used until used by a target_activate
156 FACING - player must be facing relatively in the same direction as the gun in order to use it
157 VULNERABLE - allow the gun to take damage
158 PLAYERUSE - only the player makes it run its usescript
159
160 count - how much ammo to give this gun ( default 999 )
161 health - how much damage the gun can take before it blows ( default 250 )
162 delay - ONLY AFFECTS NPCs - time between shots ( default 200 on hardest setting )
163 wait - ONLY AFFECTS NPCs - time between bursts ( default 800 on hardest setting )
164 splashdamage - how much damage a blowing up gun deals ( default 80 )
165 splashradius - radius for exploding damage ( default 128 )
166
167 scripts:
168 will run usescript, painscript and deathscript
169 */
170 //----------------------------------------------------------
eweb_pain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,const vec3_t point,int damage,int mod,int hitLoc)171 void eweb_pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc )
172 {
173 if ( self->health <= 0 )
174 {
175 // play pain effect?
176 }
177 else
178 {
179 if ( self->paintarget )
180 {
181 G_UseTargets2( self, self->activator, self->paintarget );
182 }
183
184 // Don't do script if dead
185 G_ActivateBehavior( self, BSET_PAIN );
186 }
187 }
188 //----------------------------------------------------------
eweb_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)189 void eweb_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
190 {
191 vec3_t org;
192
193 // turn off any firing animations it may have been doing
194 self->s.frame = self->startFrame = self->endFrame = 0;
195 self->svFlags &= ~(SVF_ANIMATING|SVF_PLAYER_USABLE);
196
197
198 self->health = 0;
199 // self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon
200
201 self->takedamage = qfalse;
202 self->lastEnemy = attacker;
203
204 if ( self->activator && self->activator->client )
205 {
206 if ( self->activator->NPC )
207 {
208 vec3_t right;
209
210 // radius damage seems to throw them, but add an extra bit to throw them away from the weapon
211 AngleVectors( self->currentAngles, NULL, right, NULL );
212 VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity );
213 self->activator->client->ps.velocity[2] = -100;
214
215 // kill them
216 self->activator->health = 0;
217 self->activator->client->ps.stats[STAT_HEALTH] = 0;
218 }
219
220 // kill the players emplaced ammo, cheesy way to keep the gun from firing
221 self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0;
222 }
223
224 self->e_PainFunc = painF_NULL;
225
226 if ( self->target )
227 {
228 G_UseTargets( self, attacker );
229 }
230
231 G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
232
233 VectorCopy( self->currentOrigin, org );
234 org[2] += 20;
235
236 G_PlayEffect( "emplaced/explode", org );
237
238 // Turn the top of the eweb off.
239 #define TURN_OFF 0x00000100//G2SURFACEFLAG_NODESCENDANTS
240 gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "eweb_damage", TURN_OFF );
241
242 // create some persistent smoke by using a dynamically created fx runner
243 gentity_t *ent = G_Spawn();
244
245 if ( ent )
246 {
247 ent->delay = 200;
248 ent->random = 100;
249
250 ent->fxID = G_EffectIndex( "emplaced/dead_smoke" );
251
252 ent->e_ThinkFunc = thinkF_fx_runner_think;
253 ent->nextthink = level.time + 50;
254
255 // move up above the gun origin
256 VectorCopy( self->currentOrigin, org );
257 org[2] += 35;
258 G_SetOrigin( ent, org );
259 VectorCopy( org, ent->s.origin );
260
261 VectorSet( ent->s.angles, -90, 0, 0 ); // up
262 G_SetAngles( ent, ent->s.angles );
263
264 gi.linkentity( ent );
265 }
266
267 G_ActivateBehavior( self, BSET_DEATH );
268 }
269
eweb_can_be_used(gentity_t * self,gentity_t * other,gentity_t * activator)270 qboolean eweb_can_be_used( gentity_t *self, gentity_t *other, gentity_t *activator )
271 {
272 if ( self->health <= 0 )
273 {
274 // can't use a dead gun.
275 return qfalse;
276 }
277
278 if ( self->svFlags & SVF_INACTIVE )
279 {
280 return qfalse; // can't use inactive gun
281 }
282
283 if ( !activator->client )
284 {
285 return qfalse; // only a client can use it.
286 }
287
288 if ( self->activator )
289 {
290 // someone is already in the gun.
291 return qfalse;
292 }
293
294 if ( other && other->client && G_IsRidingVehicle( other ) )
295 {//can't use eweb when on a vehicle
296 return qfalse;
297 }
298
299 if ( activator && activator->client && G_IsRidingVehicle( activator ) )
300 {//can't use eweb when on a vehicle
301 return qfalse;
302 }
303
304 if ( activator && activator->client && (activator->client->ps.pm_flags&PMF_DUCKED) )
305 {//stand up, ya cowardly varmint!
306 return qfalse;
307 }
308
309 if ( activator && activator->health <= 0 )
310 {//dead men ain't got no more use fer guns...
311 return qfalse;
312 }
313
314 vec3_t fwd1, fwd2;
315 vec3_t facingAngles;
316
317 VectorAdd( self->s.angles, self->pos1, facingAngles );
318 if ( activator->s.number < MAX_CLIENTS )
319 {//player must be facing general direction of the turret head
320 // Let's get some direction vectors for the users
321 AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL );
322 fwd1[2] = 0;
323
324 // Get the gun's direction vector
325 AngleVectors( facingAngles, fwd2, NULL, NULL );
326 fwd2[2] = 0;
327
328 float dot = DotProduct( fwd1, fwd2 );
329
330 // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it.
331 if ( dot < 0.75f )
332 {
333 return qfalse;
334 }
335 }
336
337 if ( self->delay + 500 < level.time )
338 {
339 return qtrue;
340 }
341 return qfalse;
342 }
343
eweb_use(gentity_t * self,gentity_t * other,gentity_t * activator)344 void eweb_use( gentity_t *self, gentity_t *other, gentity_t *activator )
345 {
346 if ( !eweb_can_be_used( self, other, activator ) )
347 {
348 return;
349 }
350
351 int oldWeapon = activator->s.weapon;
352
353 if ( oldWeapon == WP_SABER )
354 {
355 self->alt_fire = activator->client->ps.SaberActive();
356 }
357
358 // swap the users weapon with the emplaced gun and add the ammo the gun has to the player
359 activator->client->ps.weapon = self->s.weapon;
360 Add_Ammo( activator, WP_EMPLACED_GUN, self->count );
361 activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN );
362
363 // Allow us to point from one to the other
364 activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it.
365 self->activator = activator;
366
367 G_RemoveWeaponModels( activator );
368
369 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
370 if ( activator->NPC )
371 {
372 ChangeWeapon( activator, WP_EMPLACED_GUN );
373 }
374 else if ( activator->s.number == 0 )
375 {
376 // we don't want for it to draw the weapon select stuff
377 cg.weaponSelect = WP_EMPLACED_GUN;
378 CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 );
379 }
380
381 VectorCopy( activator->currentOrigin, self->pos4 );//keep this around so we know when to make them play the strafe anim
382
383 // the gun will track which weapon we used to have
384 self->s.weapon = oldWeapon;
385
386 // Lock the player
387 activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON;
388 activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it.
389 self->activator = activator;
390 self->delay = level.time; // can't disconnect from the thing for half a second
391
392 // Let the gun be considered an enemy
393 //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have
394 self->svFlags |= SVF_NONNPC_ENEMY;
395 self->noDamageTeam = activator->client->playerTeam;
396
397 //FIXME: should really wait a bit after spawn and get this just once?
398 self->waypoint = NAV::GetNearestNode(self);
399 #ifdef _DEBUG
400 if ( self->waypoint == -1 )
401 {
402 gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) );
403 }
404 #endif
405
406 G_Sound( self, G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" ));
407
408 if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 )
409 {//player-only usescript or any usescript
410 // Run use script
411 G_ActivateBehavior( self, BSET_USE );
412 }
413 }
414
415 //----------------------------------------------------------
SP_emplaced_eweb(gentity_t * ent)416 void SP_emplaced_eweb( gentity_t *ent )
417 {
418 char name[] = "models/map_objects/hoth/eweb_model.glm";
419
420 ent->svFlags |= SVF_PLAYER_USABLE;
421 ent->contents = CONTENTS_BODY;
422
423 if ( ent->spawnflags & EMPLACED_INACTIVE )
424 {
425 ent->svFlags |= SVF_INACTIVE;
426 }
427
428 VectorSet( ent->mins, -12, -12, -24 );
429 VectorSet( ent->maxs, 12, 12, 24 );
430
431 ent->takedamage = qtrue;
432
433 if ( ( ent->spawnflags & EWEB_INVULNERABLE ))
434 {
435 ent->flags |= FL_GODMODE;
436 }
437
438 ent->s.radius = 80;
439 ent->spawnflags |= 4; // deadsolid
440
441 //ent->e_ThinkFunc = thinkF_NULL;
442 ent->e_PainFunc = painF_eweb_pain;
443 ent->e_DieFunc = dieF_eweb_die;
444
445 G_EffectIndex( "emplaced/explode" );
446 G_EffectIndex( "emplaced/dead_smoke" );
447
448 G_SoundIndex( "sound/weapons/eweb/eweb_aim.wav" );
449 G_SoundIndex( "sound/weapons/eweb/eweb_dismount.mp3" );
450 //G_SoundIndex( "sound/weapons/eweb/eweb_empty.wav" );
451 G_SoundIndex( "sound/weapons/eweb/eweb_fire.wav" );
452 G_SoundIndex( "sound/weapons/eweb/eweb_hitplayer.wav" );
453 G_SoundIndex( "sound/weapons/eweb/eweb_hitsurface.wav" );
454 //G_SoundIndex( "sound/weapons/eweb/eweb_load.wav" );
455 G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" );
456
457 // Set up our defaults and override with custom amounts as necessary
458 G_SpawnInt( "count", "999", &ent->count );
459 G_SpawnInt( "health", "250", &ent->health );
460 G_SpawnInt( "splashDamage", "40", &ent->splashDamage );
461 G_SpawnInt( "splashRadius", "100", &ent->splashRadius );
462 G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!!
463 G_SpawnFloat( "wait", "800", &ent->wait );
464
465 ent->max_health = ent->health;
466 ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud
467
468 ent->s.modelindex = G_ModelIndex( name );
469 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
470
471 // Activate our tags and bones
472 ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*cannonflash" ); //muzzle bolt
473 ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "cannon_Xrot" ); //for placing the owner relative to rotation
474 ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
475 ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cannon_Yrot", qtrue );
476 ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cannon_Xrot", qtrue );
477 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL, 0, 0);
478 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL, 0, 0);
479 //gi.G2API_SetBoneAngles( &ent->ghoul2[0], "cannon_Yrot", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL);
480 //set the constraints for this guy as an emplaced weapon, and his constraint angles
481 //ent->s.origin2[0] = 60.0f; //60 degrees in either direction
482
483 RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN ));
484 ent->s.weapon = WP_EMPLACED_GUN;
485
486 G_SetOrigin( ent, ent->s.origin );
487 G_SetAngles( ent, ent->s.angles );
488 VectorCopy( ent->s.angles, ent->lastAngles );
489
490 // store base angles for later
491 VectorClear( ent->pos1 );
492
493 ent->e_UseFunc = useF_eweb_use;
494 ent->bounceCount = 1;//to distinguish it from the emplaced gun
495
496 gi.linkentity (ent);
497 }
498
499 /*QUAKED emplaced_gun (0 0 1) (-24 -24 0) (24 24 64) INACTIVE x VULNERABLE PLAYERUSE
500
501 INACTIVE cannot be used until used by a target_activate
502 VULNERABLE - allow the gun to take damage
503 PLAYERUSE - only the player makes it run its usescript
504
505 count - how much ammo to give this gun ( default 999 )
506 health - how much damage the gun can take before it blows ( default 250 )
507 delay - ONLY AFFECTS NPCs - time between shots ( default 200 on hardest setting )
508 wait - ONLY AFFECTS NPCs - time between bursts ( default 800 on hardest setting )
509 splashdamage - how much damage a blowing up gun deals ( default 80 )
510 splashradius - radius for exploding damage ( default 128 )
511
512 scripts:
513 will run usescript, painscript and deathscript
514 */
515
516 //----------------------------------------------------------
emplaced_gun_use(gentity_t * self,gentity_t * other,gentity_t * activator)517 void emplaced_gun_use( gentity_t *self, gentity_t *other, gentity_t *activator )
518 {
519 vec3_t fwd1, fwd2;
520
521 if ( self->health <= 0 )
522 {
523 // can't use a dead gun.
524 return;
525 }
526
527 if ( self->svFlags & SVF_INACTIVE )
528 {
529 return; // can't use inactive gun
530 }
531
532 if ( !activator->client )
533 {
534 return; // only a client can use it.
535 }
536
537 if ( self->activator )
538 {
539 // someone is already in the gun.
540 return;
541 }
542
543 if ( other && other->client && G_IsRidingVehicle( other ) )
544 {//can't use eweb when on a vehicle
545 return;
546 }
547
548 if ( activator && activator->client && G_IsRidingVehicle( activator ) )
549 {//can't use eweb when on a vehicle
550 return;
551 }
552
553 // We'll just let the designers duke this one out....I mean, as to whether they even want to limit such a thing.
554 if ( self->spawnflags & EMPLACED_FACING )
555 {
556 // Let's get some direction vectors for the users
557 AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL );
558
559 // Get the guns direction vector
560 AngleVectors( self->pos1, fwd2, NULL, NULL );
561
562 float dot = DotProduct( fwd1, fwd2 );
563
564 // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it.
565 if ( dot < 0.0f )
566 {
567 return;
568 }
569 }
570
571 // don't allow using it again for half a second
572 if ( self->delay + 500 < level.time )
573 {
574 int oldWeapon = activator->s.weapon;
575
576 if ( oldWeapon == WP_SABER )
577 {
578 self->alt_fire = activator->client->ps.SaberActive();
579 }
580
581 // swap the users weapon with the emplaced gun and add the ammo the gun has to the player
582 activator->client->ps.weapon = self->s.weapon;
583 Add_Ammo( activator, WP_EMPLACED_GUN, self->count );
584 activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN );
585
586 // Allow us to point from one to the other
587 activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it.
588 self->activator = activator;
589
590 G_RemoveWeaponModels( activator );
591
592 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
593 if ( activator->NPC )
594 {
595 ChangeWeapon( activator, WP_EMPLACED_GUN );
596 }
597 else if ( activator->s.number == 0 )
598 {
599 // we don't want for it to draw the weapon select stuff
600 cg.weaponSelect = WP_EMPLACED_GUN;
601 CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 );
602 }
603 // Since we move the activator inside of the gun, we reserve a solid spot where they were standing in order to be able to get back out without being in solid
604 if ( self->nextTrain )
605 {//you never know
606 G_FreeEntity( self->nextTrain );
607 }
608 self->nextTrain = G_Spawn();
609 //self->nextTrain->classname = "emp_placeholder";
610 self->nextTrain->contents = CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP;//hmm... playerclip too now that we're doing it for NPCs?
611 G_SetOrigin( self->nextTrain, activator->client->ps.origin );
612 VectorCopy( activator->mins, self->nextTrain->mins );
613 VectorCopy( activator->maxs, self->nextTrain->maxs );
614 gi.linkentity( self->nextTrain );
615
616 //need to inflate the activator's mins/maxs since the gunsit anim puts them outside of their bbox
617 VectorSet( activator->mins, -24, -24, -24 );
618 VectorSet( activator->maxs, 24, 24, 40 );
619
620 // Move the activator into the center of the gun. For NPC's the only way the can get out of the gun is to die.
621 VectorCopy( self->s.origin, activator->client->ps.origin );
622 activator->client->ps.origin[2] += 30; // move them up so they aren't standing in the floor
623 gi.linkentity( activator );
624
625 // the gun will track which weapon we used to have
626 self->s.weapon = oldWeapon;
627
628 // Lock the player
629 activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON;
630 activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it.
631 self->activator = activator;
632 self->delay = level.time; // can't disconnect from the thing for half a second
633
634 // Let the gun be considered an enemy
635 //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have
636 self->svFlags |= SVF_NONNPC_ENEMY;
637 self->noDamageTeam = activator->client->playerTeam;
638
639 // FIXME: don't do this, we'll try and actually put the player in this beast
640 // move the player to the center of the gun
641 // activator->contents = 0;
642 // VectorCopy( self->currentOrigin, activator->client->ps.origin );
643
644 SetClientViewAngle( activator, self->pos1 );
645
646 //FIXME: should really wait a bit after spawn and get this just once?
647 self->waypoint = NAV::GetNearestNode(self);
648 #ifdef _DEBUG
649 if ( self->waypoint == -1 )
650 {
651 gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) );
652 }
653 #endif
654
655 G_Sound( self, G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" ));
656
657 if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 )
658 {//player-only usescript or any usescript
659 // Run use script
660 G_ActivateBehavior( self, BSET_USE );
661 }
662 }
663 }
664
665 //----------------------------------------------------------
emplaced_gun_pain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,const vec3_t point,int damage,int mod,int hitLoc)666 void emplaced_gun_pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc )
667 {
668 if ( self->health <= 0 )
669 {
670 // play pain effect?
671 }
672 else
673 {
674 if ( self->paintarget )
675 {
676 G_UseTargets2( self, self->activator, self->paintarget );
677 }
678
679 // Don't do script if dead
680 G_ActivateBehavior( self, BSET_PAIN );
681 }
682 }
683
684 //----------------------------------------------------------
emplaced_blow(gentity_t * ent)685 void emplaced_blow( gentity_t *ent )
686 {
687 ent->e_DieFunc = dieF_NULL;
688 emplaced_gun_die( ent, ent->lastEnemy, ent->lastEnemy, 0, MOD_UNKNOWN );
689 }
690
691 //----------------------------------------------------------
emplaced_gun_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)692 void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
693 {
694 vec3_t org;
695
696 // turn off any firing animations it may have been doing
697 self->s.frame = self->startFrame = self->endFrame = 0;
698 self->svFlags &= ~SVF_ANIMATING;
699
700 self->health = 0;
701 // self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon
702
703 self->takedamage = qfalse;
704 self->lastEnemy = attacker;
705
706 // we defer explosion so the player has time to get out
707 if ( self->e_DieFunc )
708 {
709 self->e_ThinkFunc = thinkF_emplaced_blow;
710 self->nextthink = level.time + 3000; // don't blow for a couple of seconds
711 return;
712 }
713
714 if ( self->activator && self->activator->client )
715 {
716 if ( self->activator->NPC )
717 {
718 vec3_t right;
719
720 // radius damage seems to throw them, but add an extra bit to throw them away from the weapon
721 AngleVectors( self->currentAngles, NULL, right, NULL );
722 VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity );
723 self->activator->client->ps.velocity[2] = -100;
724
725 // kill them
726 self->activator->health = 0;
727 self->activator->client->ps.stats[STAT_HEALTH] = 0;
728 }
729
730 // kill the players emplaced ammo, cheesy way to keep the gun from firing
731 self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0;
732 }
733
734 self->e_PainFunc = painF_NULL;
735 self->e_ThinkFunc = thinkF_NULL;
736
737 if ( self->target )
738 {
739 G_UseTargets( self, attacker );
740 }
741
742 G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
743
744 // when the gun is dead, add some ugliness to it.
745 vec3_t ugly;
746
747 ugly[YAW] = 4;
748 ugly[PITCH] = self->lastAngles[PITCH] * 0.8f + Q_flrand(-1.0f, 1.0f) * 6;
749 ugly[ROLL] = Q_flrand(-1.0f, 1.0f) * 7;
750 gi.G2API_SetBoneAnglesIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, ugly, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
751
752 VectorCopy( self->currentOrigin, org );
753 org[2] += 20;
754
755 G_PlayEffect( "emplaced/explode", org );
756
757 // create some persistent smoke by using a dynamically created fx runner
758 gentity_t *ent = G_Spawn();
759
760 if ( ent )
761 {
762 ent->delay = 200;
763 ent->random = 100;
764
765 ent->fxID = G_EffectIndex( "emplaced/dead_smoke" );
766
767 ent->e_ThinkFunc = thinkF_fx_runner_think;
768 ent->nextthink = level.time + 50;
769
770 // move up above the gun origin
771 VectorCopy( self->currentOrigin, org );
772 org[2] += 35;
773 G_SetOrigin( ent, org );
774 VectorCopy( org, ent->s.origin );
775
776 VectorSet( ent->s.angles, -90, 0, 0 ); // up
777 G_SetAngles( ent, ent->s.angles );
778
779 gi.linkentity( ent );
780 }
781
782 G_ActivateBehavior( self, BSET_DEATH );
783 }
784
785 //----------------------------------------------------------
SP_emplaced_gun(gentity_t * ent)786 void SP_emplaced_gun( gentity_t *ent )
787 {
788 char name[] = "models/map_objects/imp_mine/turret_chair.glm";
789
790 ent->svFlags |= SVF_PLAYER_USABLE;
791 ent->contents = CONTENTS_BODY;//CONTENTS_SHOTCLIP|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP;//CONTENTS_SOLID;
792
793 if ( ent->spawnflags & EMPLACED_INACTIVE )
794 {
795 ent->svFlags |= SVF_INACTIVE;
796 }
797
798 VectorSet( ent->mins, -30, -30, -5 );
799 VectorSet( ent->maxs, 30, 30, 60 );
800
801 ent->takedamage = qtrue;
802
803 if ( !( ent->spawnflags & EMPLACED_VULNERABLE ))
804 {
805 ent->flags |= FL_GODMODE;
806 }
807
808 ent->s.radius = 110;
809 ent->spawnflags |= 4; // deadsolid
810
811 //ent->e_ThinkFunc = thinkF_NULL;
812 ent->e_PainFunc = painF_emplaced_gun_pain;
813 ent->e_DieFunc = dieF_emplaced_gun_die;
814
815 G_EffectIndex( "emplaced/explode" );
816 G_EffectIndex( "emplaced/dead_smoke" );
817
818 G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" );
819 G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" );
820 G_SoundIndex( "sound/weapons/emplaced/emplaced_move_lp.wav" );
821
822 // Set up our defaults and override with custom amounts as necessary
823 G_SpawnInt( "count", "999", &ent->count );
824 G_SpawnInt( "health", "250", &ent->health );
825 G_SpawnInt( "splashDamage", "80", &ent->splashDamage );
826 G_SpawnInt( "splashRadius", "128", &ent->splashRadius );
827 G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!!
828 G_SpawnFloat( "wait", "800", &ent->wait );
829
830 ent->max_health = ent->health;
831 ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud
832
833 ent->s.modelindex = G_ModelIndex( name );
834 ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
835
836 // Activate our tags and bones
837 ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*seat" );
838 ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*flash01" );
839 ent->handRBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*flash02" );
840 ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "base_bone", qtrue );
841 ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "swivel_bone", qtrue );
842 gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0);
843
844 RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN ));
845 ent->s.weapon = WP_EMPLACED_GUN;
846
847 G_SetOrigin( ent, ent->s.origin );
848 G_SetAngles( ent, ent->s.angles );
849 VectorCopy( ent->s.angles, ent->lastAngles );
850
851 // store base angles for later
852 VectorCopy( ent->s.angles, ent->pos1 );
853
854 ent->e_UseFunc = useF_emplaced_gun_use;
855 ent->bounceCount = 0;//to distinguish it from the eweb
856
857 gi.linkentity (ent);
858 }
859
860 //====================================================
861 //General Emplaced Weapon Funcs called in g_active.cpp
862 //====================================================
863
G_UpdateEmplacedWeaponData(gentity_t * ent)864 void G_UpdateEmplacedWeaponData( gentity_t *ent )
865 {
866 if ( ent && ent->owner && ent->health > 0 )
867 {
868 gentity_t *chair = ent->owner;
869 if ( chair->e_UseFunc == useF_emplaced_gun_use )//yeah, crappy way to check this, but...
870 {//one that you sit in
871 //take the emplaced gun's waypoint as your own
872 ent->waypoint = chair->waypoint;
873
874 //update the actual origin of the sitter
875 mdxaBone_t boltMatrix;
876 vec3_t chairAng = {0, ent->client->ps.viewangles[YAW], 0};
877
878 // Getting the seat bolt here
879 gi.G2API_GetBoltMatrix( chair->ghoul2, chair->playerModel, chair->headBolt,
880 &boltMatrix, chairAng, chair->currentOrigin, (cg.time?cg.time:level.time),
881 NULL, chair->s.modelScale );
882 // Storing ent position, bolt position, and bolt axis
883 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->ps.origin );
884 gi.linkentity( ent );
885 }
886 else if ( chair->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but...
887 {//standing at an E-Web
888 EWebPositionUser( ent, chair );
889 }
890 }
891 }
892
ExitEmplacedWeapon(gentity_t * ent)893 void ExitEmplacedWeapon( gentity_t *ent )
894 {
895 // requesting to unlock from the weapon
896 // We'll leave the gun pointed in the direction it was last facing, though we'll cut out the pitch
897 if ( ent->client )
898 {
899 // if we are the player we will have put down a brush that blocks NPCs so that we have a clear spot to get back out.
900 //gentity_t *place = G_Find( NULL, FOFS(classname), "emp_placeholder" );
901
902 if ( ent->health > 0 )
903 {//he's still alive, and we have a placeholder, so put him back
904 if ( ent->owner->nextTrain )
905 {
906 // reset the players position
907 VectorCopy( ent->owner->nextTrain->currentOrigin, ent->client->ps.origin );
908 //reset ent's size to normal
909 VectorCopy( ent->owner->nextTrain->mins, ent->mins );
910 VectorCopy( ent->owner->nextTrain->maxs, ent->maxs );
911 //free the placeholder
912 G_FreeEntity( ent->owner->nextTrain );
913 //re-link the ent
914 gi.linkentity( ent );
915 }
916 else if ( ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but...
917 {
918 // so give 'em a push away from us
919 vec3_t backDir, start, end;
920 trace_t trace;
921 gentity_t *eweb = ent->owner;
922 float curRadius = 0.0f;
923 float minRadius, maxRadius;
924 qboolean safeExit = qfalse;
925
926 VectorSubtract( ent->currentOrigin, eweb->currentOrigin, backDir );
927 backDir[2] = 0;
928 minRadius = VectorNormalize( backDir )-8.0f;
929
930 maxRadius = (ent->maxs[0]+ent->maxs[1])*0.5f;
931 maxRadius += (eweb->maxs[0]+eweb->maxs[1])*0.5f;
932 maxRadius *= 1.5f;
933
934 if ( minRadius >= maxRadius - 1.0f )
935 {
936 maxRadius = minRadius + 8.0f;
937 }
938
939 ent->owner = NULL;//so his trace hits me
940
941 for ( curRadius = minRadius; curRadius <= maxRadius; curRadius += 4.0f )
942 {
943 VectorMA( ent->currentOrigin, curRadius, backDir, start );
944 //make sure they're not in the ground
945 VectorCopy( start, end );
946 start[2] += 18;
947 end[2] -= 18;
948 gi.trace(&trace, start, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask, (EG2_Collision)0, 0);
949 if ( !trace.allsolid && !trace.startsolid )
950 {
951 G_SetOrigin( ent, trace.endpos );
952 gi.linkentity( ent );
953 safeExit = qtrue;
954 break;
955 }
956 }
957 //Hmm... otherwise, don't allow them to get off?
958 ent->owner = eweb;
959 if ( !safeExit )
960 {//don't try again for a second
961 ent->owner->delay = level.time + 500;
962 return;
963 }
964 }
965 }
966 else if ( ent->health <= 0 )
967 {
968 // dead, so give 'em a push out of the chair
969 vec3_t dir;
970 AngleVectors( ent->owner->s.angles, NULL, dir, NULL );
971
972 if ( rand() & 1 )
973 {
974 VectorScale( dir, -1, dir );
975 }
976
977 VectorMA( ent->client->ps.velocity, 75, dir, ent->client->ps.velocity );
978 }
979 //don't let them move towards me for a couple frames so they don't step back into me while I'm becoming solid to them
980 if ( ent->s.number < MAX_CLIENTS )
981 {
982 if ( ent->client->ps.pm_time < 100 )
983 {
984 ent->client->ps.pm_time = 100;
985 }
986 ent->client->ps.pm_flags |= (PMF_TIME_NOFRICTION|PMF_TIME_KNOCKBACK);
987 }
988
989 if ( !ent->owner->bounceCount )
990 {//not an EWeb - the overridden bone angles will remember the angle we left it at
991 VectorCopy( ent->client->ps.viewangles, ent->owner->s.angles );
992 ent->owner->s.angles[PITCH] = 0;
993 G_SetAngles( ent->owner, ent->owner->s.angles );
994 VectorCopy( ent->owner->s.angles, ent->owner->pos1 );
995 }
996 }
997
998 // Remove the emplaced gun from our inventory
999 ent->client->ps.stats[STAT_WEAPONS] &= ~( 1 << WP_EMPLACED_GUN );
1000
1001 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
1002 extern void CG_ChangeWeapon( int num );
1003 if ( ent->health <= 0 )
1004 {//when die, don't set weapon back on when ejected from emplaced/eweb
1005 //empty hands
1006 ent->client->ps.weapon = WP_NONE;
1007 if ( ent->NPC )
1008 {
1009 ChangeWeapon( ent, ent->client->ps.weapon ); // should be OK actually.
1010 }
1011 else
1012 {
1013 CG_ChangeWeapon( ent->client->ps.weapon );
1014 }
1015 if ( ent->s.number < MAX_CLIENTS )
1016 {
1017 gi.cvar_set( "cg_thirdperson", "1" );
1018 }
1019 }
1020 else
1021 {
1022 // when we lock or unlock from the the gun, we get our old weapon back
1023 ent->client->ps.weapon = ent->owner->s.weapon;
1024
1025 if ( ent->NPC )
1026 {//BTW, if a saber-using NPC ever gets off of an emplaced gun/eweb, this will not work, look at NPC_ChangeWeapon for the proper way
1027 ChangeWeapon( ent, ent->client->ps.weapon );
1028 }
1029 else
1030 {
1031 G_RemoveWeaponModels( ent );
1032 CG_ChangeWeapon( ent->client->ps.weapon );
1033 if ( ent->client->ps.weapon == WP_SABER )
1034 {
1035 WP_SaberAddG2SaberModels( ent );
1036 }
1037 else
1038 {
1039 G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 );
1040 }
1041
1042 if ( ent->s.number < MAX_CLIENTS )
1043 {
1044 if ( ent->client->ps.weapon == WP_SABER )
1045 {
1046 gi.cvar_set( "cg_thirdperson", "1" );
1047 }
1048 else if ( ent->client->ps.weapon != WP_SABER && cg_gunAutoFirst.integer )
1049 {
1050 gi.cvar_set( "cg_thirdperson", "0" );
1051 }
1052 }
1053 }
1054
1055 if ( ent->client->ps.weapon == WP_SABER )
1056 {
1057 if ( ent->owner->alt_fire )
1058 {
1059 ent->client->ps.SaberActivate();
1060 }
1061 else
1062 {
1063 ent->client->ps.SaberDeactivate();
1064 }
1065 }
1066 }
1067 //set the emplaced gun/eweb's weapon back to the emplaced gun
1068 ent->owner->s.weapon = WP_EMPLACED_GUN;
1069 // gi.G2API_DetachG2Model( &ent->ghoul2[ent->playerModel] );
1070
1071 ent->s.eFlags &= ~EF_LOCKED_TO_WEAPON;
1072 ent->client->ps.eFlags &= ~EF_LOCKED_TO_WEAPON;
1073
1074 ent->owner->noDamageTeam = TEAM_FREE;
1075 ent->owner->svFlags &= ~SVF_NONNPC_ENEMY;
1076 ent->owner->delay = level.time;
1077 ent->owner->activator = NULL;
1078
1079 if ( !ent->NPC )
1080 {
1081 // by keeping the owner, a dead npc can be pushed out of the chair without colliding with it
1082 ent->owner = NULL;
1083 }
1084 }
1085
RunEmplacedWeapon(gentity_t * ent,usercmd_t ** ucmd)1086 void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd )
1087 {
1088 if (( (*ucmd)->buttons & BUTTON_USE || (*ucmd)->forwardmove < 0 || (*ucmd)->upmove > 0 ) && ent->owner && ent->owner->delay + 500 < level.time )
1089 {
1090 ent->owner->s.loopSound = 0;
1091
1092 if ( ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but...
1093 {
1094 G_Sound( ent, G_SoundIndex( "sound/weapons/eweb/eweb_dismount.mp3" ));
1095 }
1096 else
1097 {
1098 G_Sound( ent, G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" ));
1099
1100 }
1101
1102 ExitEmplacedWeapon( ent );
1103 (*ucmd)->buttons &= ~BUTTON_USE;
1104 if ( (*ucmd)->upmove > 0 )
1105 {//don't actually jump
1106 (*ucmd)->upmove = 0;
1107 }
1108 }
1109 else
1110 {
1111 // this is a crappy way to put sounds on a moving eweb....
1112 if ( ent->owner
1113 && ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but...
1114 {
1115 if ( !VectorCompare( ent->client->ps.viewangles, ent->owner->movedir ))
1116 {
1117 ent->owner->s.loopSound = G_SoundIndex( "sound/weapons/eweb/eweb_aim.wav" );
1118 ent->owner->fly_sound_debounce_time = level.time;
1119 }
1120 else
1121 {
1122 if ( ent->owner->fly_sound_debounce_time + 100 <= level.time )
1123 {
1124 ent->owner->s.loopSound = 0;
1125 }
1126 }
1127
1128 VectorCopy( ent->client->ps.viewangles, ent->owner->movedir );
1129 }
1130
1131 // don't allow movement, weapon switching, and most kinds of button presses
1132 (*ucmd)->forwardmove = 0;
1133 (*ucmd)->rightmove = 0;
1134 (*ucmd)->upmove = 0;
1135 (*ucmd)->buttons &= (BUTTON_ATTACK|BUTTON_ALT_ATTACK);
1136
1137 (*ucmd)->weapon = ent->client->ps.weapon; //WP_EMPLACED_GUN;
1138
1139 if ( ent->health <= 0 )
1140 {
1141 ExitEmplacedWeapon( ent );
1142 }
1143 }
1144 }
1145