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