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 #include "g_headers.h"
23
24 #include "g_local.h"
25 #include "g_functions.h"
26 #include "b_local.h"
27
28 extern team_t TranslateTeamName( const char *name );
29 extern cvar_t *g_spskill;
30
31 void G_SetEnemy( gentity_t *self, gentity_t *enemy );
32 void finish_spawning_turret( gentity_t *base );
33 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
34 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
35
36 #define ARM_ANGLE_RANGE 60
37 #define HEAD_ANGLE_RANGE 90
38
39 //------------------------------------------------------------------------------------------------------------
TurretPain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,vec3_t point,int damage,int mod,int hitLoc)40 void TurretPain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod, int hitLoc )
41 //------------------------------------------------------------------------------------------------------------
42 {
43 vec3_t dir;
44
45 VectorSubtract( point, self->currentOrigin, dir );
46 VectorNormalize( dir );
47
48 if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
49 {
50 // DEMP2 makes the turret stop shooting for a bit..and does extra feedback
51 self->attackDebounceTime = level.time + 800 + Q_flrand(0.0f, 1.0f) * 500;
52 G_PlayEffect( "spark_exp_nosnd", point, dir );
53 }
54
55 G_PlayEffect( "spark_exp_nosnd", point, dir );
56 }
57
58 //------------------------------------------------------------------------------------------------------------
turret_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)59 void turret_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
60 //------------------------------------------------------------------------------------------------------------
61 {
62 vec3_t forward = { 0,0,-1 }, pos;
63
64 // Turn off the thinking of the base & use it's targets
65 self->e_ThinkFunc = thinkF_NULL;
66 self->e_UseFunc = useF_NULL;
67
68 // clear my data
69 self->e_DieFunc = dieF_NULL;
70 self->takedamage = qfalse;
71 self->health = 0;
72 self->s.loopSound = 0;
73
74 // hack the effect angle so that explode death can orient the effect properly
75 if ( self->spawnflags & 2 )
76 {
77 VectorSet( forward, 0, 0, 1 );
78 }
79
80 // VectorCopy( self->currentOrigin, self->s.pos.trBase );
81
82 if ( self->fxID > 0 )
83 {
84 VectorMA( self->currentOrigin, 12, forward, pos );
85 G_PlayEffect( self->fxID, pos, forward );
86 }
87
88 if ( self->splashDamage > 0 && self->splashRadius > 0 )
89 {
90 G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, attacker, MOD_UNKNOWN );
91 }
92
93 if ( self->s.eFlags & EF_SHADER_ANIM )
94 {
95 self->s.frame = 1; // black
96 }
97
98 self->s.weapon = 0; // crosshair code uses this to mark crosshair red
99
100 if ( self->s.modelindex2 )
101 {
102 // switch to damage model if we should
103 self->s.modelindex = self->s.modelindex2;
104
105 VectorCopy( self->currentAngles, self->s.apos.trBase );
106 VectorClear( self->s.apos.trDelta );
107
108 if ( self->target )
109 {
110 G_UseTargets( self, attacker );
111 }
112 }
113 else
114 {
115 ObjectDie( self, inflictor, attacker, damage, meansOfDeath );
116 }
117 }
118
119 #define START_DIS 15
120
121 //----------------------------------------------------------------
turret_fire(gentity_t * ent,vec3_t start,vec3_t dir)122 static void turret_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
123 //----------------------------------------------------------------
124 {
125 vec3_t org;
126 gentity_t *bolt;
127
128 if ( gi.pointcontents( start, MASK_SHOT ))
129 {
130 return;
131 }
132
133 VectorMA( start, -START_DIS, dir, org ); // dumb....
134 G_PlayEffect( "blaster/muzzle_flash", org, dir );
135
136 bolt = G_Spawn();
137
138 bolt->classname = "turret_proj";
139 bolt->nextthink = level.time + 10000;
140 bolt->e_ThinkFunc = thinkF_G_FreeEntity;
141 bolt->s.eType = ET_MISSILE;
142 bolt->s.weapon = WP_BLASTER;
143 bolt->owner = ent;
144 bolt->damage = ent->damage;
145 bolt->dflags = DAMAGE_NO_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS; // Don't push them around, or else we are constantly re-aiming
146 bolt->splashDamage = 0;
147 bolt->splashRadius = 0;
148 bolt->methodOfDeath = MOD_ENERGY;
149 bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
150 bolt->trigger_formation = qfalse; // don't draw tail on first frame
151
152 VectorSet( bolt->maxs, 1.5, 1.5, 1.5 );
153 VectorScale( bolt->maxs, -1, bolt->mins );
154 bolt->s.pos.trType = TR_LINEAR;
155 bolt->s.pos.trTime = level.time;
156 VectorCopy( start, bolt->s.pos.trBase );
157 VectorScale( dir, 1100, bolt->s.pos.trDelta );
158 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
159 VectorCopy( start, bolt->currentOrigin);
160 }
161
162 //-----------------------------------------------------
turret_head_think(gentity_t * self)163 void turret_head_think( gentity_t *self )
164 //-----------------------------------------------------
165 {
166 // if it's time to fire and we have an enemy, then gun 'em down! pushDebounce time controls next fire time
167 if ( self->enemy && self->pushDebounceTime < level.time && self->attackDebounceTime < level.time )
168 {
169 // set up our next fire time
170 self->pushDebounceTime = level.time + self->wait;
171
172 vec3_t fwd, org;
173 mdxaBone_t boltMatrix;
174
175 // Getting the flash bolt here
176 gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
177 self->torsoBolt,
178 &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time),
179 NULL, self->s.modelScale );
180
181 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
182 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
183
184 VectorMA( org, START_DIS, fwd, org );
185
186 turret_fire( self, org, fwd );
187 self->fly_sound_debounce_time = level.time;//used as lastShotTime
188 }
189 }
190
191 //-----------------------------------------------------
turret_aim(gentity_t * self)192 static void turret_aim( gentity_t *self )
193 //-----------------------------------------------------
194 {
195 vec3_t enemyDir, org, org2;
196 vec3_t desiredAngles, setAngle;
197 float diffYaw = 0.0f, diffPitch = 0.0f;
198
199 // move our gun base yaw to where we should be at this time....
200 EvaluateTrajectory( &self->s.apos, level.time, self->currentAngles );
201 self->currentAngles[YAW] = AngleNormalize360( self->currentAngles[YAW] );
202 self->speed = AngleNormalize360( self->speed );
203
204 if ( self->enemy )
205 {
206 // ...then we'll calculate what new aim adjustments we should attempt to make this frame
207 // Aim at enemy
208 if ( self->enemy->client )
209 {
210 VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
211 }
212 else
213 {
214 VectorCopy( self->enemy->currentOrigin, org );
215 }
216 if ( self->spawnflags & 2 )
217 {
218 org[2] -= 15;
219 }
220 else
221 {
222 org[2] -= 5;
223 }
224 mdxaBone_t boltMatrix;
225
226 // Getting the "eye" here
227 gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
228 self->torsoBolt,
229 &boltMatrix, self->currentAngles, self->s.origin, (cg.time?cg.time:level.time),
230 NULL, self->s.modelScale );
231
232 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
233
234 VectorSubtract( org, org2, enemyDir );
235 vectoangles( enemyDir, desiredAngles );
236
237 diffYaw = AngleSubtract( self->currentAngles[YAW], desiredAngles[YAW] );
238 diffPitch = AngleSubtract( self->speed, desiredAngles[PITCH] );
239 }
240 else
241 {
242 // no enemy, so make us slowly sweep back and forth as if searching for a new one
243 // diffYaw = sin( level.time * 0.0001f + self->count ) * 5.0f; // don't do this for now since it can make it go into walls.
244 }
245
246 if ( diffYaw )
247 {
248 // cap max speed....
249 if ( fabs(diffYaw) > 14.0f )
250 {
251 diffYaw = ( diffYaw >= 0 ? 14.0f : -14.0f );
252 }
253
254 // ...then set up our desired yaw
255 VectorSet( setAngle, 0.0f, diffYaw, 0.0f );
256
257 VectorCopy( self->currentAngles, self->s.apos.trBase );
258 VectorScale( setAngle,- 5, self->s.apos.trDelta );
259 self->s.apos.trTime = level.time;
260 self->s.apos.trType = TR_LINEAR;
261 }
262
263 if ( diffPitch )
264 {
265 if ( fabs(diffPitch) > 3.0f )
266 {
267 // cap max speed
268 self->speed += (diffPitch > 0.0f) ? -3.0f : 3.0f;
269 }
270 else
271 {
272 // small enough, so just add half the diff so we smooth out the stopping
273 self->speed -= ( diffPitch );//desiredAngles[PITCH];
274 }
275
276 // Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit?
277 if ( self->spawnflags & 2 )
278 {
279 VectorSet( desiredAngles, self->speed, 0.0f, 0.0f );
280 }
281 else
282 {
283 VectorSet( desiredAngles, -self->speed, 0.0f, 0.0f );
284 }
285
286 gi.G2API_SetBoneAngles( &self->ghoul2[0], "Bone_body", desiredAngles,
287 BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg.time );
288 }
289
290 if ( diffYaw || diffPitch )
291 {
292 self->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
293 }
294 else
295 {
296 self->s.loopSound = 0;
297 }
298 }
299
300 //-----------------------------------------------------
turret_turnoff(gentity_t * self)301 static void turret_turnoff( gentity_t *self )
302 //-----------------------------------------------------
303 {
304 if ( self->enemy == NULL )
305 {
306 // we don't need to turnoff
307 return;
308 }
309
310 // shut-down sound
311 G_Sound( self, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
312
313 // make turret play ping sound for 5 seconds
314 self->aimDebounceTime = level.time + 5000;
315
316 // Clear enemy
317 self->enemy = NULL;
318 }
319
320 //-----------------------------------------------------
turret_find_enemies(gentity_t * self)321 static qboolean turret_find_enemies( gentity_t *self )
322 //-----------------------------------------------------
323 {
324 qboolean found = qfalse;
325 int count;
326 float bestDist = self->radius * self->radius;
327 float enemyDist;
328 vec3_t enemyDir, org, org2;
329 gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
330
331 if ( self->aimDebounceTime > level.time ) // time since we've been shut off
332 {
333 // We were active and alert, i.e. had an enemy in the last 3 secs
334 if ( self->painDebounceTime < level.time )
335 {
336 G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" ));
337 self->painDebounceTime = level.time + 1000;
338 }
339 }
340
341 VectorCopy( self->currentOrigin, org2 );
342 if ( self->spawnflags & 2 )
343 {
344 org2[2] += 20;
345 }
346 else
347 {
348 org2[2] -= 20;
349 }
350
351 count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
352
353 for ( int i = 0; i < count; i++ )
354 {
355 target = entity_list[i];
356
357 if ( !target->client )
358 {
359 // only attack clients
360 continue;
361 }
362 if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
363 {
364 continue;
365 }
366 if ( target->client->playerTeam == self->noDamageTeam )
367 {
368 // A bot we don't want to shoot
369 continue;
370 }
371 if ( !gi.inPVS( org2, target->currentOrigin ))
372 {
373 continue;
374 }
375
376 VectorCopy( target->client->renderInfo.eyePoint, org );
377
378 if ( self->spawnflags & 2 )
379 {
380 org[2] -= 15;
381 }
382 else
383 {
384 org[2] += 5;
385 }
386
387 trace_t tr;
388 gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
389
390 if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
391 {
392 // Only acquire if have a clear shot, Is it in range and closer than our best?
393 VectorSubtract( target->currentOrigin, self->currentOrigin, enemyDir );
394 enemyDist = VectorLengthSquared( enemyDir );
395
396 if ( enemyDist < bestDist )// all things equal, keep current
397 {
398 if ( self->attackDebounceTime < level.time )
399 {
400 // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
401 G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" ));
402
403 // Wind up turrets for a bit
404 self->attackDebounceTime = level.time + 1400;
405 }
406
407 bestTarget = target;
408 bestDist = enemyDist;
409 found = qtrue;
410 }
411 }
412 }
413
414 if ( found )
415 {
416 if ( !self->enemy )
417 {//just aquired one
418 AddSoundEvent( bestTarget, self->currentOrigin, 256, AEL_DISCOVERED );
419 AddSightEvent( bestTarget, self->currentOrigin, 512, AEL_DISCOVERED, 20 );
420 }
421 G_SetEnemy( self, bestTarget );
422 if ( VALIDSTRING( self->target2 ))
423 {
424 G_UseTargets2( self, self, self->target2 );
425 }
426 }
427
428 return found;
429 }
430
431 //-----------------------------------------------------
turret_base_think(gentity_t * self)432 void turret_base_think( gentity_t *self )
433 //-----------------------------------------------------
434 {
435 qboolean turnOff = qtrue;
436 float enemyDist;
437 vec3_t enemyDir, org, org2;
438
439 self->nextthink = level.time + FRAMETIME;
440
441 if ( self->spawnflags & 1 )
442 {
443 // not turned on
444 turret_turnoff( self );
445 turret_aim( self );
446
447 // No target
448 self->flags |= FL_NOTARGET;
449 return;
450 }
451 else
452 {
453 // I'm all hot and bothered
454 self->flags &= ~FL_NOTARGET;
455 }
456
457 if ( !self->enemy )
458 {
459 if ( turret_find_enemies( self ))
460 {
461 turnOff = qfalse;
462 }
463 }
464 else
465 {
466 if ( self->enemy->health > 0 )
467 {
468 // enemy is alive
469 VectorSubtract( self->enemy->currentOrigin, self->currentOrigin, enemyDir );
470 enemyDist = VectorLengthSquared( enemyDir );
471
472 if ( enemyDist < self->radius * self->radius )
473 {
474 // was in valid radius
475 if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) )
476 {
477 // Every now and again, check to see if we can even trace to the enemy
478 trace_t tr;
479
480 if ( self->enemy->client )
481 {
482 VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
483 }
484 else
485 {
486 VectorCopy( self->enemy->currentOrigin, org );
487 }
488 VectorCopy( self->currentOrigin, org2 );
489 if ( self->spawnflags & 2 )
490 {
491 org2[2] += 10;
492 }
493 else
494 {
495 org2[2] -= 10;
496 }
497 gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
498
499 if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number )
500 {
501 turnOff = qfalse; // Can see our enemy
502 }
503 }
504 }
505 }
506
507 turret_head_think( self );
508 }
509
510 if ( turnOff )
511 {
512 if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off
513 {
514 turret_turnoff( self );
515 }
516 }
517 else
518 {
519 // keep our enemy for a minimum of 2 seconds from now
520 self->bounceCount = level.time + 2000 + Q_flrand(0.0f, 1.0f) * 150;
521 }
522
523 turret_aim( self );
524 }
525
526 //-----------------------------------------------------------------------------
turret_base_use(gentity_t * self,gentity_t * other,gentity_t * activator)527 void turret_base_use( gentity_t *self, gentity_t *other, gentity_t *activator )
528 //-----------------------------------------------------------------------------
529 {
530 // Toggle on and off
531 self->spawnflags = (self->spawnflags ^ 1);
532
533 if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off
534 {
535 self->s.frame = 1; // black
536 }
537 else
538 {
539 self->s.frame = 0; // glow
540 }
541 }
542
543
544 /*QUAKED misc_turret (1 0 0) (-8 -8 -22) (8 8 0) START_OFF UPSIDE_DOWN
545 Turret that hangs from the ceiling, will aim and shoot at enemies
546
547 START_OFF - Starts off
548 UPSIDE_DOWN - make it rest on a surface/floor instead of hanging from the ceiling
549
550 radius - How far away an enemy can be for it to pick it up (default 512)
551 wait - Time between shots (default 150 ms)
552 dmg - How much damage each shot does (default 5)
553 health - How much damage it can take before exploding (default 100)
554
555 splashDamage - How much damage the explosion does
556 splashRadius - The radius of the explosion
557 NOTE: If either of the above two are 0, it will not make an explosion
558
559 targetname - Toggles it on/off
560 target - What to use when destroyed
561 target2 - What to use when it decides to start shooting at an enemy
562
563 team - team that is not targeted by and does not take damage from this turret
564 "player",
565 "enemy", (default)
566 "neutral"
567 */
568 //-----------------------------------------------------
SP_misc_turret(gentity_t * base)569 void SP_misc_turret( gentity_t *base )
570 //-----------------------------------------------------
571 {
572 base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/turret_canon.glm" );
573 base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" );
574 base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/turret_canon.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
575 base->s.radius = 80.0f;
576
577 gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
578 base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash03" );
579
580 finish_spawning_turret( base );
581
582 if (( base->spawnflags & 1 )) // Start_Off
583 {
584 base->s.frame = 1; // black
585 }
586 else
587 {
588 base->s.frame = 0; // glow
589 }
590 base->s.eFlags |= EF_SHADER_ANIM;
591 }
592
593 //-----------------------------------------------------
finish_spawning_turret(gentity_t * base)594 void finish_spawning_turret( gentity_t *base )
595 {
596 vec3_t fwd;
597
598 if ( base->spawnflags & 2 )
599 {
600 base->s.angles[ROLL] += 180;
601 base->s.origin[2] -= 22.0f;
602 }
603
604 G_SetAngles( base, base->s.angles );
605 AngleVectors( base->currentAngles, fwd, NULL, NULL );
606
607 G_SetOrigin(base, base->s.origin);
608
609 base->noDamageTeam = TEAM_ENEMY;
610
611 base->s.eType = ET_GENERAL;
612
613 if ( base->team && base->team[0] )
614 {
615 base->noDamageTeam = TranslateTeamName( base->team );
616 base->team = NULL;
617 }
618
619 // Set up our explosion effect for the ExplodeDeath code....
620 base->fxID = G_EffectIndex( "turret/explode" );
621 G_EffectIndex( "spark_exp_nosnd" );
622
623 base->e_UseFunc = useF_turret_base_use;
624 base->e_PainFunc = painF_TurretPain;
625
626 // don't start working right away
627 base->e_ThinkFunc = thinkF_turret_base_think;
628 base->nextthink = level.time + FRAMETIME * 5;
629
630 // this is really the pitch angle.....
631 base->speed = 0;
632
633 // this is a random time offset for the no-enemy-search-around-mode
634 base->count = Q_flrand(0.0f, 1.0f) * 9000;
635
636 if ( !base->health )
637 {
638 base->health = 100;
639 }
640
641 // search radius
642 if ( !base->radius )
643 {
644 base->radius = 512;
645 }
646
647 // How quickly to fire
648 if ( !base->wait )
649 {
650 base->wait = 150 + Q_flrand(0.0f, 1.0f) * 55;
651 }
652
653 if ( !base->splashDamage )
654 {
655 base->splashDamage = 10;
656 }
657
658 if ( !base->splashRadius )
659 {
660 base->splashRadius = 25;
661 }
662
663 // how much damage each shot does
664 if ( !base->damage )
665 {
666 base->damage = 5;
667 }
668
669 if ( base->spawnflags & 2 )
670 {//upside-down, invert mins and maxe
671 VectorSet( base->maxs, 10.0f, 10.0f, 30.0f );
672 VectorSet( base->mins, -10.0f, -10.0f, 0.0f );
673 }
674 else
675 {
676 VectorSet( base->maxs, 10.0f, 10.0f, 0.0f );
677 VectorSet( base->mins, -10.0f, -10.0f, -30.0f );
678 }
679 // Precache moving sounds
680 G_SoundIndex( "sound/chars/turret/startup.wav" );
681 G_SoundIndex( "sound/chars/turret/shutdown.wav" );
682 G_SoundIndex( "sound/chars/turret/ping.wav" );
683 G_SoundIndex( "sound/chars/turret/move.wav" );
684
685 base->contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP;
686
687 base->max_health = base->health;
688 base->takedamage = qtrue;
689 base->e_DieFunc = dieF_turret_die;
690
691 base->material = MAT_METAL;
692 base->svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
693
694 // Register this so that we can use it for the missile effect
695 RegisterItem( FindItemForWeapon( WP_BLASTER ));
696
697 // But set us as a turret so that we can be identified as a turret
698 base->s.weapon = WP_TURRET;
699
700 gi.linkentity( base );
701 }
702
703 /*QUAKED misc_ns_turret (1 0 0) (-8 -8 -32) (8 8 29) START_OFF
704 NS turret that only hangs from the ceiling, will aim and shoot at enemies
705
706 START_OFF - Starts off
707
708 radius - How far away an enemy can be for it to pick it up (default 512)
709 wait - Time between shots (default 150 ms)
710 dmg - How much damage each shot does (default 5)
711 health - How much damage it can take before exploding (default 100)
712
713 splashDamage - How much damage the explosion does
714 splashRadius - The radius of the explosion
715 NOTE: If either of the above two are 0, it will not make an explosion
716
717 targetname - Toggles it on/off
718 target - What to use when destroyed
719
720 team - team that is not targeted by and does not take damage from this turret
721 "player",
722 "enemy", (default)
723 "neutral"
724 */
725 //-----------------------------------------------------
SP_misc_ns_turret(gentity_t * base)726 void SP_misc_ns_turret( gentity_t *base )
727 //-----------------------------------------------------
728 {
729 base->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/turret/turret.glm" );
730 base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" ); // FIXME!
731 base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/nar_shaddar/turret/turret.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
732 base->s.radius = 80.0f;
733
734 gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
735 base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
736
737 finish_spawning_turret( base );
738 }
739
740 //--------------------------------------
741
laser_arm_fire(gentity_t * ent)742 void laser_arm_fire (gentity_t *ent)
743 {
744 vec3_t start, end, fwd, rt, up;
745 trace_t trace;
746
747 if ( ent->attackDebounceTime < level.time && ent->alt_fire )
748 {
749 // If I'm firing the laser and it's time to quit....then quit!
750 ent->alt_fire = qfalse;
751 // ent->e_ThinkFunc = thinkF_NULL;
752 // return;
753 }
754
755 ent->nextthink = level.time + FRAMETIME;
756
757 // If a fool gets in the laser path, fry 'em
758 AngleVectors( ent->currentAngles, fwd, rt, up );
759
760 VectorMA( ent->currentOrigin, 20, fwd, start );
761 //VectorMA( start, -6, rt, start );
762 //VectorMA( start, -3, up, start );
763 VectorMA( start, 4096, fwd, end );
764
765 gi.trace( &trace, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT, G2_NOCOLLIDE, 0 );//ignore
766 ent->fly_sound_debounce_time = level.time;//used as lastShotTime
767
768 // Only deal damage when in alt-fire mode
769 if ( trace.fraction < 1.0 && ent->alt_fire )
770 {
771 if ( trace.entityNum < ENTITYNUM_WORLD )
772 {
773 gentity_t *hapless_victim = &g_entities[trace.entityNum];
774 if ( hapless_victim && hapless_victim->takedamage && ent->damage )
775 {
776 G_Damage( hapless_victim, ent, ent->nextTrain->activator, fwd, trace.endpos, ent->damage, DAMAGE_IGNORE_TEAM, MOD_UNKNOWN );
777 }
778 }
779 }
780
781 if ( ent->alt_fire )
782 {
783 // CG_FireLaser( start, trace.endpos, trace.plane.normal, ent->nextTrain->startRGBA, qfalse );
784 }
785 else
786 {
787 // CG_AimLaser( start, trace.endpos, trace.plane.normal );
788 }
789 }
790
laser_arm_use(gentity_t * self,gentity_t * other,gentity_t * activator)791 void laser_arm_use (gentity_t *self, gentity_t *other, gentity_t *activator)
792 {
793 vec3_t newAngles;
794
795 self->activator = activator;
796 switch( self->count )
797 {
798 case 0:
799 default:
800 //Fire
801 //gi.Printf("FIRE!\n");
802 // self->lastEnemy->lastEnemy->e_ThinkFunc = thinkF_laser_arm_fire;
803 // self->lastEnemy->lastEnemy->nextthink = level.time + FRAMETIME;
804 //For 3 seconds
805 self->lastEnemy->lastEnemy->alt_fire = qtrue; // Let 'er rip!
806 self->lastEnemy->lastEnemy->attackDebounceTime = level.time + self->lastEnemy->lastEnemy->wait;
807 G_Sound(self->lastEnemy->lastEnemy, G_SoundIndex("sound/chars/l_arm/fire.wav"));
808 break;
809 case 1:
810 //Yaw left
811 //gi.Printf("LEFT...\n");
812 VectorCopy( self->lastEnemy->currentAngles, newAngles );
813 newAngles[1] += self->speed;
814 G_SetAngles( self->lastEnemy, newAngles );
815 // bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS );
816 G_Sound( self->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
817 break;
818 case 2:
819 //Yaw right
820 //gi.Printf("RIGHT...\n");
821 VectorCopy( self->lastEnemy->currentAngles, newAngles );
822 newAngles[1] -= self->speed;
823 G_SetAngles( self->lastEnemy, newAngles );
824 // bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS );
825 G_Sound( self->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
826 break;
827 case 3:
828 //pitch up
829 //gi.Printf("UP...\n");
830 //FIXME: Clamp
831 VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles );
832 newAngles[0] -= self->speed;
833 if ( newAngles[0] < -45 )
834 {
835 newAngles[0] = -45;
836 }
837 G_SetAngles( self->lastEnemy->lastEnemy, newAngles );
838 G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
839 break;
840 case 4:
841 //pitch down
842 //gi.Printf("DOWN...\n");
843 //FIXME: Clamp
844 VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles );
845 newAngles[0] += self->speed;
846 if ( newAngles[0] > 90 )
847 {
848 newAngles[0] = 90;
849 }
850 G_SetAngles( self->lastEnemy->lastEnemy, newAngles );
851 G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) );
852 break;
853 }
854 }
855 /*QUAKED misc_laser_arm (1 0 0) (-8 -8 -8) (8 8 8)
856
857 What it does when used depends on it's "count" (can be set by a script)
858 count:
859 0 (default) - Fire in direction facing
860 1 turn left
861 2 turn right
862 3 aim up
863 4 aim down
864
865 speed - How fast it turns (degrees per second, default 30)
866 dmg - How much damage the laser does 10 times a second (default 5 = 50 points per second)
867 wait - How long the beam lasts, in seconds (default is 3)
868
869 targetname - to use it
870 target - What thing for it to be pointing at to start with
871
872 "startRGBA" - laser color, Red Green Blue Alpha, range 0 to 1 (default 1.0 0.85 0.15 0.75 = Yellow-Orange)
873 */
laser_arm_start(gentity_t * base)874 void laser_arm_start (gentity_t *base)
875 {
876 vec3_t armAngles;
877 vec3_t headAngles;
878
879 base->e_ThinkFunc = thinkF_NULL;
880 //We're the base, spawn the arm and head
881 gentity_t *arm = G_Spawn();
882 gentity_t *head = G_Spawn();
883
884 VectorCopy( base->s.angles, armAngles );
885 VectorCopy( base->s.angles, headAngles );
886 if ( base->target && base->target[0] )
887 {//Start out pointing at something
888 gentity_t *targ = G_Find( NULL, FOFS(targetname), base->target );
889 if ( !targ )
890 {//couldn't find it!
891 Com_Printf(S_COLOR_RED "ERROR : laser_arm can't find target %s!\n", base->target);
892 }
893 else
894 {//point at it
895 vec3_t dir, angles;
896
897 VectorSubtract(targ->currentOrigin, base->s.origin, dir );
898 vectoangles( dir, angles );
899 armAngles[1] = angles[1];
900 headAngles[0] = angles[0];
901 headAngles[1] = angles[1];
902 }
903 }
904
905 //Base
906 //Base does the looking for enemies and pointing the arm and head
907 G_SetAngles( base, base->s.angles );
908 //base->s.origin[2] += 4;
909 G_SetOrigin(base, base->s.origin);
910 gi.linkentity(base);
911 //FIXME: need an actual model
912 base->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_base.md3");
913 base->s.eType = ET_GENERAL;
914 G_SpawnVector4( "startRGBA", "1.0 0.85 0.15 0.75", (float *)&base->startRGBA );
915 //anglespeed - how fast it can track the player, entered in degrees per second, so we divide by FRAMETIME/1000
916 if ( !base->speed )
917 {
918 base->speed = 3.0f;
919 }
920 else
921 {
922 base->speed *= FRAMETIME/1000.0f;
923 }
924 base->e_UseFunc = useF_laser_arm_use;
925 base->nextthink = level.time + FRAMETIME;
926
927 //Arm
928 //Does nothing, not solid, gets removed when head explodes
929 G_SetOrigin( arm, base->s.origin );
930 gi.linkentity(arm);
931 G_SetAngles( arm, armAngles );
932 // bolt_head_to_arm( arm, head, LARM_FOFS, LARM_ROFS, LARM_UOFS );
933 arm->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_arm.md3");
934
935 //Head
936 //Fires when enemy detected, animates, can be blown up
937 //Need to normalize the headAngles pitch for the clamping later
938 if ( headAngles[0] < -180 )
939 {
940 headAngles[0] += 360;
941 }
942 else if ( headAngles[0] > 180 )
943 {
944 headAngles[0] -= 360;
945 }
946 G_SetAngles( head, headAngles );
947 head->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_head.md3");
948 head->s.eType = ET_GENERAL;
949 // head->svFlags |= SVF_BROADCAST;// Broadcast to all clients
950 VectorSet( head->mins, -8, -8, -8 );
951 VectorSet( head->maxs, 8, 8, 8 );
952 head->contents = CONTENTS_BODY;
953 //FIXME: make an index into an external string table for localization
954 head->fullName = "Surgical Laser";
955 gi.linkentity(head);
956
957 //dmg
958 if ( !base->damage )
959 {
960 head->damage = 5;
961 }
962 else
963 {
964 head->damage = base->damage;
965 }
966 base->damage = 0;
967 //lifespan of beam
968 if ( !base->wait )
969 {
970 head->wait = 3000;
971 }
972 else
973 {
974 head->wait = base->wait * 1000;
975 }
976 base->wait = 0;
977
978 //Precache firing and explode sounds
979 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
980 G_SoundIndex("sound/chars/l_arm/fire.wav");
981 G_SoundIndex("sound/chars/l_arm/move.wav");
982
983 //Link them up
984 base->lastEnemy = arm;
985 arm->lastEnemy = head;
986 head->owner = arm;
987 arm->nextTrain = head->nextTrain = base;
988
989 // The head should always think, since it will be either firing a damage laser or just a target laser
990 head->e_ThinkFunc = thinkF_laser_arm_fire;
991 head->nextthink = level.time + FRAMETIME;
992 head->alt_fire = qfalse; // Don't do damage until told to
993 }
994
SP_laser_arm(gentity_t * base)995 void SP_laser_arm (gentity_t *base)
996 {
997 base->e_ThinkFunc = thinkF_laser_arm_start;
998 base->nextthink = level.time + START_TIME_LINK_ENTS;
999 }
1000
1001 //--------------------------
1002 // PERSONAL ASSAULT SENTRY
1003 //--------------------------
1004
1005 #define PAS_DAMAGE 2
1006
1007 //-----------------------------------------------------------------------------
pas_use(gentity_t * self,gentity_t * other,gentity_t * activator)1008 void pas_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1009 //-----------------------------------------------------------------------------
1010 {
1011 // Toggle on and off
1012 self->spawnflags = (self->spawnflags ^ 1);
1013
1014 if ( self->spawnflags & 1 )
1015 {
1016 self->nextthink = 0; // turn off and do nothing
1017 self->e_ThinkFunc = thinkF_NULL;
1018 }
1019 else
1020 {
1021 self->nextthink = level.time + 50;
1022 self->e_ThinkFunc = thinkF_pas_think;
1023 }
1024 }
1025
1026 //----------------------------------------------------------------
pas_fire(gentity_t * ent)1027 void pas_fire( gentity_t *ent )
1028 //----------------------------------------------------------------
1029 {
1030 vec3_t fwd, org;
1031 mdxaBone_t boltMatrix;
1032
1033 // Getting the flash bolt here
1034 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
1035 ent->torsoBolt,
1036 &boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time),
1037 NULL, ent->s.modelScale );
1038
1039 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
1040 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
1041
1042 G_PlayEffect( "turret/muzzle_flash", org, fwd );
1043
1044 gentity_t *bolt;
1045
1046 bolt = G_Spawn();
1047
1048 bolt->classname = "turret_proj";
1049 bolt->nextthink = level.time + 10000;
1050 bolt->e_ThinkFunc = thinkF_G_FreeEntity;
1051 bolt->s.eType = ET_MISSILE;
1052 bolt->s.weapon = WP_TURRET;
1053 bolt->owner = ent;
1054 bolt->damage = PAS_DAMAGE;
1055 bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
1056 bolt->splashDamage = 0;
1057 bolt->splashRadius = 0;
1058 bolt->methodOfDeath = MOD_ENERGY;
1059 bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1060
1061 VectorSet( bolt->maxs, 1, 1, 1 );
1062 VectorScale( bolt->maxs, -1, bolt->mins );
1063
1064 bolt->s.pos.trType = TR_LINEAR;
1065 bolt->s.pos.trTime = level.time; // move a bit on the very first frame
1066 VectorCopy( org, bolt->s.pos.trBase );
1067 VectorScale( fwd, 900, bolt->s.pos.trDelta );
1068 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
1069 VectorCopy( org, bolt->currentOrigin);
1070 }
1071
1072 //-----------------------------------------------------
pas_find_enemies(gentity_t * self)1073 static qboolean pas_find_enemies( gentity_t *self )
1074 //-----------------------------------------------------
1075 {
1076 qboolean found = qfalse;
1077 int count;
1078 float bestDist = self->radius * self->radius;
1079 float enemyDist;
1080 vec3_t enemyDir, org, org2;
1081 gentity_t *entity_list[MAX_GENTITIES], *target;
1082
1083 if ( self->aimDebounceTime > level.time ) // time since we've been shut off
1084 {
1085 // We were active and alert, i.e. had an enemy in the last 3 secs
1086 if ( self->painDebounceTime < level.time )
1087 {
1088 G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" ));
1089 self->painDebounceTime = level.time + 1000;
1090 }
1091 }
1092
1093 mdxaBone_t boltMatrix;
1094
1095 // Getting the "eye" here
1096 gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
1097 self->torsoBolt,
1098 &boltMatrix, self->currentAngles, self->s.origin, (cg.time?cg.time:level.time),
1099 NULL, self->s.modelScale );
1100
1101 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
1102
1103 count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
1104
1105 for ( int i = 0; i < count; i++ )
1106 {
1107 target = entity_list[i];
1108
1109 if ( !target->client )
1110 {
1111 continue;
1112 }
1113 if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
1114 {
1115 continue;
1116 }
1117 if ( target->client->playerTeam == self->noDamageTeam )
1118 {
1119 // A bot we don't want to shoot
1120 continue;
1121 }
1122 if ( !gi.inPVS( org2, target->currentOrigin ))
1123 {
1124 continue;
1125 }
1126
1127 if ( target->client )
1128 {
1129 VectorCopy( target->client->renderInfo.eyePoint, org );
1130 org[2] -= 15;
1131 }
1132 else
1133 {
1134 VectorCopy( target->currentOrigin, org );
1135 }
1136
1137 trace_t tr;
1138 gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
1139
1140 if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
1141 {
1142 // Only acquire if have a clear shot, Is it in range and closer than our best?
1143 VectorSubtract( target->currentOrigin, self->currentOrigin, enemyDir );
1144 enemyDist = VectorLengthSquared( enemyDir );
1145
1146 if ( target->s.number ) // don't do this for the player
1147 {
1148 G_StartFlee( target, self, self->currentOrigin, AEL_DANGER, 3000, 5000 );
1149 }
1150
1151 if ( enemyDist < bestDist )// all things equal, keep current
1152 {
1153 if ( self->attackDebounceTime + 2000 < level.time )
1154 {
1155 // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
1156 G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" ));
1157
1158 // Wind up turrets for a bit
1159 self->attackDebounceTime = level.time + 900 + Q_flrand(0.0f, 1.0f) * 200;
1160 }
1161
1162 G_SetEnemy( self, target );
1163 bestDist = enemyDist;
1164 found = qtrue;
1165 }
1166 }
1167 }
1168
1169 if ( found && VALIDSTRING( self->target2 ))
1170 {
1171 G_UseTargets2( self, self, self->target2 );
1172 }
1173
1174 return found;
1175 }
1176
1177 //---------------------------------
pas_adjust_enemy(gentity_t * ent)1178 void pas_adjust_enemy( gentity_t *ent )
1179 //---------------------------------
1180 {
1181 qboolean keep = qtrue;
1182
1183 if ( ent->enemy->health <= 0 )
1184 {
1185 keep = qfalse;
1186 }
1187 else// if ( Q_flrand(0.0f, 1.0f) > 0.5f )
1188 {
1189 // do a trace every now and then.
1190 mdxaBone_t boltMatrix;
1191 vec3_t org, org2;
1192
1193 // Getting the "eye" here
1194 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel,
1195 ent->torsoBolt,
1196 &boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time),
1197 NULL, ent->s.modelScale );
1198
1199 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
1200
1201 if ( ent->enemy->client )
1202 {
1203 VectorCopy( ent->enemy->client->renderInfo.eyePoint, org );
1204 org[2] -= 15;
1205 }
1206 else
1207 {
1208 VectorCopy( ent->enemy->currentOrigin, org );
1209 }
1210
1211 trace_t tr;
1212 gi.trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
1213
1214 if ( tr.allsolid || tr.startsolid || tr.entityNum != ent->enemy->s.number )
1215 {
1216 // trace failed
1217 keep = qfalse;
1218 }
1219 }
1220
1221 if ( keep )
1222 {
1223 ent->bounceCount = level.time + 500 + Q_flrand(0.0f, 1.0f) * 150;
1224 }
1225 else if ( ent->bounceCount < level.time ) // don't ping pong on and off
1226 {
1227 ent->enemy = NULL;
1228 // shut-down sound
1229 G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
1230
1231 // make turret play ping sound for 5 seconds
1232 ent->aimDebounceTime = level.time + 5000;
1233 }
1234 }
1235
1236 //---------------------------------
pas_think(gentity_t * ent)1237 void pas_think( gentity_t *ent )
1238 //---------------------------------
1239 {
1240 if ( !ent->damage )
1241 {
1242 // let us do our animation, then we are good to go in terms of pounding the crap out of enemies.
1243 ent->damage = 1;
1244 gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 11, BONE_ANIM_OVERRIDE_FREEZE, 0.8f, cg.time, -1, -1 );
1245 ent->nextthink = level.time + 1200;
1246 return;
1247 }
1248
1249 if ( !ent->count )
1250 {
1251 // turrets that have no ammo may as well do nothing
1252 return;
1253 }
1254
1255 ent->nextthink = level.time + FRAMETIME;
1256
1257 if ( ent->enemy )
1258 {
1259 // make sure that the enemy is still valid
1260 pas_adjust_enemy( ent );
1261 }
1262
1263 if ( !ent->enemy )
1264 {
1265 pas_find_enemies( ent );
1266 }
1267
1268 qboolean moved = qfalse;
1269 float diffYaw = 0.0f, diffPitch = 0.0f;
1270 vec3_t enemyDir, org;
1271 vec3_t frontAngles, backAngles;
1272 vec3_t desiredAngles;
1273
1274 ent->speed = AngleNormalize360( ent->speed );
1275 ent->random = AngleNormalize360( ent->random );
1276
1277 if ( ent->enemy )
1278 {
1279 // ...then we'll calculate what new aim adjustments we should attempt to make this frame
1280 // Aim at enemy
1281 if ( ent->enemy->client )
1282 {
1283 VectorCopy( ent->enemy->client->renderInfo.eyePoint, org );
1284 org[2] -= 40;
1285 }
1286 else
1287 {
1288 VectorCopy( ent->enemy->currentOrigin, org );
1289 }
1290
1291 VectorSubtract( org, ent->currentOrigin, enemyDir );
1292 vectoangles( enemyDir, desiredAngles );
1293
1294 diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] );
1295 diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] );
1296 }
1297 else
1298 {
1299 // no enemy, so make us slowly sweep back and forth as if searching for a new one
1300 diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f;
1301 }
1302
1303 if ( fabs(diffYaw) > 0.25f )
1304 {
1305 moved = qtrue;
1306
1307 if ( fabs(diffYaw) > 10.0f )
1308 {
1309 // cap max speed
1310 ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f;
1311 }
1312 else
1313 {
1314 // small enough
1315 ent->speed -= diffYaw;
1316 }
1317 }
1318
1319
1320 if ( fabs(diffPitch) > 0.25f )
1321 {
1322 moved = qtrue;
1323
1324 if ( fabs(diffPitch) > 4.0f )
1325 {
1326 // cap max speed
1327 ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f;
1328 }
1329 else
1330 {
1331 // small enough
1332 ent->random -= diffPitch;
1333 }
1334 }
1335
1336 // the bone axes are messed up, so hence some dumbness here
1337 VectorSet( frontAngles, -ent->random, 0.0f, 0.0f );
1338 VectorSet( backAngles, 0.0f, 0.0f, ent->speed - ent->s.angles[YAW] );
1339
1340 gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_barrel", frontAngles,
1341 BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time);
1342 gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_gback", frontAngles,
1343 BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time);
1344 gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_hinge", backAngles,
1345 BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL,100,cg.time);
1346
1347 if ( moved )
1348 {
1349 //ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
1350 }
1351 else
1352 {
1353 ent->s.loopSound = 0;
1354 }
1355
1356 if ( ent->enemy && ent->attackDebounceTime < level.time && Q_flrand(0.0f, 1.0f) > 0.3f )
1357 {
1358 ent->count--;
1359
1360 if ( ent->count )
1361 {
1362 pas_fire( ent );
1363 ent->fly_sound_debounce_time = level.time;//used as lastShotTime
1364 }
1365 else
1366 {
1367 ent->nextthink = 0;
1368 G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
1369 }
1370 }
1371 }
1372
1373 /*QUAKED misc_sentry_turret (1 0 0) (-16 -16 0) (16 16 24) START_OFF RESERVED
1374 personal assault sentry, like the ones you can carry in your inventory
1375
1376 RESERVED - do no use this flag for anything, does nothing..etc.
1377
1378 radius - How far away an enemy can be for it to pick it up (default 512)
1379 count - number of shots before thing deactivates. -1 = infinite, default 150
1380 health - How much damage it can take before exploding (default 50)
1381
1382 splashDamage - How much damage the explosion does
1383 splashRadius - The radius of the explosion
1384 NOTE: If either of the above two are 0, it will not make an explosion
1385
1386 target - What to use when destroyed
1387 target2 - What to use when it decides to fire at an enemy
1388
1389 team - team that does not take damage from this item
1390 "player",
1391 "enemy",
1392 "neutral"
1393
1394 */
1395
1396 //---------------------------------
SP_PAS(gentity_t * base)1397 void SP_PAS( gentity_t *base )
1398 //---------------------------------
1399 {
1400 base->classname = "PAS";
1401 G_SetOrigin( base, base->s.origin );
1402 G_SetAngles( base, base->s.angles );
1403
1404 base->speed = base->s.angles[YAW];
1405
1406 base->s.modelindex = G_ModelIndex( "models/items/psgun.glm" );
1407 base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/items/psgun.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
1408 base->s.radius = 30.0f;
1409 VectorSet( base->s.modelScale, 1.0f, 1.0f, 1.0f );
1410
1411 base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue );
1412 gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_hinge", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
1413 gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_gback", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
1414 gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_barrel", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 0, 0 );
1415
1416 base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
1417
1418 base->s.eType = ET_GENERAL;
1419
1420 if ( !base->radius )
1421 {
1422 base->radius = 512;
1423 }
1424
1425 if ( base->count == 0 )
1426 {
1427 // give ammo
1428 base->count = 150;
1429 }
1430
1431 base->e_UseFunc = useF_pas_use;
1432
1433 base->damage = 0; // start animation flag
1434
1435 base->contents = CONTENTS_SHOTCLIP|CONTENTS_CORPSE;//for certain traces
1436 VectorSet( base->mins, -8, -8, 0 );
1437 VectorSet( base->maxs, 8, 8, 18 );
1438
1439 if ( !(base->spawnflags & 1 )) // START_OFF
1440 {
1441 base->nextthink = level.time + 1000; // we aren't starting off, so start working right away
1442 base->e_ThinkFunc = thinkF_pas_think;
1443 }
1444
1445 // Set up our explosion effect for the ExplodeDeath code....
1446 base->fxID = G_EffectIndex( "turret/explode" );
1447 G_EffectIndex( "spark_exp_nosnd" );
1448
1449 if ( !base->health )
1450 {
1451 base->health = 50;
1452 }
1453 base->max_health = base->health;
1454
1455 base->takedamage = qtrue;
1456 base->e_PainFunc = painF_TurretPain;
1457 base->e_DieFunc = dieF_turret_die;
1458
1459 // hack this flag on so that when it calls the turret die code, it will orient the effect up
1460 // HACK
1461 //--------------------------------------
1462 base->spawnflags |= 2;
1463
1464 // Use this for our missile effect
1465 RegisterItem( FindItemForWeapon( WP_TURRET ));
1466 base->s.weapon = WP_TURRET;
1467
1468 base->svFlags |= SVF_NONNPC_ENEMY;
1469
1470 base->noDamageTeam = TEAM_NEUTRAL;
1471 if ( base->team && base->team[0] )
1472 {
1473 base->noDamageTeam = TranslateTeamName( base->team );
1474 base->team = NULL;
1475 }
1476
1477 gi.linkentity( base );
1478 }
1479
1480 //------------------------------------------------------------------------
place_portable_assault_sentry(gentity_t * self,vec3_t origin,vec3_t angs)1481 qboolean place_portable_assault_sentry( gentity_t *self, vec3_t origin, vec3_t angs )
1482 //------------------------------------------------------------------------
1483 {
1484 vec3_t fwd, pos;
1485 vec3_t mins, maxs;
1486 trace_t tr;
1487 gentity_t *pas;
1488
1489 VectorSet( maxs, 9, 9, 0 );
1490 VectorScale( maxs, -1, mins );
1491
1492 angs[PITCH] = 0;
1493 angs[ROLL] = 0;
1494 AngleVectors( angs, fwd, NULL, NULL );
1495
1496 // and move a consistent distance away from us so we don't have the dumb thing spawning inside of us.
1497 VectorMA( origin, 30, fwd, pos );
1498 gi.trace( &tr, origin, NULL, NULL, pos, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
1499
1500 // find the ground
1501 tr.endpos[2] += 20;
1502 VectorCopy( tr.endpos, pos );
1503 pos[2] -= 64;
1504
1505 gi.trace( &tr, tr.endpos, mins, maxs, pos, self->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 );
1506
1507 // check for a decent surface, meaning mostly flat...should probably also check surface parms so we don't set us down on lava or something.
1508 if ( !tr.startsolid && !tr.allsolid && tr.fraction < 1.0f && tr.plane.normal[2] > 0.9f && tr.entityNum >= ENTITYNUM_WORLD )
1509 {
1510 // Then spawn us if it seems cool.
1511 pas = G_Spawn();
1512
1513 if ( pas )
1514 {
1515 VectorCopy( tr.endpos, pas->s.origin );
1516 SP_PAS( pas );
1517
1518 pas->contents |= CONTENTS_PLAYERCLIP; // player placed ones can block players but not npcs
1519
1520 pas->e_UseFunc = useF_NULL; // placeable ones never need to be used
1521
1522 // we don't hurt us or anyone who belongs to the same team as us.
1523 if ( self->client )
1524 {
1525 pas->noDamageTeam = self->client->playerTeam;
1526 }
1527
1528 G_Sound( self, G_SoundIndex( "sound/player/use_sentry" ));
1529 pas->activator = self;
1530 return qtrue;
1531 }
1532 }
1533 return qfalse;
1534 }
1535
1536
1537 //-------------
1538 // ION CANNON
1539 //-------------
1540
1541
1542 //----------------------------------------
ion_cannon_think(gentity_t * self)1543 void ion_cannon_think( gentity_t *self )
1544 //----------------------------------------
1545 {
1546 if ( self->spawnflags & 2 )
1547 {
1548 if ( self->count )
1549 {
1550 // still have bursts left, so keep going
1551 self->count--;
1552 }
1553 else
1554 {
1555 // done with burst, so wait delay amount, plus a random bit
1556 self->nextthink = level.time + ( self->delay + Q_flrand(-1.0f, 1.0f) * self->random );
1557 self->count = Q_irand(0,5); // 0-5 bursts
1558
1559 // Not firing this time
1560 return;
1561 }
1562 }
1563
1564 if ( self->fxID )
1565 {
1566 vec3_t fwd, org;
1567 mdxaBone_t boltMatrix;
1568
1569 // Getting the flash bolt here
1570 gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
1571 self->torsoBolt,
1572 &boltMatrix, self->s.angles, self->s.origin, (cg.time?cg.time:level.time),
1573 NULL, self->s.modelScale );
1574
1575 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
1576 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
1577
1578 G_PlayEffect( self->fxID, org, fwd );
1579 }
1580
1581 if ( self->target2 )
1582 {
1583 // If we have a target2 fire it off in sync with our gun firing
1584 G_UseTargets2( self, self, self->target2 );
1585 }
1586
1587 gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 8, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 );
1588 self->nextthink = level.time + self->wait + Q_flrand(-1.0f, 1.0f) * self->random;
1589 }
1590
1591 //----------------------------------------------------------------------------------------------
ion_cannon_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)1592 void ion_cannon_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
1593 //----------------------------------------------------------------------------------------------
1594 {
1595 vec3_t org;
1596
1597 // dead, so nuke the ghoul model and put in the damage md3 version
1598 if ( self->playerModel >= 0 )
1599 {
1600 gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel );
1601 }
1602 self->s.modelindex = self->s.modelindex2;
1603 self->s.modelindex2 = 0;
1604
1605 // Turn off the thinking of the base & use it's targets
1606 self->e_ThinkFunc = thinkF_NULL;
1607 self->e_UseFunc = useF_NULL;
1608
1609 if ( self->target )
1610 {
1611 G_UseTargets( self, attacker );
1612 }
1613
1614 // clear my data
1615 self->e_DieFunc = dieF_NULL;
1616 self->takedamage = qfalse;
1617 self->health = 0;
1618
1619 self->takedamage = qfalse;//stop chain reaction runaway loops
1620 self->s.loopSound = 0;
1621
1622 // not solid anymore
1623 self->contents = 0;
1624
1625 VectorCopy( self->currentOrigin, self->s.pos.trBase );
1626
1627 VectorCopy( self->currentOrigin, org );
1628 org[2] += 20;
1629 G_PlayEffect( "env/ion_cannon_explosion", org );
1630
1631 if ( self->splashDamage > 0 && self->splashRadius > 0 )
1632 {
1633 G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius,
1634 attacker, MOD_UNKNOWN );
1635 }
1636
1637 gi.linkentity( self );
1638 }
1639
1640 //----------------------------------------------------------------------------
ion_cannon_use(gentity_t * self,gentity_t * other,gentity_t * activator)1641 void ion_cannon_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1642 //----------------------------------------------------------------------------
1643 {
1644 // toggle
1645 if ( self->e_ThinkFunc == thinkF_NULL )
1646 {
1647 // start thinking now
1648 self->e_ThinkFunc = thinkF_ion_cannon_think;
1649 self->nextthink = level.time + FRAMETIME; // fires right on being used
1650 }
1651 else
1652 {
1653 self->e_ThinkFunc = thinkF_NULL;
1654 }
1655 }
1656
1657 /*QUAKED misc_ion_cannon (1 0 0) (-140 -140 0) (140 140 320) START_OFF BURSTS SHIELDED
1658 Huge ion cannon, like the ones at the rebel base on Hoth.
1659
1660 START_OFF - Starts off
1661 BURSTS - adds more variation, shots come out in bursts
1662 SHIELDED - cannon is shielded, any kind of shot bounces off.
1663
1664 wait - How fast it shoots (default 1500 ms between shots, can't be less than 500 ms)
1665 random - milliseconds wait variation (default 400 ms...up to plus or minus .4 seconds)
1666 delay - Number of milliseconds between bursts (default 6000 ms, can't be less than 1000 ms, only works when BURSTS checked)
1667
1668 health - default 2000
1669 splashDamage - how much damage to do when it dies, must be greater than 0 to actually work
1670 splashRadius - damage radius, must be greater than 0 to actually work
1671
1672 targetname - Toggles it on/off
1673 target - What to use when destroyed
1674 target2 - What to use when it fires a shot.
1675 */
1676 //-----------------------------------------------------
SP_misc_ion_cannon(gentity_t * base)1677 void SP_misc_ion_cannon( gentity_t *base )
1678 //-----------------------------------------------------
1679 {
1680 G_SetAngles( base, base->s.angles );
1681
1682 G_SetOrigin(base, base->s.origin);
1683
1684 base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon.glm" );
1685 base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/ion_cannon.glm", base->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
1686 base->s.radius = 320.0f;
1687 VectorSet( base->s.modelScale, 1.0f, 1.0f, 1.0f );
1688
1689 base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue );
1690 base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" );
1691
1692 // register damage model
1693 base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon_damage.md3" );
1694
1695 base->e_UseFunc = useF_ion_cannon_use;
1696
1697 // How quickly to fire
1698 if ( base->wait == 0.0f )
1699 {
1700 base->wait = 1500.0f;
1701 }
1702 else if ( base->wait < 500.0f )
1703 {
1704 base->wait = 500.0f;
1705 }
1706
1707 if ( base->random == 0.0f )
1708 {
1709 base->random = 400.0f;
1710 }
1711
1712 if ( base->delay == 0 )
1713 {
1714 base->delay = 6000;
1715 }
1716 else if ( base->delay < 1000 )
1717 {
1718 base->delay = 1000;
1719 }
1720
1721 // we only take damage from a heavy weapon class missile
1722 base->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
1723
1724 if ( base->spawnflags & 4 )//shielded
1725 {
1726 base->flags |= FL_SHIELDED; //technically, this would only take damage from a lightsaber, but the other flag just above would cancel that out too.
1727 }
1728
1729 G_SpawnInt( "health", "2000", &base->health );
1730 base->e_DieFunc = dieF_ion_cannon_die;
1731 base->takedamage = qtrue;
1732
1733 // Start Off?
1734 if ( base->spawnflags & 1 )
1735 {
1736 base->e_ThinkFunc = thinkF_NULL;
1737 }
1738 else
1739 {
1740 // start thinking now, otherwise, we'll wait until we are used
1741 base->e_ThinkFunc = thinkF_ion_cannon_think;
1742 base->nextthink = level.time + base->wait + Q_flrand(-1.0f, 1.0f) * base->random;
1743 }
1744
1745 // Bursts?
1746 if ( base->spawnflags & 2 )
1747 {
1748 base->count = Q_irand(0,5); // 0-5 bursts
1749 }
1750
1751 // precache
1752 base->fxID = G_EffectIndex( "env/ion_cannon" );
1753
1754 // Set up our explosion effect for the ExplodeDeath code....
1755 G_EffectIndex( "env/ion_cannon_explosion" );
1756
1757 base->contents = CONTENTS_BODY;
1758
1759 VectorSet( base->mins, -141.0f, -148.0f, 0.0f );
1760 VectorSet( base->maxs, 142.0f, 135.0f, 245.0f );
1761
1762 gi.linkentity( base );
1763 }
1764
1765
1766 //-----------------------------------------------------
spotlight_use(gentity_t * self,gentity_t * other,gentity_t * activator)1767 void spotlight_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1768 {
1769 if ( self->e_ThinkFunc == thinkF_NULL )
1770 {
1771 // start thinking now, otherwise, we'll wait until we are used
1772 self->e_ThinkFunc = thinkF_spotlight_think;
1773 self->nextthink = level.time + FRAMETIME;
1774 }
1775 else
1776 {
1777 self->e_ThinkFunc = thinkF_NULL;
1778 self->s.eFlags &= ~EF_ALT_FIRING;
1779 }
1780 }
1781
1782 //-----------------------------------------------------
spotlight_think(gentity_t * ent)1783 void spotlight_think( gentity_t *ent )
1784 {
1785 vec3_t dir, end;
1786 trace_t tr;
1787
1788 // dumb hack flag so that we can draw an interpolated light cone cgame side.
1789 ent->s.eFlags |= EF_ALT_FIRING;
1790
1791 VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, dir );
1792 VectorNormalize( dir );
1793 vectoangles( dir, ent->s.apos.trBase );
1794 ent->s.apos.trType = TR_INTERPOLATE;
1795
1796 VectorMA( ent->currentOrigin, 2048, dir, end ); // just pick some max trace distance
1797 gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, CONTENTS_SOLID, G2_NOCOLLIDE, 0 );
1798
1799 ent->radius = tr.fraction * 2048.0f;
1800
1801 if ( tr.fraction < 1 )
1802 {
1803 if ( DistanceSquared( tr.endpos, g_entities[0].currentOrigin ) < 140 * 140 )
1804 {
1805 // hit player--use target2
1806 G_UseTargets2( ent, &g_entities[0], ent->target2 );
1807
1808 #ifndef FINAL_BUILD
1809 if ( g_developer->integer == PRINT_DEVELOPER )
1810 {
1811 Com_Printf( S_COLOR_MAGENTA "Spotlight hit player at time: %d!!!\n", level.time );
1812 }
1813 #endif
1814 }
1815 }
1816
1817 ent->nextthink = level.time + 50;
1818 }
1819
1820 //-----------------------------------------------------
spotlight_link(gentity_t * ent)1821 void spotlight_link( gentity_t *ent )
1822 {
1823 gentity_t *target = 0;
1824
1825 target = G_Find( target, FOFS(targetname), ent->target );
1826
1827 if ( !target )
1828 {
1829 Com_Printf( S_COLOR_RED "ERROR: spotlight_link: bogus target %s\n", ent->target );
1830 G_FreeEntity( ent );
1831 return;
1832 }
1833
1834 ent->enemy = target;
1835
1836 // Start Off?
1837 if ( ent->spawnflags & 1 )
1838 {
1839 ent->e_ThinkFunc = thinkF_NULL;
1840 ent->s.eFlags &= ~EF_ALT_FIRING;
1841 }
1842 else
1843 {
1844 // start thinking now, otherwise, we'll wait until we are used
1845 ent->e_ThinkFunc = thinkF_spotlight_think;
1846 ent->nextthink = level.time + FRAMETIME;
1847 }
1848 }
1849
1850 /*QUAKED misc_spotlight (1 0 0) (-10 -10 0) (10 10 10) START_OFF
1851 #MODELNAME="models/map_objects/imp_mine/spotlight.md3"
1852 Search spotlight that must be targeted at a func_train or other entity
1853 Uses its target2 when it detects the player
1854
1855 START_OFF - Starts off
1856
1857 targetname - Toggles it on/off
1858 target - What to point at
1859 target2 - What to use when detects player
1860 */
1861
1862 //-----------------------------------------------------
SP_misc_spotlight(gentity_t * base)1863 void SP_misc_spotlight( gentity_t *base )
1864 //-----------------------------------------------------
1865 {
1866 if ( !base->target )
1867 {
1868 Com_Printf( S_COLOR_RED "ERROR: misc_spotlight must have a target\n" );
1869 G_FreeEntity( base );
1870 return;
1871 }
1872
1873 G_SetAngles( base, base->s.angles );
1874 G_SetOrigin( base, base->s.origin );
1875
1876 base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/spotlight.md3" );
1877
1878 G_SpawnInt( "health", "300", &base->health );
1879
1880 // Set up our lightcone effect, though we will have it draw cgame side so it looks better
1881 G_EffectIndex( "env/light_cone" );
1882
1883 base->contents = CONTENTS_BODY;
1884 base->e_UseFunc = useF_spotlight_use;
1885
1886 // the thing we need to target may not have spawned yet, so try back in a bit
1887 base->e_ThinkFunc = thinkF_spotlight_link;
1888 base->nextthink = level.time + 100;
1889
1890 gi.linkentity( base );
1891 }
1892
1893 /*QUAKED misc_panel_turret (0 0 1) (-8 -8 -12) (8 8 16) HEALTH
1894 Creates a turret that, when the player uses a panel, takes control of this turret and adopts the turret view
1895
1896 HEALTH - gun turret has health and displays a hud with its current health
1897
1898 "target" - thing to use when player enters the turret view
1899 "target2" - thing to use when player leaves the turret view
1900 "target3" - thing to use when it dies.
1901
1902 radius - the max yaw range in degrees, (default 90) which means you can move 90 degrees on either side of the start angles.
1903 random - the max pitch range in degrees, (default 60) which means you can move 60 degrees above or below the start angles.
1904 delay - time between shots, in milliseconds (default 200).
1905 damage - amount of damage shots do, (default 50).
1906 speed - missile speed, (default 3000)
1907
1908 heatlh - how much heatlh the thing has, (default 200) only works if HEALTH is checked, otherwise it can't be destroyed.
1909 */
1910
1911 extern gentity_t *player;
1912 extern qboolean G_ClearViewEntity( gentity_t *ent );
1913 extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
1914 extern gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
1915
panel_turret_shoot(gentity_t * self,vec3_t org,vec3_t dir)1916 void panel_turret_shoot( gentity_t *self, vec3_t org, vec3_t dir)
1917 {
1918 gentity_t *missile = CreateMissile( org, dir, self->speed, 10000, self );
1919
1920 missile->classname = "b_proj";
1921 missile->s.weapon = WP_EMPLACED_GUN;
1922
1923 VectorSet( missile->maxs, 7, 7, 7 );
1924 VectorScale( missile->maxs, -1, missile->mins );
1925
1926 missile->bounceCount = 0;
1927
1928 missile->damage = self->damage;
1929 missile->dflags = DAMAGE_DEATH_KNOCKBACK;
1930 missile->methodOfDeath = MOD_ENERGY;
1931 missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
1932
1933 G_SoundOnEnt( self, CHAN_AUTO, "sound/movers/objects/ladygun_fire" );
1934
1935 VectorMA( org, 32, dir, org );
1936 org[2] -= 5;
1937 G_PlayEffect( "emplaced/muzzle_flash", org, dir );
1938 }
1939
1940 //-----------------------------------------
misc_panel_turret_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)1941 void misc_panel_turret_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
1942 {
1943 if ( self->target3 )
1944 {
1945 G_UseTargets2( self, player, self->target3 );
1946 }
1947
1948 // FIXME: might need some other kind of logic or functionality in here??
1949 G_UseTargets2( self, player, self->target2 );
1950 G_ClearViewEntity( player );
1951 cg.overrides.active &= ~CG_OVERRIDE_FOV;
1952 cg.overrides.fov = 0;
1953 }
1954
1955 //-----------------------------------------
panel_turret_think(gentity_t * self)1956 void panel_turret_think( gentity_t *self )
1957 {
1958 // Ensure that I am the viewEntity
1959 if ( player && player->client && player->client->ps.viewEntity == self->s.number )
1960 {
1961 usercmd_t *ucmd = &player->client->usercmd;
1962
1963 // We are the viewEnt so update our new viewangles based on the sum of our start angles and the ucmd angles
1964 for ( int i = 0; i < 3; i++ )
1965 {
1966 // convert our base angle to a short, add with the usercmd.angle ( a short ), then switch use back to a real angle
1967 self->s.apos.trBase[i] = AngleNormalize180( SHORT2ANGLE( ucmd->angles[i] + ANGLE2SHORT( self->s.angles[i] ) + self->pos3[i] ));
1968 }
1969
1970 // Only clamp if we have a PITCH clamp
1971 if ( self->random != 0.0f )
1972 // Angle clamping -- PITCH
1973 {
1974 if ( self->s.apos.trBase[PITCH] > self->random ) // random is PITCH
1975 {
1976 self->pos3[PITCH] += ANGLE2SHORT( AngleNormalize180( self->random - self->s.apos.trBase[PITCH]));
1977 self->s.apos.trBase[PITCH] = self->random;
1978 }
1979 else if ( self->s.apos.trBase[PITCH] < -self->random )
1980 {
1981 self->pos3[PITCH] -= ANGLE2SHORT( AngleNormalize180( self->random + self->s.apos.trBase[PITCH]));
1982 self->s.apos.trBase[PITCH] = -self->random;
1983 }
1984 }
1985
1986 // Only clamp if we have a YAW clamp
1987 if ( self->radius != 0.0f )
1988 {
1989 float yawDif = AngleSubtract( self->s.apos.trBase[YAW], self->s.angles[YAW] );
1990
1991 // Angle clamping -- YAW
1992 if ( yawDif > self->radius ) // radius is YAW
1993 {
1994 self->pos3[YAW] += ANGLE2SHORT( self->radius - yawDif );
1995 self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] + self->radius );
1996 }
1997 else if ( yawDif < -self->radius ) // radius is YAW
1998 {
1999 self->pos3[YAW] -= ANGLE2SHORT( self->radius + yawDif );
2000 self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] - self->radius );
2001 }
2002 }
2003
2004 // Let cgame interpolation smooth out the angle changes
2005 self->s.apos.trType = TR_INTERPOLATE;
2006 self->s.pos.trType = TR_INTERPOLATE; // not really moving, but this fixes an interpolation bug in cg_ents.
2007
2008 // Check for backing out of turret
2009 if ( ( self->useDebounceTime < level.time ) && ((ucmd->buttons & BUTTON_BLOCKING) || (ucmd->buttons & BUTTON_USE) || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove) )
2010 {
2011 self->useDebounceTime = level.time + 200;
2012
2013 G_UseTargets2( self, player, self->target2 );
2014 G_ClearViewEntity( player );
2015 G_Sound( player, self->soundPos2 );
2016
2017 cg.overrides.active &= ~CG_OVERRIDE_FOV;
2018 cg.overrides.fov = 0;
2019 if ( ucmd->upmove > 0 )
2020 {//stop player from doing anything for a half second after
2021 player->aimDebounceTime = level.time + 500;
2022 }
2023
2024 // can be drawn
2025 // self->s.eFlags &= ~EF_NODRAW;
2026 }
2027 else
2028 {
2029 // don't draw me when being looked through
2030 // self->s.eFlags |= EF_NODRAW;
2031 // self->s.modelindex = 0;
2032
2033 // we only need to think when we are being used
2034 self->nextthink = level.time + 50;
2035
2036 cg.overrides.active |= CG_OVERRIDE_FOV;
2037 cg.overrides.fov = 50;
2038 }
2039
2040 if ( ucmd->buttons & BUTTON_ATTACK || ucmd->buttons & BUTTON_ALT_ATTACK )
2041 {
2042 if ( self->attackDebounceTime < level.time )
2043 {
2044 vec3_t dir, pt;
2045
2046 AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
2047
2048 VectorCopy( self->currentOrigin, pt );
2049 pt[2] -= 4;
2050 panel_turret_shoot( self, pt, dir );
2051
2052 self->attackDebounceTime = level.time + self->delay;
2053 }
2054 }
2055 }
2056 }
2057
2058 //-----------------------------------------
panel_turret_use(gentity_t * self,gentity_t * other,gentity_t * activator)2059 void panel_turret_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2060 {
2061 // really only usable by the player
2062 if ( !activator || !activator->client || activator->s.number )
2063 {
2064 return;
2065 }
2066
2067 if ( self->useDebounceTime > level.time )
2068 {
2069 // can't use it again right away.
2070 return;
2071 }
2072
2073 if ( self->spawnflags & 1 ) // health...presumably the lady luck gun
2074 {
2075 G_Sound( self, G_SoundIndex( "sound/movers/objects/ladygun_on" ));
2076 }
2077
2078 self->useDebounceTime = level.time + 200;
2079
2080 // Compensating for the difference between the players view at the time of use and the start angles that the gun object has
2081 self->pos3[PITCH] = -activator->client->usercmd.angles[PITCH];
2082 self->pos3[YAW] = -activator->client->usercmd.angles[YAW];
2083 self->pos3[ROLL] = 0;
2084
2085 // set me as view entity
2086 G_UseTargets2( self, activator, self->target );
2087 G_SetViewEntity( activator, self );
2088
2089 G_Sound( activator, self->soundPos1 );
2090
2091 self->e_ThinkFunc = thinkF_panel_turret_think;
2092 // panel_turret_think( self );
2093 self->nextthink = level.time + 150;
2094 }
2095
2096 //-----------------------------------------
SP_misc_panel_turret(gentity_t * self)2097 void SP_misc_panel_turret( gentity_t *self )
2098 {
2099 G_SpawnFloat( "radius", "90", &self->radius ); // yaw
2100 G_SpawnFloat( "random", "60", &self->random ); // pitch
2101 G_SpawnFloat( "speed" , "3000", &self->speed );
2102 G_SpawnInt( "delay", "200", &self->delay );
2103 G_SpawnInt( "damage", "50", &self->damage );
2104
2105 VectorSet( self->pos3, 0.0f, 0.0f, 0.0f );
2106
2107 if ( self->spawnflags & 1 ) // heatlh
2108 {
2109 self->takedamage = qtrue;
2110 self->contents = CONTENTS_SHOTCLIP;
2111 G_SpawnInt( "health", "200", &self->health );
2112
2113 self->max_health = self->health;
2114 self->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud
2115 G_SoundIndex( "sound/movers/objects/ladygun_on" );
2116 }
2117
2118 self->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ladyluck_gun.md3" );
2119
2120 self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" );
2121 self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" );
2122
2123 G_SoundIndex( "sound/movers/objects/ladygun_fire" );
2124
2125 G_SetOrigin( self, self->s.origin );
2126 G_SetAngles( self, self->s.angles );
2127
2128 VectorSet( self->mins, -8, -8, -12 );
2129 VectorSet( self->maxs, 8, 8, 0 );
2130 self->contents = CONTENTS_SOLID;
2131
2132 self->s.weapon = WP_TURRET;
2133
2134 RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN ));
2135 gi.linkentity( self );
2136
2137 self->e_UseFunc = useF_panel_turret_use;
2138 self->e_DieFunc = dieF_misc_panel_turret_die;
2139 }