1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 /*
30 * name: g_misc.c
31 *
32 * desc:
33 *
34 */
35
36
37 #include "g_local.h"
38
39 extern void AimAtTarget( gentity_t * self );
40
41 int sniper_sound;
42 int snd_noammo;
43
44 /*QUAKED func_group (0 0 0) ?
45 Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
46 */
47
48
49 /*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
50 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
51 */
SP_info_camp(gentity_t * self)52 void SP_info_camp( gentity_t *self ) {
53 G_SetOrigin( self, self->s.origin );
54 }
55
56
57 /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
58 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
59 */
SP_info_null(gentity_t * self)60 void SP_info_null( gentity_t *self ) {
61 G_FreeEntity( self );
62 }
63
64
65 /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
66 Used as a positional target for in-game calculation, like jumppad targets.
67 target_position does the same thing
68 */
SP_info_notnull(gentity_t * self)69 void SP_info_notnull( gentity_t *self ) {
70 G_SetOrigin( self, self->s.origin );
71 }
72
73
74 /*QUAKED info_notnull_big (1 0 0) (-16 -16 -24) (16 16 32)
75 info_notnull with a bigger box for ease of positioning
76 */
SP_info_notnull_big(gentity_t * self)77 void SP_info_notnull_big( gentity_t *self ) {
78 G_SetOrigin( self, self->s.origin );
79 }
80
81
82
83 /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point q3map_non-dynamic
84 Non-displayed light.
85 "light" overrides the default 300 intensity.
86 Nonlinear checkbox gives inverse square falloff instead of linear
87 Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
88 Lights pointed at a target will be spotlights.
89 "radius" overrides the default 64 unit radius of a spotlight at the target point.
90 "fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
91 "q3map_non-dynamic" specifies that this light should not contribute to the world's 'light grid' and therefore will not light dynamic models in the game.(wolf)
92 */
SP_light(gentity_t * self)93 void SP_light( gentity_t *self ) {
94 G_FreeEntity( self );
95 }
96
97 /*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
98 Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
99 "light" overrides the default 300 intensity.
100 Nonlinear checkbox gives inverse square falloff instead of linear
101 Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
102 Lights pointed at a target will be spotlights.
103 "radius" overrides the default 64 unit radius of a spotlight at the target point.
104 "fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
105 */
SP_lightJunior(gentity_t * self)106 void SP_lightJunior( gentity_t *self ) {
107 G_FreeEntity( self );
108 }
109
110
111
112 /*
113 =================================================================================
114
115 TELEPORTERS
116
117 =================================================================================
118 */
TeleportPlayer(gentity_t * player,vec3_t origin,vec3_t angles)119 void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) {
120 gentity_t *tent;
121 qboolean noAngles;
122
123 noAngles = (angles[0] > 999999.0);
124 // use temp events at source and destination to prevent the effect
125 // from getting dropped by a second player event
126 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
127 tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
128 tent->s.clientNum = player->s.clientNum;
129
130 tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
131 tent->s.clientNum = player->s.clientNum;
132 }
133
134 // unlink to make sure it can't possibly interfere with G_KillBox
135 trap_UnlinkEntity( player );
136
137 VectorCopy( origin, player->client->ps.origin );
138 player->client->ps.origin[2] += 1;
139
140 if (!noAngles) {
141 // spit the player out
142 // AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
143 // VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
144 // player->client->ps.pm_time = 160; // hold time
145 // player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
146
147 // set angles
148 SetClientViewAngle(player, angles);
149 }
150 // toggle the teleport bit so the client knows to not lerp
151 player->client->ps.eFlags ^= EF_TELEPORT_BIT;
152
153 // kill anything at the destination
154 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
155 G_KillBox( player );
156 }
157
158 // save results of pmove
159 BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
160
161 // use the precise origin for linking
162 VectorCopy( player->client->ps.origin, player->r.currentOrigin );
163
164 if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
165 trap_LinkEntity( player );
166 }
167 }
168
169
170 /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
171 Point teleporters at these.
172 Now that we don't have teleport destination pads, this is just
173 an info_notnull
174 */
SP_misc_teleporter_dest(gentity_t * ent)175 void SP_misc_teleporter_dest( gentity_t *ent ) {
176 }
177
178
179 /*
180 =================================================================================
181
182 misc_grabber_trap
183
184 */
185
186
187 static int attackDurations[] = { ( 11 * 1000 ) / 15,
188 ( 16 * 1000 ) / 15,
189 ( 16 * 1000 ) / 15 };
190
191 static int attackHittimes[] = { ( 7 * 1000 ) / 15,
192 ( 6 * 1000 ) / 15,
193 ( 7 * 1000 ) / 15 };
194
195 /*
196 ==============
197 grabber_think_idle
198 think func for the grabber ent to reset to idle if not attacking
199 ==============
200 */
grabber_think_idle(gentity_t * ent)201 void grabber_think_idle( gentity_t *ent ) {
202 if ( ent->s.frame > 1 ) { // non-idle status
203 ent->s.frame = rand() % 2;
204 }
205 }
206
207 /*
208 ==============
209 grabber_think_hit
210 think func for grabber ent following an attack command
211 ==============
212 */
grabber_think_hit(gentity_t * ent)213 void grabber_think_hit( gentity_t *ent ) {
214 G_RadiusDamage( ent->s.pos.trBase, ent, ent->damage, ent->duration, ent, MOD_GRABBER );
215 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); // sound2to1 is the 'pain' sound
216
217 ent->nextthink = level.time + ( attackDurations[( ent->s.frame ) - 2] - attackHittimes[( ent->s.frame ) - 2] );
218 ent->think = grabber_think_idle;
219 }
220
221
222 /*
223 ==============
224 grabber_die
225 ==============
226 */
227 extern void GibEntity( gentity_t * self, int killer ) ;
228
grabber_die(gentity_t * ent,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)229 void grabber_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {
230
231 // FIXME FIXME
232 // this is buggy. the trigger brush entity (ent->enemy) does not free.
233 // need to fix.
234
235 GibEntity( ent, 0 ); // use temporarily to show 'death' of entity
236
237 // trap_UnlinkEntity(ent->enemy);
238 ent->enemy->think = G_FreeEntity;
239 ent->enemy->nextthink = level.time + FRAMETIME;
240 // G_FreeEntity(ent->enemy);
241
242 G_UseTargets( ent, attacker );
243
244 // trap_UnlinkEntity(ent);
245 ent->think = G_FreeEntity;
246 ent->nextthink = level.time + FRAMETIME;
247 // G_FreeEntity(ent);
248 }
249
250
251
252
253 /*
254 ==============
255 grabber_attack
256 direct call to the grabber entity (not a trigger) to call the attack
257 ==============
258 */
grabber_attack(gentity_t * ent)259 void grabber_attack( gentity_t *ent ) {
260 ent->s.frame = ( rand() % 3 ) + 2; // randomly choose an attack sequence
261
262 ent->nextthink = level.time + attackHittimes[( ent->s.frame ) - 2];
263 ent->think = grabber_think_hit;
264 }
265
266 /*
267 ==============
268 grabber_close
269 touch func for attack distance trigger entity
270 ==============
271 */
grabber_close(gentity_t * ent,gentity_t * other,trace_t * trace)272 void grabber_close( gentity_t *ent, gentity_t *other, trace_t *trace ) {
273 if ( ent->parent->nextthink > level.time ) {
274 return;
275 }
276
277 grabber_attack( ent->parent );
278 }
279
280
281
282 /*
283 ==============
284 grabber_pain
285 pain func for the grabber entity (not triggers)
286 ==============
287 */
grabber_pain(gentity_t * ent,gentity_t * attacker,int damage,vec3_t point)288 void grabber_pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) {
289 G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); // sound2to1 is the 'pain' sound
290 }
291
292
293 /*
294 ==============
295 grabber_wake
296 ent calling this is the bounding box for the grabber, not the grabber ent itself.
297 the grabber ent is 'ent->parent'
298 ==============
299 */
grabber_wake(gentity_t * ent)300 void grabber_wake( gentity_t *ent ) {
301 gentity_t *parent;
302
303 parent = ent->parent;
304
305 // change the 'a' trigger to the 'b' trigger for grabber attacking
306 VectorCopy( parent->s.origin, ent->r.mins );
307 VectorCopy( parent->s.origin, ent->r.maxs );
308
309 if ( 1 ) { // temp fast trigger
310 VectorAdd( ent->r.mins, tv( -( ent->random ), -( ent->random ), -( ent->random ) ), ent->r.mins );
311 VectorAdd( ent->r.maxs, tv( ent->random, ent->random, ent->random ), ent->r.maxs );
312 }
313
314 ent->touch = grabber_close;
315
316 // parent entity: show model/play anim/take damage
317 {
318 parent->clipmask = CONTENTS_SOLID;
319 parent->r.contents = CONTENTS_SOLID;
320 parent->takedamage = qtrue;
321 parent->active = qtrue;
322 parent->die = grabber_die;
323 parent->pain = grabber_pain;
324 trap_LinkEntity( parent );
325
326 ent->s.frame = 5; // starting position
327
328 // go back to an idle if not attacking immediately
329 parent->nextthink = level.time + FRAMETIME;
330 parent->think = grabber_think_idle;
331 }
332
333 G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); // soundPos1 is the 'wake' sound
334 }
335
336
337 /*
338 ==============
339 grabber_use
340 use func for the grabber entity
341 if not awake, allow waking by trigger
342 if awake, allow attacking by trigger
343 ==============
344 */
grabber_use(gentity_t * ent,gentity_t * other,gentity_t * activator)345 void grabber_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
346 G_Printf( "grabber_use: %d\n", level.time );
347
348 if ( !ent->active ) {
349 grabber_wake( ent );
350 } else {
351 grabber_attack( ent );
352 }
353 }
354
355 /*
356 ==============
357 grabber_wake_touch
358 touch func for the first 'wake' trigger entity
359 ==============
360 */
grabber_wake_touch(gentity_t * ent,gentity_t * other,trace_t * trace)361 void grabber_wake_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) {
362 grabber_wake( ent );
363 }
364
365
366 /*QUAKED misc_grabber_trap (1 0 0) (-8 -8 -8) (8 8 8)
367 fields:
368 "adist" - radius of 'wakeup' box. player passing closer than distance activates grabber (def: 64)
369 "bdist" - radius of 'attack' box. player passing into this gets a swipe. (def: 32)
370 "health" - how much damage grabber can take after 'wakeup' (def: 100)
371 "range" - when attacking, how far from the origin the grabber can strike (def: 64)
372 "dmg" - max damage to give on a successful strike (def: 10)
373 "wait" - how long to wait between strikes if the player stays within the 'attack' box (def: see below)
374
375 If you do not set a "wait" value, then it will default to the duration of the animations. (so since the first attack animation is 11 frames long and plays at 15 fps, the default wait after using attack 1 would be 11/15, or 0.73 seconds)
376
377 grabber media:
378 model - "models/misc/grabber/grabber.md3"
379 wake sound - "models/misc/grabber/grabber_wake.wav"
380 attack sound - "models/misc/grabber/grabber_attack.wav"
381 pain sound - "models/misc/grabber/grabber_pain.wav"
382
383 The current frames are:
384 first frame
385 | length
386 | looping frames
387 | fps
388 | damage at frame
389 |
390 0 6 6 5 0 (main idle)
391 5 21 21 7 0 (random idle)
392 25 11 10 15 7 (attack big swipe)
393 35 16 0 15 6 (attack small swipe)
394 50 16 0 15 7 (attack grab)
395 66 1 1 15 0 (starting position)
396
397 */
SP_misc_grabber_trap(gentity_t * ent)398 void SP_misc_grabber_trap( gentity_t *ent ) {
399 int adist, bdist, range;
400 gentity_t *trig;
401
402 // TODO: change from 'trap' to something else. 'trap' is a misnomer. it's actually used for other stuff too
403 ent->s.eType = ET_TRAP;
404
405 // TODO: make these user assignable?
406 ent->s.modelindex = G_ModelIndex( "models/misc/grabber/grabber.md3" );
407 ent->soundPos1 = G_SoundIndex( "models/misc/grabber/grabber_wake.wav" );
408 ent->sound1to2 = G_SoundIndex( "models/misc/grabber/grabber_attack.wav" );
409 ent->sound2to1 = G_SoundIndex( "models/misc/grabber/grabber_pain.wav" );
410
411 G_SetOrigin( ent, ent->s.origin );
412 VectorCopy( ent->s.angles, ent->s.apos.trBase );
413 ent->s.apos.trBase[YAW] -= 90; // adjust for model rotation
414
415
416 if ( !ent->health ) {
417 ent->health = 100; // default to 100
418
419 }
420 if ( !ent->damage ) {
421 ent->damage = 10; // default to 10
422
423 }
424 ent->s.frame = 5;
425
426 ent->use = grabber_use; // allow 'waking' from trigger
427
428 VectorSet( ent->r.mins, -12, -12, 0 ); // target area for shooting it after it wakes
429 VectorSet( ent->r.maxs, 12, 12, 48 );
430
431 // create the 'a' trigger for waking up the grabber
432 trig = ent->enemy = G_Spawn();
433
434 VectorCopy( ent->s.origin, trig->r.mins );
435 VectorCopy( ent->s.origin, trig->r.maxs );
436
437 // store attack range in 'duration'
438 G_SpawnInt( "range", "64", &range );
439 ent->duration = range;
440
441 // store adist/bdist in 'count/random' of the trigger brush ent
442 G_SpawnInt( "adist", "64", &adist );
443 trig->count = adist;
444 G_SpawnInt( "bdist", "32", &bdist );
445 trig->random = bdist;
446
447 // just make an even trigger box around the ent (do properly sized/oriented trigger after it's working)
448 if ( 1 ) { // temp fast trigger
449 VectorAdd( trig->r.mins, tv( -( trig->count ), -( trig->count ), -( trig->count ) ), trig->r.mins );
450 VectorAdd( trig->r.maxs, tv( trig->count, trig->count, trig->count ), trig->r.maxs );
451 }
452
453 trig->parent = ent;
454 trig->r.contents = CONTENTS_TRIGGER;
455 trig->r.svFlags = SVF_NOCLIENT;
456 trig->touch = grabber_wake_touch;
457 trap_LinkEntity( trig );
458
459 }
460
use_spotlight(gentity_t * ent,gentity_t * other,gentity_t * activator)461 void use_spotlight( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
462 gentity_t *tent;
463
464 if ( ent->r.linked ) {
465 trap_UnlinkEntity( ent );
466 } else
467 {
468 tent = G_PickTarget( ent->target );
469 VectorCopy( tent->s.origin, ent->s.origin2 );
470
471 ent->active = 0;
472 trap_LinkEntity( ent );
473 }
474 }
475
476
spotlight_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)477 void spotlight_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {
478
479 AICast_AudibleEvent( attacker->s.number, self->r.currentOrigin, 1024 ); // loud audible event
480
481 self->s.time2 = level.time;
482 self->s.frame = 1; // 1 == dead
483 self->takedamage = 0;
484
485 // G_AddEvent( self, EV_ENTDEATH, 0 );
486 }
487
spotlight_finish_spawning(gentity_t * ent)488 void spotlight_finish_spawning( gentity_t *ent ) {
489 if ( ent->spawnflags & 1 ) { // START_ON
490 // ent->active = 0;
491 trap_LinkEntity( ent );
492 }
493
494 ent->use = use_spotlight;
495 ent->die = spotlight_die;
496 if ( !ent->health ) {
497 ent->health = 1;
498 }
499 ent->takedamage = 1;
500 ent->think = 0;
501 ent->nextthink = 0;
502 ent->s.frame = 0;
503
504 ent->clipmask = CONTENTS_SOLID;
505 ent->r.contents = CONTENTS_SOLID;
506
507 VectorSet( ent->r.mins, -10, -10, -10 );
508 VectorSet( ent->r.maxs, 10, 10, 10 );
509 }
510
511
512 //----(SA) added
513 /*QUAKED misc_spotlight (1 0 0) (-16 -16 -16) (16 16 16) START_ON BACK_AND_FORTH
514 "target" - .camera (spline) file for light to track. do not specify file extension.
515
516 BACK_AND_FORTH - when end of target spline is hit, reverse direction rather than looping (looping is default)
517 ( /\ not active yet /\ )
518 */
519 //"model" - 'base' model that moves with the light. Default: "models/mapobjects/light/searchlight_pivot.md3"
SP_misc_spotlight(gentity_t * ent)520 void SP_misc_spotlight( gentity_t *ent ) {
521
522 ent->s.eType = ET_SPOTLIGHT_EF;
523
524 ent->think = spotlight_finish_spawning;
525 ent->nextthink = level.time + 100;
526
527 //----(SA) model now tracked by client
528
529 // if ( ent->model )
530 // ent->s.modelindex = G_ModelIndex( ent->model );
531 // else
532 // ent->s.modelindex = G_ModelIndex("models/mapobjects/light/searchlight_pivot.md3");
533
534 if ( ent->target ) {
535 ent->s.density = G_FindConfigstringIndex( ent->target, CS_SPLINES, MAX_SPLINE_CONFIGSTRINGS, qtrue );
536 }
537
538 }
539
540 //----(SA) end
541
542 //===========================================================
543
544 /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
545 "model" arbitrary .md3 file to display
546 "modelscale" scale multiplier (defaults to 1x)
547 "modelscale_vec" scale multiplier (defaults to 1 1 1, scales each axis as requested)
548
549 "modelscale_vec" - Set scale per-axis. Overrides "modelscale", so if you have both, the "modelscale" is ignored
550 */
SP_misc_model(gentity_t * ent)551 void SP_misc_model( gentity_t *ent ) {
552 G_FreeEntity( ent );
553 }
554
555
556 //----(SA)
557 /*QUAKED misc_gamemodel (1 0 0) (-16 -16 -16) (16 16 16) ORIENT_LOD
558 md3 placed in the game at runtime (rather than in the bsp)
559 "model" arbitrary .md3 file to display
560 "modelscale" scale multiplier (defaults to 1x, and scales uniformly)
561 "modelscale_vec" scale multiplier (defaults to 1 1 1, scales each axis as requested)
562 "trunk" diameter of solid core (used for trace visibility and collision (not ai pathing))
563 "trunkheight" height of trunk
564 ORIENT_LOD - if flagged, the entity will yaw towards the player when the LOD switches
565
566 "modelscale_vec" - Set scale per-axis. Overrides "modelscale", so if you have both, the "modelscale" is ignored
567
568 */
SP_misc_gamemodel(gentity_t * ent)569 void SP_misc_gamemodel( gentity_t *ent ) {
570
571 float scale[3] = {1,1,1};
572 vec3_t scalevec;
573 int trunksize, trunkheight;
574
575 ent->s.eType = ET_GAMEMODEL;
576 ent->s.modelindex = G_ModelIndex( ent->model );
577
578 // look for general scaling
579 if ( G_SpawnFloat( "modelscale", "1", &scale[0] ) ) {
580 scale[2] = scale[1] = scale[0];
581 }
582
583 // look for axis specific scaling
584 if ( G_SpawnVector( "modelscale_vec", "1 1 1", &scalevec[0] ) ) {
585 VectorCopy( scalevec, scale );
586 }
587
588 G_SpawnInt( "trunk", "0", &trunksize );
589 if ( !G_SpawnInt( "trunkhight", "0", &trunkheight ) ) {
590 trunkheight = 256;
591 }
592
593 if ( trunksize ) {
594 float rad;
595
596 ent->clipmask = CONTENTS_SOLID;
597 ent->r.contents = CONTENTS_SOLID;
598
599 ent->r.svFlags |= SVF_CAPSULE;
600
601 rad = (float)trunksize / 2.0f;
602 VectorSet( ent->r.mins, -rad, -rad, 0 );
603 VectorSet( ent->r.maxs, rad, rad, trunkheight );
604 }
605
606 // scale is stored in 'angles2'
607 VectorCopy( scale, ent->s.angles2 );
608
609 G_SetOrigin( ent, ent->s.origin );
610 VectorCopy( ent->s.angles, ent->s.apos.trBase );
611
612 if ( ent->spawnflags & 1 ) {
613 ent->s.apos.trType = 1; // misc_gamemodels (since they have no movement) will use type = 0 for static models, type = 1 for auto-aligning models
614
615
616 }
617 trap_LinkEntity( ent );
618
619 }
620
621
622
623
624 //----(SA)
625
locateMaster(gentity_t * ent)626 void locateMaster( gentity_t *ent ) {
627 ent->target_ent = G_Find( NULL, FOFS( targetname ), ent->target );
628 if ( ent->target_ent ) {
629 ent->s.otherEntityNum = ent->target_ent->s.number;
630 }
631 }
632
633 /*QUAKED misc_vis_dummy (1 .5 0) (-8 -8 -8) (8 8 8)
634 If this entity is "visible" (in player's PVS) then it's target is forced to be active whether it is in the player's PVS or not.
635 This entity itself is never visible or transmitted to clients.
636 For safety, you should have each dummy only point at one entity (however, it's okay to have many dummies pointing at one entity)
637 */
SP_misc_vis_dummy(gentity_t * ent)638 void SP_misc_vis_dummy( gentity_t *ent ) {
639
640 if ( !ent->target ) { //----(SA) added safety check
641 G_Printf( "Couldn't find target for misc_vis_dummy at %s\n", vtos( ent->r.currentOrigin ) );
642 G_FreeEntity( ent );
643 return;
644 }
645
646 ent->r.svFlags |= SVF_VISDUMMY;
647 G_SetOrigin( ent, ent->s.origin );
648 trap_LinkEntity( ent );
649
650 ent->think = locateMaster;
651 ent->nextthink = level.time + 1000;
652
653 }
654
655 //----(SA) end
656
657 /*QUAKED misc_vis_dummy_multiple (1 .5 0) (-8 -8 -8) (8 8 8)
658 If this entity is "visible" (in player's PVS) then it's target is forced to be active whether it is in the player's PVS or not.
659 This entity itself is never visible or transmitted to clients.
660 This entity was created to have multiple speakers targeting it
661 */
SP_misc_vis_dummy_multiple(gentity_t * ent)662 void SP_misc_vis_dummy_multiple( gentity_t *ent ) {
663 if ( !ent->targetname ) {
664 G_Printf( "misc_vis_dummy_multiple needs a targetname at %s\n", vtos( ent->r.currentOrigin ) );
665 G_FreeEntity( ent );
666 return;
667 }
668
669 ent->r.svFlags |= SVF_VISDUMMY_MULTIPLE;
670 G_SetOrigin( ent, ent->s.origin );
671 trap_LinkEntity( ent );
672
673 }
674
675
676 //===========================================================
677
678 //----(SA)
679 /*QUAKED misc_light_surface (1 .5 0) (-8 -8 -8) (8 8 8)
680 The surfaces nearest these entities will be the only surfaces lit by the targeting light
681 This must be within 64 world units of the surface to be lit!
682 */
SP_misc_light_surface(gentity_t * ent)683 void SP_misc_light_surface( gentity_t *ent ) {
684 G_FreeEntity( ent );
685 }
686
687 //----(SA) end
688
689 //===========================================================
690
locateCamera(gentity_t * ent)691 void locateCamera( gentity_t *ent ) {
692 vec3_t dir;
693 gentity_t *target;
694 gentity_t *owner;
695
696 owner = G_PickTarget( ent->target );
697 if ( !owner ) {
698 G_Printf( "Couldn't find target for misc_partal_surface\n" );
699 G_FreeEntity( ent );
700 return;
701 }
702 ent->r.ownerNum = owner->s.number;
703
704 // frame holds the rotate speed
705 if ( owner->spawnflags & 1 ) {
706 ent->s.frame = 25;
707 } else if ( owner->spawnflags & 2 ) {
708 ent->s.frame = 75;
709 }
710
711 // clientNum holds the rotate offset
712 ent->s.clientNum = owner->s.clientNum;
713
714 VectorCopy( owner->s.origin, ent->s.origin2 );
715
716 // see if the portal_camera has a target
717 target = G_PickTarget( owner->target );
718 if ( target ) {
719 VectorSubtract( target->s.origin, owner->s.origin, dir );
720 VectorNormalize( dir );
721 } else {
722 G_SetMovedir( owner->s.angles, dir );
723 }
724
725 ent->s.eventParm = DirToByte( dir );
726 }
727
728 /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
729 The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
730 This must be within 64 world units of the surface!
731 */
SP_misc_portal_surface(gentity_t * ent)732 void SP_misc_portal_surface( gentity_t *ent ) {
733 VectorClear( ent->r.mins );
734 VectorClear( ent->r.maxs );
735 trap_LinkEntity( ent );
736
737 ent->r.svFlags = SVF_PORTAL;
738 ent->s.eType = ET_PORTAL;
739
740 if ( !ent->target ) {
741 VectorCopy( ent->s.origin, ent->s.origin2 );
742 } else {
743 ent->think = locateCamera;
744 ent->nextthink = level.time + 100;
745 }
746 }
747
748 /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate
749 The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view.
750 "roll" an angle modifier to orient the camera around the target vector;
751 */
SP_misc_portal_camera(gentity_t * ent)752 void SP_misc_portal_camera( gentity_t *ent ) {
753 float roll;
754
755 VectorClear( ent->r.mins );
756 VectorClear( ent->r.maxs );
757 trap_LinkEntity( ent );
758
759 G_SpawnFloat( "roll", "0", &roll );
760
761 ent->s.clientNum = roll / 360.0 * 256;
762 }
763
764 /*
765 ======================================================================
766
767 SHOOTERS
768
769 ======================================================================
770 */
771
Use_Shooter(gentity_t * ent,gentity_t * other,gentity_t * activator)772 void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
773 vec3_t dir;
774 float deg;
775 vec3_t up, right;
776
777 // see if we have a target
778 if ( ent->enemy ) {
779 VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir );
780 if ( ent->s.weapon != WP_SNIPER ) {
781 VectorNormalize( dir );
782 }
783 } else {
784 VectorCopy( ent->movedir, dir );
785 }
786
787 if ( ent->s.weapon == WP_MORTAR ) {
788 AimAtTarget( ent ); // store in ent->s.origin2 the direction/force needed to pass through the target
789 VectorCopy( ent->s.origin2, dir );
790 }
791
792 if ( ent->s.weapon != WP_SNIPER ) {
793 // randomize a bit
794 PerpendicularVector( up, dir );
795 CrossProduct( up, dir, right );
796
797 deg = crandom() * ent->random;
798 VectorMA( dir, deg, up, dir );
799
800 deg = crandom() * ent->random;
801 VectorMA( dir, deg, right, dir );
802
803 VectorNormalize( dir );
804 }
805
806 switch ( ent->s.weapon ) {
807 case WP_GRENADE_LAUNCHER:
808 VectorScale( dir, 700, dir ); //----(SA) had to add this as fire_grenade now expects a non-normalized direction vector
809 fire_grenade( ent, ent->s.origin, dir, WP_GRENADE_LAUNCHER );
810 break;
811 case WP_PANZERFAUST:
812 fire_rocket( ent, ent->s.origin, dir );
813 break;
814
815 case WP_MONSTER_ATTACK1:
816 fire_zombiespit( ent, ent->s.origin, dir );
817 break;
818
819 // Rafael sniper
820 case WP_SNIPER:
821 fire_lead( ent, ent->s.origin, dir, ent->damage );
822 break;
823 // done
824
825 case WP_MORTAR:
826 AimAtTarget( ent ); // store in ent->s.origin2 the direction/force needed to pass through the target
827 VectorScale( dir, VectorLength( ent->s.origin2 ), dir );
828 fire_mortar( ent, ent->s.origin, dir );
829 break;
830
831 }
832
833 G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
834 }
835
InitShooter_Finish(gentity_t * ent)836 static void InitShooter_Finish( gentity_t *ent ) {
837 ent->enemy = G_PickTarget( ent->target );
838 ent->think = 0;
839 ent->nextthink = 0;
840 }
841
InitShooter(gentity_t * ent,int weapon)842 void InitShooter( gentity_t *ent, int weapon ) {
843 ent->use = Use_Shooter;
844 ent->s.weapon = weapon;
845
846 // Rafael sniper
847 if ( weapon != WP_SNIPER ) {
848 RegisterItem( BG_FindItemForWeapon( weapon ) );
849 }
850 // done
851
852 G_SetMovedir( ent->s.angles, ent->movedir );
853
854 if ( !ent->random ) {
855 ent->random = 1.0;
856 }
857
858 if ( ent->s.weapon != WP_SNIPER ) {
859 ent->random = sin( M_PI * ent->random / 180 );
860 }
861
862 // target might be a moving object, so we can't set movedir for it
863 if ( ent->target ) {
864 ent->think = InitShooter_Finish;
865 ent->nextthink = level.time + 500;
866 }
867 trap_LinkEntity( ent );
868 }
869
870 /*QUAKED shooter_mortar (1 0 0) (-16 -16 -16) (16 16 16) SMOKE_FX FLASH_FX
871 Lobs a mortar so that it will pass through the info_notnull targeted by this entity
872 "random" the number of degrees of deviance from the taget. (1.0 default)
873 if LAUNCH_FX is checked a smoke effect will play at the origin of this entity.
874 if FLASH_FX is checked a muzzle flash effect will play at the origin of this entity.
875 */
SP_shooter_mortar(gentity_t * ent)876 void SP_shooter_mortar( gentity_t *ent ) {
877 // (SA) TODO: must have a self->target. Do a check/print if this is not the case.
878 InitShooter( ent, WP_MORTAR );
879
880 if ( ent->spawnflags & 1 ) { // smoke at source
881 }
882 if ( ent->spawnflags & 2 ) { // muzzle flash at source
883 }
884 }
885
886 /*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
887 Fires at either the target or the current direction.
888 "random" the number of degrees of deviance from the taget. (1.0 default)
889 */
SP_shooter_rocket(gentity_t * ent)890 void SP_shooter_rocket( gentity_t *ent ) {
891 InitShooter( ent, WP_PANZERFAUST );
892 }
893
894 /*QUAKED shooter_zombiespit (1 0 0) (-16 -16 -16) (16 16 16)
895 Fires at either the target or the current direction.
896 "random" the number of degrees of deviance from the taget. (1.0 default)
897 */
SP_shooter_zombiespit(gentity_t * ent)898 void SP_shooter_zombiespit( gentity_t *ent ) {
899 InitShooter( ent, WP_MONSTER_ATTACK1 );
900 }
901
902
903 /*
904 ==============
905 use_shooter_tesla
906 ==============
907 */
use_shooter_tesla(gentity_t * ent,gentity_t * other,gentity_t * activator)908 void use_shooter_tesla( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
909 gentity_t *tent;
910
911 if ( ent->r.linked ) {
912 trap_UnlinkEntity( ent );
913 } else
914 {
915 tent = G_PickTarget( ent->target );
916 VectorCopy( tent->s.origin, ent->s.origin2 );
917
918 ent->active = 0;
919 trap_LinkEntity( ent );
920 }
921 }
922
923 //----(SA) added
924 /*QUAKED shooter_tesla (1 0 0) (-16 -16 -16) (16 16 16) START_ON DLIGHT
925 START_ON means it starts out active, the default is to start off and fire when triggered
926 DLIGHT will have a built-in dlight flashing too (use color picker to set color of dlight. (def: blue) )
927
928 "sticktime" - how long each bolt should 'stick' to an impact point (def: .5)
929 "random" - how far away to drift from the target. (def: 0.0)
930 "width" - width of the bolts (def: 20)
931 "count" - number of bolts to fire per impact point. (def: 2)
932 "dlightsize" - how big to make the attached light. (def: 500)
933 */
934
shooter_tesla_finish_spawning(gentity_t * ent)935 void shooter_tesla_finish_spawning( gentity_t *ent ) {
936 gentity_t *tent; // target ent
937
938 ent->think = 0;
939 ent->nextthink = 0;
940
941 // locate the target and set the location
942 tent = G_PickTarget( ent->target );
943 if ( !tent ) { // if there's a problem with tent
944 G_Printf( "shooter_tesla (%s) at %s has no target.\n", ent->target, vtos( ent->s.origin ) );
945 return;
946 }
947
948 VectorCopy( tent->s.origin, ent->s.origin2 );
949
950 if ( ent->spawnflags & 1 ) { // START_ON
951 ent->active = 0;
952 trap_LinkEntity( ent );
953 }
954 }
955
SP_shooter_tesla(gentity_t * ent)956 void SP_shooter_tesla( gentity_t *ent ) {
957
958 float tempf;
959
960 // it's a shooter_ since it will act like any other shooter, except
961 // the tesla is a client-side effect that reports damage back to the
962 // game, so this will create an linked-entity on the client that acts as dictated
963 // and, if necessary, passes back damage info like the weapon. this will
964 // keep the server->client messages to a minimum (start firing/stop firing)
965 // rather than sending an event for each bolt.
966 ent->s.eType = ET_TESLA_EF;
967 ent->use = use_shooter_tesla;
968
969 // set number of bolts
970 if ( ent->count ) {
971 ent->s.density = ent->count;
972 } else {
973 ent->s.density = 2;
974 }
975
976
977 // width
978 if ( G_SpawnFloat( "width", "", &tempf ) ) {
979 ent->s.frame = (int)tempf;
980 } else {
981 ent->s.frame = 20;
982 }
983
984
985 // 'sticky' time (stored in .weapon)
986 if ( G_SpawnFloat( "sticktime", "", &tempf ) ) {
987 ent->s.time2 = (int)( tempf * 1000.0f );
988 } else {
989 ent->s.time2 = 500; // default to 1/2 sec
990
991 }
992 // randomness
993 ent->s.angles2[0] = ent->random;
994
995
996 // DLIGHT
997 if ( ent->spawnflags & 2 ) {
998 int dlightsize;
999 if ( G_SpawnInt( "dlightsize", "", &dlightsize ) ) {
1000 ent->s.time = dlightsize;
1001 } else {
1002 ent->s.time = 500;
1003 }
1004
1005 if ( ent->random ) {
1006 ent->s.time2 = ent->random;
1007 } else {
1008 ent->s.time2 = 4; // dlight randomness
1009
1010 }
1011 if ( ent->dl_color[0] <= 0 && // if it's black or has no color assigned
1012 ent->dl_color[1] <= 0 &&
1013 ent->dl_color[2] <= 0 ) {
1014 // default is the same color as the tesla weapon
1015 ent->dl_color[0] = 0.2f;
1016 ent->dl_color[1] = 0.6f;
1017 ent->dl_color[2] = 1.0f;
1018 }
1019
1020 ent->dl_color[0] = ent->dl_color[0] * 255;
1021 ent->dl_color[1] = ent->dl_color[1] * 255;
1022 ent->dl_color[2] = ent->dl_color[2] * 255;
1023
1024 ent->s.dl_intensity = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 );
1025
1026 } else {
1027 ent->s.dl_intensity = 0;
1028 }
1029
1030
1031 // finish up after everything has spawned in so we know all potential targets are ready
1032 ent->think = shooter_tesla_finish_spawning;
1033 ent->nextthink = level.time + 100;
1034 }
1035 //----(SA) end
1036
1037
1038 /*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
1039 Fires at either the target or the current direction.
1040 "random" is the number of degrees of deviance from the taget. (1.0 default)
1041 */
SP_shooter_grenade(gentity_t * ent)1042 void SP_shooter_grenade( gentity_t *ent ) {
1043 InitShooter( ent, WP_GRENADE_LAUNCHER );
1044 }
1045
1046 // Rafael sniper
1047 /*QUAKED shooter_sniper (1 0 0) (-16 -16 -16) (16 16 16)
1048 Fires at either the target or the current direction.
1049 "random" is the number of degrees of deviance from the taget. (1.0 default)
1050 "damage" the amount of damage sniper will cause when he hits his target default is 10
1051 "radius" is the dist the target would need to travel before sniper lost his beat default 256
1052 "delay" is the rate of fire defaults to 1 sec
1053 */
SP_shooter_sniper(gentity_t * ent)1054 void SP_shooter_sniper( gentity_t *ent ) {
1055
1056 char *damage;
1057
1058 if ( G_SpawnString( "damage", "0", &damage ) ) {
1059 ent->damage = atoi( damage );
1060 }
1061
1062 if ( !ent->damage ) {
1063 ent->damage = 10;
1064 }
1065 if ( !ent->radius ) { // radius
1066 ent->radius = 256;
1067 }
1068 if ( !ent->delay ) {
1069 ent->delay = 1.0; // one sec
1070
1071 }
1072 InitShooter( ent, WP_SNIPER );
1073
1074 ent->delay *= 1000;
1075
1076 ent->wait = level.time + ent->delay;
1077 }
1078
brush_activate_sniper(gentity_t * ent,gentity_t * other,trace_t * trace)1079 void brush_activate_sniper( gentity_t *ent, gentity_t *other, trace_t *trace ) {
1080 gentity_t *sniper;
1081 float dist;
1082 vec3_t vec;
1083 gentity_t *player;
1084
1085 player = AICast_FindEntityForName( "player" );
1086
1087 if ( player && player != other ) {
1088 // G_Printf ("other: %s\n", other->aiName);
1089 return;
1090 }
1091
1092 if ( other->client ) {
1093 ent->enemy = other;
1094 }
1095
1096 sniper = G_Find( NULL, FOFS( targetname ), ent->target );
1097
1098 if ( !sniper ) {
1099 G_Printf( "sniper not found\n" );
1100 } else
1101 {
1102 if ( visible( sniper, other ) ) {
1103 if ( sniper->wait < level.time ) {
1104 if ( sniper->count == 0 ) {
1105 sniper->count = 1;
1106 sniper->wait = level.time + sniper->delay;
1107 // record enemypos pos
1108 VectorCopy( ent->enemy->r.currentOrigin, ent->pos1 );
1109 } else if ( sniper->count == 1 ) {
1110 VectorSubtract( ent->enemy->r.currentOrigin, ent->pos1, vec );
1111 dist = VectorLength( vec );
1112 if ( dist < sniper->radius ) {
1113 // ok the enemy is still inside the radius take a shot
1114 sniper->enemy = other;
1115 sniper->use( sniper, other, other );
1116 G_UseTargets( ent, other );
1117
1118 // added sniper shot
1119
1120 G_AddEvent( player, EV_GENERAL_SOUND, sniper_sound );
1121
1122 }
1123
1124 // reset the sniper delay
1125 sniper->count = 0;
1126 sniper->wait = level.time + sniper->delay;
1127 }
1128 }
1129 } else
1130 {
1131 //sniper->wait = level.time + sniper->delay;
1132 sniper->count = 0;
1133 }
1134 }
1135
1136 }
1137
sniper_brush_init(gentity_t * ent)1138 void sniper_brush_init( gentity_t *ent ) {
1139 vec3_t center;
1140
1141 if ( !ent->target ) {
1142 VectorSubtract( ent->r.maxs, ent->r.mins, center );
1143 VectorScale( center, 0.5, center );
1144
1145 G_Printf( "sniper_brush at %s without a target\n", vtos( center ) );
1146 }
1147 }
1148
1149 extern void InitTrigger( gentity_t *self );
1150
1151 /*QUAKED sniper_brush (1 0 0) ?
1152 this should be a volume that will encompase the area where the sniper target assigned to the
1153 brush would fire at the player
1154 */
SP_sniper_brush(gentity_t * ent)1155 void SP_sniper_brush( gentity_t *ent ) {
1156 ent->nextthink = level.time + FRAMETIME;
1157 ent->think = sniper_brush_init;
1158 ent->touch = brush_activate_sniper;
1159
1160 sniper_sound = G_SoundIndex( "sound/weapons/machinegun/machgf1b.wav" );
1161
1162 InitTrigger( ent );
1163 trap_LinkEntity( ent );
1164 }
1165
1166
1167
1168 /*QUAKED corona (0 1 0) (-4 -4 -4) (4 4 4) START_OFF
1169 Use color picker to set color or key "color". values are 0.0-1.0 for each color (rgb).
1170 "scale" will designate a multiplier to the default size. (so 2.0 is 2xdefault size, 0.5 is half)
1171 */
1172
1173 /*
1174 ==============
1175 use_corona
1176 so level designers can toggle them on/off
1177 ==============
1178 */
use_corona(gentity_t * ent,gentity_t * other,gentity_t * activator)1179 void use_corona( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
1180 if ( ent->r.linked ) {
1181 trap_UnlinkEntity( ent );
1182 } else
1183 {
1184 ent->active = 0;
1185 trap_LinkEntity( ent );
1186 }
1187 }
1188
1189
1190 /*
1191 ==============
1192 SP_corona
1193 ==============
1194 */
SP_corona(gentity_t * ent)1195 void SP_corona( gentity_t *ent ) {
1196 float scale;
1197
1198 ent->s.eType = ET_CORONA;
1199
1200 if ( ent->dl_color[0] <= 0 && // if it's black or has no color assigned
1201 ent->dl_color[1] <= 0 &&
1202 ent->dl_color[2] <= 0 ) {
1203 ent->dl_color[0] = ent->dl_color[1] = ent->dl_color[2] = 1; // set white
1204
1205 }
1206 ent->dl_color[0] = ent->dl_color[0] * 255;
1207 ent->dl_color[1] = ent->dl_color[1] * 255;
1208 ent->dl_color[2] = ent->dl_color[2] * 255;
1209
1210 ent->s.dl_intensity = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 );
1211
1212 G_SpawnFloat( "scale", "1", &scale );
1213 ent->s.density = (int)( scale * 255 );
1214
1215 ent->use = use_corona;
1216
1217 if ( !( ent->spawnflags & 1 ) ) {
1218 trap_LinkEntity( ent );
1219 }
1220 }
1221
1222
1223 // (SA) dlights and dlightstyles
1224
1225 char* predef_lightstyles[] = {
1226 "mmnmmommommnonmmonqnmmo",
1227 "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba",
1228 "mmmmmaaaaammmmmaaaaaabcdefgabcdefg",
1229 "ma",
1230 "jklmnopqrstuvwxyzyxwvutsrqponmlkj",
1231 "nmonqnmomnmomomono",
1232 "mmmaaaabcdefgmmmmaaaammmaamm",
1233 "aaaaaaaazzzzzzzz",
1234 "mmamammmmammamamaaamammma",
1235 "abcdefghijklmnopqrrqponmlkjihgfedcba",
1236 "mmnommomhkmmomnonmmonqnmmo",
1237 "kmamaamakmmmaakmamakmakmmmma",
1238 "kmmmakakmmaaamammamkmamakmmmma",
1239 "mmnnoonnmmmmmmmmmnmmmmnonmmmmmmm",
1240 "mmmmnonmmmmnmmmmmnonmmmmmnmmmmmmm",
1241 "zzzzzzzzaaaaaaaa",
1242 "zzzzzzzzaaaaaaaaaaaaaaaa",
1243 "aaaaaaaazzzzzzzzaaaaaaaa",
1244 "aaaaaaaaaaaaaaaazzzzzzzz"
1245 };
1246
1247
1248 /*
1249 ==============
1250 dlight_finish_spawning
1251 All the dlights should call this on the same frame, thereby
1252 being synched, starting their sequences all at the same time.
1253 ==============
1254 */
dlight_finish_spawning(gentity_t * ent)1255 void dlight_finish_spawning( gentity_t *ent ) {
1256 G_FindConfigstringIndex( va( "%i %s %i %i %i", ent->s.number, ent->dl_stylestring, ent->health, ent->soundLoop, ent->dl_atten ), CS_DLIGHTS, MAX_DLIGHT_CONFIGSTRINGS, qtrue );
1257 }
1258
1259 static int dlightstarttime = 0;
1260
1261
1262 /*QUAKED dlight (0 1 0) (-12 -12 -12) (12 12 12) FORCEACTIVE STARTOFF ONETIME
1263 "style": value is an int from 1-19 that contains a pre-defined 'flicker' string.
1264 "stylestring": set your own 'flicker' string. (ex. "klmnmlk"). NOTE: this should be all lowercase
1265 Stylestring characters run at 10 cps in the game. (meaning the alphabet, at 24 characters, would take 2.4 seconds to cycle)
1266 "offset": change the initial index in a style string. So val of 3 in the above example would start this light at 'N'. (used to get dlights using the same style out of sync).
1267 "atten": offset from the alpha values of the stylestring. stylestring of "ddeeffzz" with an atten of -1 would result in "ccddeeyy"
1268 Use color picker to set color or key "color". values are 0.0-1.0 for each color (rgb).
1269 FORCEACTIVE - toggle makes sure this light stays alive in a map even if the user has r_dynamiclight set to 0.
1270 STARTOFF - means the dlight doesn't spawn in until ent is triggered
1271 ONETIME - when the dlight is triggered, it will play through it's cycle once, then shut down until triggered again
1272 "shader" name of shader to apply
1273 "sound" sound to loop every cycle (this actually just plays the sound at the beginning of each cycle)
1274
1275 styles:
1276 1 - "mmnmmommommnonmmonqnmmo"
1277 2 - "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"
1278 3 - "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"
1279 4 - "ma"
1280 5 - "jklmnopqrstuvwxyzyxwvutsrqponmlkj"
1281 6 - "nmonqnmomnmomomono"
1282 7 - "mmmaaaabcdefgmmmmaaaammmaamm"
1283 8 - "aaaaaaaazzzzzzzz"
1284 9 - "mmamammmmammamamaaamammma"
1285 10 - "abcdefghijklmnopqrrqponmlkjihgfedcba"
1286 11 - "mmnommomhkmmomnonmmonqnmmo"
1287 12 - "kmamaamakmmmaakmamakmakmmmma"
1288 13 - "kmmmakakmmaaamammamkmamakmmmma"
1289 14 - "mmnnoonnmmmmmmmmmnmmmmnonmmmmmmm"
1290 15 - "mmmmnonmmmmnmmmmmnonmmmmmnmmmmmmm"
1291 16 - "zzzzzzzzaaaaaaaa"
1292 17 - "zzzzzzzzaaaaaaaaaaaaaaaa"
1293 18 - "aaaaaaaazzzzzzzzaaaaaaaa"
1294 19 - "aaaaaaaaaaaaaaaazzzzzzzz"
1295 */
1296
1297
1298 /*
1299 ==============
1300 shutoff_dlight
1301 the dlight knew when it was triggered to unlink after going through it's cycle once
1302 ==============
1303 */
shutoff_dlight(gentity_t * ent)1304 void shutoff_dlight( gentity_t *ent ) {
1305 if ( !( ent->r.linked ) ) {
1306 return;
1307 }
1308
1309 trap_UnlinkEntity( ent );
1310 ent->think = 0;
1311 ent->nextthink = 0;
1312 }
1313
1314
1315 /*
1316 ==============
1317 use_dlight
1318 ==============
1319 */
use_dlight(gentity_t * ent,gentity_t * other,gentity_t * activator)1320 void use_dlight( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
1321 if ( ent->r.linked ) {
1322 trap_UnlinkEntity( ent );
1323 } else
1324 {
1325 ent->active = 0;
1326 trap_LinkEntity( ent );
1327
1328 if ( ent->spawnflags & 4 ) { // ONETIME
1329 ent->think = shutoff_dlight;
1330 ent->nextthink = level.time + ( strlen( ent->dl_stylestring ) * 100 ) - 100;
1331 }
1332 }
1333 }
1334
1335
1336
1337 /*
1338 ==============
1339 SP_dlight
1340 ent->dl_stylestring contains the lightstyle string
1341 ent->health tracks current index into style string
1342 ent->count tracks length of style string
1343 ==============
1344 */
SP_dlight(gentity_t * ent)1345 void SP_dlight( gentity_t *ent ) {
1346 char *snd, *shader;
1347 int i;
1348 int offset, style, atten;
1349
1350 G_SpawnInt( "offset", "0", &offset ); // starting index into the stylestring
1351 G_SpawnInt( "style", "0", &style ); // predefined stylestring
1352 G_SpawnString( "sound", "", &snd ); //
1353 G_SpawnInt( "atten", "0", &atten ); //
1354 G_SpawnString( "shader", "", &shader ); // name of shader to use for this dlight image
1355
1356 if ( G_SpawnString( "sound", "0", &snd ) ) {
1357 ent->soundLoop = G_SoundIndex( snd );
1358 }
1359
1360 if ( ent->dl_stylestring && strlen( ent->dl_stylestring ) ) { // if they're specified in a string, use em
1361 } else if ( style ) {
1362 style = max( 1, style ); // clamp to predefined range
1363 style = min( 19, style );
1364 ent->dl_stylestring = predef_lightstyles[style - 1]; // these are input as 1-20
1365 } else {
1366 ent->dl_stylestring = "mmmaaa"; // default to a strobe to call attention to this not being set
1367 }
1368
1369 ent->count = strlen( ent->dl_stylestring );
1370
1371 ent->dl_atten = atten;
1372
1373 // make the initial offset a valid index into the stylestring
1374 offset = offset % ( ent->count );
1375
1376 ent->health = offset; // set the offset into the string
1377
1378 ent->think = dlight_finish_spawning;
1379 if ( !dlightstarttime ) { // sync up all the dlights
1380 dlightstarttime = level.time + 100;
1381 }
1382 ent->nextthink = dlightstarttime;
1383
1384 if ( ent->dl_color[0] <= 0 && // if it's black or has no color assigned, make it white
1385 ent->dl_color[1] <= 0 &&
1386 ent->dl_color[2] <= 0 ) {
1387 ent->dl_color[0] = ent->dl_color[1] = ent->dl_color[2] = 1;
1388 }
1389
1390 ent->dl_color[0] = ent->dl_color[0] * 255; // range 0-255 now so the client doesn't have to on every update
1391 ent->dl_color[1] = ent->dl_color[1] * 255;
1392 ent->dl_color[2] = ent->dl_color[2] * 255;
1393
1394 i = (int)( ent->dl_stylestring[offset] ) - (int)'a';
1395 i = i * ( 1000.0f / 24.0f );
1396
1397 ent->s.constantLight = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 ) | ( i / 4 << 24 );
1398
1399 ent->use = use_dlight;
1400
1401 if ( !( ent->spawnflags & 2 ) ) {
1402 trap_LinkEntity( ent );
1403 }
1404
1405 }
1406 // done (SA)
1407
1408
1409
1410 // Rafael particles
1411 /*QUAKED misc_snow256 (1 0 0) (-256 -256 -16) (256 256 16) TURBULENT
1412 health = density defaults to 32
1413 */
1414 /*QUAKED misc_snow128 (1 0 0) (-128 -128 -16) (128 128 16) TURBULENT
1415 health = density defaults to 32
1416 */
1417 /*QUAKED misc_snow64 (1 0 0) (-64 -64 -16) (64 64 16) TURBULENT
1418 health = density defaults to 32
1419 */
1420 /*QUAKED misc_snow32 (1 0 0) (-32 -32 -16) (32 32 16) TURBULENT
1421 health = density defaults to 32
1422 */
1423
1424 /*QUAKED misc_bubbles8 (1 0 0) (-8 -8 0) (8 8 16) TURBULENT
1425 health = density defaults to 32
1426 */
1427 /*QUAKED misc_bubbles16 (1 0 0) (-16 -16 0) (16 16 16) TURBULENT
1428 health = density defaults to 32
1429 */
1430 /*QUAKED misc_bubbles32 (1 0 0) (-32 -32 0) (32 32 16) TURBULENT
1431 health = density defaults to 32
1432 */
1433 /*QUAKED misc_bubbles64 (1 0 0) (-64 -64 0) (64 64 64) TURBULENT
1434 health = density defaults to 32
1435 */
1436
1437
snowInPVS(gentity_t * ent)1438 void snowInPVS( gentity_t *ent ) {
1439 gentity_t *tent;
1440 gentity_t *player;
1441 qboolean inPVS = qfalse;
1442 qboolean oldactive;
1443
1444 oldactive = ent->active;
1445
1446 ent->nextthink = level.time + FRAMETIME;
1447
1448 player = AICast_FindEntityForName( "player" );
1449
1450 if ( player ) {
1451 inPVS = trap_InPVS( player->r.currentOrigin, ent->r.currentOrigin );
1452
1453 if ( inPVS ) {
1454 ent->active = qtrue;
1455 } else {
1456 ent->active = qfalse;
1457 }
1458 } else {
1459 return;
1460 }
1461
1462 // there hasn't been a change so bail
1463 if ( oldactive == ent->active ) {
1464 return;
1465 }
1466
1467 if ( ent->active ) {
1468 tent = G_TempEntity( player->r.currentOrigin, EV_SNOW_ON );
1469 // G_Printf( "on\n");
1470 } else
1471 {
1472 tent = G_TempEntity( player->r.currentOrigin, EV_SNOW_OFF );
1473 // G_Printf( "off\n");
1474 }
1475
1476 tent->s.frame = ent->s.number;
1477 trap_LinkEntity( ent );
1478 }
1479
snow_think(gentity_t * ent)1480 void snow_think( gentity_t *ent ) {
1481 trace_t tr;
1482 vec3_t dest;
1483 int turb;
1484
1485 VectorCopy( ent->s.origin, dest );
1486
1487 if ( ent->spawnflags & 2 ) { // bubble
1488 dest[2] += 8192;
1489 } else {
1490 dest[2] -= 8192;
1491 }
1492
1493 trap_Trace( &tr, ent->s.origin, NULL, NULL, dest, ent->s.number, MASK_SHOT );
1494
1495 if ( ent->spawnflags & 1 ) {
1496 turb = 1;
1497 } else {
1498 turb = 0;
1499 }
1500
1501 if ( !Q_stricmp( ent->classname, "misc_snow256" ) ) {
1502 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW256, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1503 } else if ( !Q_stricmp( ent->classname, "misc_snow128" ) ) {
1504 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW128, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1505 } else if ( !Q_stricmp( ent->classname, "misc_snow64" ) ) {
1506 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW64, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1507 } else if ( !Q_stricmp( ent->classname, "misc_snow32" ) ) {
1508 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW32, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1509 } else if ( !Q_stricmp( ent->classname, "misc_bubbles8" ) ) {
1510 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE8, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1511 } else if ( !Q_stricmp( ent->classname, "misc_bubbles16" ) ) {
1512 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE16, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1513 } else if ( !Q_stricmp( ent->classname, "misc_bubbles32" ) ) {
1514 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE32, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1515 } else if ( !Q_stricmp( ent->classname, "misc_bubbles64" ) ) {
1516 G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE64, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
1517 }
1518
1519 ent->think = snowInPVS;
1520 ent->nextthink = level.time + FRAMETIME;
1521
1522 }
1523
SP_Snow(gentity_t * ent)1524 void SP_Snow( gentity_t *ent ) {
1525 ent->think = snow_think;
1526 ent->nextthink = level.time + FRAMETIME;
1527
1528 G_SetOrigin( ent, ent->s.origin );
1529
1530 ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1531 ent->s.eType = ET_GENERAL;
1532
1533 trap_LinkEntity( ent );
1534
1535 if ( !ent->health ) {
1536 ent->health = 32;
1537 }
1538
1539 ent->active = qtrue;
1540 }
1541 // done.
1542
1543
SP_Bubbles(gentity_t * ent)1544 void SP_Bubbles( gentity_t *ent ) {
1545 ent->think = snow_think;
1546 ent->nextthink = level.time + FRAMETIME;
1547
1548 G_SetOrigin( ent, ent->s.origin );
1549
1550 ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1551 ent->s.eType = ET_GENERAL;
1552
1553 trap_LinkEntity( ent );
1554
1555 if ( !ent->health ) {
1556 ent->health = 32;
1557 }
1558
1559 ent->active = qtrue;
1560
1561 ent->spawnflags |= 2;
1562 }
1563
flakPuff(vec3_t origin,qboolean sky,vec3_t forward)1564 void flakPuff( vec3_t origin, qboolean sky, vec3_t forward ) {
1565 gentity_t *tent;
1566 vec3_t point;
1567
1568 VectorCopy( origin, point );
1569 if ( sky ) {
1570 VectorMA( point, -256, forward, point );
1571 }
1572
1573 point[2] += 16; // raise puff off the ground some
1574 tent = G_TempEntity( point, EV_SMOKE );
1575 VectorCopy( point, tent->s.origin );
1576 tent->s.time = 2000;
1577 tent->s.time2 = 1000;
1578 tent->s.density = 0;
1579 tent->s.angles2[0] = 16 + 8;
1580 tent->s.angles2[1] = 48 + 24;
1581 tent->s.angles2[2] = 10;
1582 }
1583
1584 int muzzleflashmodel;
1585
mg42_muzzleflash(gentity_t * ent,vec3_t muzzlepos)1586 void mg42_muzzleflash( gentity_t *ent, vec3_t muzzlepos ) { // cheezy, but lets me use this routine for finding the muzzle point for firing the actual bullet
1587
1588 vec3_t forward;
1589 vec3_t point;
1590 gentity_t *flash;
1591
1592 flash = G_Spawn();
1593
1594 if ( flash ) {
1595 VectorCopy( ent->s.pos.trBase, flash->s.origin );
1596 VectorCopy( ent->s.apos.trBase, flash->s.angles );
1597 G_SetAngle( flash, ent->s.apos.trBase );
1598 G_SetOrigin( flash, ent->s.pos.trBase );
1599
1600 VectorCopy( flash->s.origin, point );
1601 AngleVectors( flash->s.angles, forward, NULL, NULL );
1602 VectorMA( point, 40, forward, point );
1603
1604 if ( muzzlepos ) {
1605 VectorCopy( point, muzzlepos );
1606 }
1607
1608 G_SetOrigin( flash, point );
1609
1610 flash->s.modelindex = muzzleflashmodel;
1611
1612 flash->r.contents = CONTENTS_TRIGGER;
1613 flash->r.svFlags = SVF_USE_CURRENT_ORIGIN;
1614 flash->s.eType = ET_GENERAL;
1615
1616 flash->think = G_FreeEntity;
1617 flash->nextthink = level.time + 50;
1618
1619 trap_LinkEntity( flash );
1620 }
1621
1622 }
1623
1624 /*
1625 ==============
1626 Fire_Lead
1627 ==============
1628 */
1629 //----(SA) added 'activator' so the bits that used to expect 'ent' to be the gun still work
Fire_Lead(gentity_t * ent,gentity_t * activator,float spread,int damage,vec3_t muzzle,vec3_t angles)1630 void Fire_Lead( gentity_t *ent, gentity_t *activator, float spread, int damage, vec3_t muzzle, vec3_t angles ) {
1631 trace_t tr;
1632 vec3_t end, lead_muzzle, mg42_muzzle = {0};
1633 float r;
1634 float u;
1635 gentity_t *tent;
1636 gentity_t *traceEnt;
1637 vec3_t forward, right, up;
1638
1639 //qboolean isflak = qfalse;
1640
1641 AngleVectors( angles, forward, right, up );
1642
1643 // 'muzzle' is bogus for mg42. adjust for it
1644 if ( !Q_stricmp( ent->classname, "misc_mg42" ) ) {
1645 mg42_muzzleflash( ent, mg42_muzzle ); // get current position for mg42 muzzle flash/bullet origin
1646 VectorCopy( mg42_muzzle, lead_muzzle );
1647 } else {
1648 VectorCopy( muzzle, lead_muzzle );
1649 }
1650
1651 r = crandom() * spread;
1652 u = crandom() * spread;
1653 VectorMA( lead_muzzle, 8192, forward, end );
1654 VectorMA( end, r, right, end );
1655 VectorMA( end, u, up, end );
1656
1657 trap_Trace( &tr, lead_muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
1658
1659 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
1660 AICast_ProcessBullet( activator, lead_muzzle, tr.endpos );
1661 }
1662
1663 if ( tr.surfaceFlags & SURF_NOIMPACT ) {
1664 if ( !Q_stricmp( ent->classname, "misc_flak" ) ) {
1665 if ( ent->count == 1 ) {
1666 G_AddEvent( ent, EV_FLAKGUN1, 0 );
1667 } else if ( ent->count == 2 ) {
1668 G_AddEvent( ent, EV_FLAKGUN2, 0 );
1669 } else if ( ent->count == 3 ) {
1670 G_AddEvent( ent, EV_FLAKGUN3, 0 );
1671 } else if ( ent->count == 4 ) {
1672 G_AddEvent( ent, EV_FLAKGUN4, 0 );
1673 }
1674
1675 flakPuff( tr.endpos, qtrue, forward );
1676 } else {
1677 // mg42_muzzleflash (ent, 0);
1678 G_AddEvent( ent, EV_FIRE_WEAPON_MG42, 0 );
1679 }
1680 return;
1681 }
1682
1683 traceEnt = &g_entities[ tr.entityNum ];
1684
1685 // snap the endpos to integers, but nudged towards the line
1686 SnapVectorTowards( tr.endpos, lead_muzzle );
1687
1688 // send bullet impact
1689 if ( traceEnt->takedamage && traceEnt->client ) {
1690 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );
1691 tent->s.eventParm = traceEnt->s.number;
1692 tent->s.otherEntityNum = ent->s.number;
1693
1694 if ( LogAccuracyHit( traceEnt, ent ) ) {
1695 ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
1696 }
1697
1698 } else {
1699 // Ridah, bullet impact should reflect off surface
1700 vec3_t reflect;
1701 float dot;
1702
1703 if ( !Q_stricmp( ent->classname, "misc_mg42" ) ) {
1704 tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_WALL );
1705
1706 dot = DotProduct( forward, tr.plane.normal );
1707 VectorMA( forward, -2 * dot, tr.plane.normal, reflect );
1708 VectorNormalize( reflect );
1709
1710 tent->s.eventParm = DirToByte( reflect );
1711 tent->s.otherEntityNum = ent->s.number;
1712 tent->s.otherEntityNum2 = activator->s.number; // (SA) store the user id, so the client can position the tracer
1713 }
1714 // done.
1715 }
1716
1717 if ( traceEnt->takedamage ) {
1718 G_Damage( traceEnt, ent, ent, forward, tr.endpos,
1719 damage, 0, MOD_MACHINEGUN );
1720 }
1721
1722 if ( !Q_stricmp( ent->classname, "misc_mg42" ) ) {
1723 G_AddEvent( ent, EV_FIRE_WEAPON_MG42, 0 );
1724 } else if ( !Q_stricmp( ent->classname, "misc_flak" ) ) {
1725 if ( ent->count == 1 ) {
1726 G_AddEvent( ent, EV_FLAKGUN1, 0 );
1727 } else if ( ent->count == 2 ) {
1728 G_AddEvent( ent, EV_FLAKGUN2, 0 );
1729 } else if ( ent->count == 3 ) {
1730 G_AddEvent( ent, EV_FLAKGUN3, 0 );
1731 } else if ( ent->count == 4 ) {
1732 G_AddEvent( ent, EV_FLAKGUN4, 0 );
1733 }
1734
1735 flakPuff( tr.endpos, qfalse, forward );
1736 }
1737
1738 }
1739
1740 float AngleDifference( float ang1, float ang2 );
1741
clamp_hweapontofirearc(gentity_t * self,gentity_t * other,vec3_t dang)1742 void clamp_hweapontofirearc( gentity_t *self, gentity_t *other, vec3_t dang ) {
1743
1744 // NOTE: use this value, and THEN the cl_input.c scales to tweak the feel
1745 #define MG42_YAWSPEED 300.0 // degrees per second
1746 #define MG42_IDLEYAWSPEED 80.0 // degrees per second (while returning to base)
1747
1748 int i;
1749 float diff, yawspeed;
1750 qboolean clamped;
1751
1752 clamped = qfalse;
1753
1754 if ( other ) {
1755 VectorCopy( self->TargetAngles, dang );
1756 yawspeed = MG42_YAWSPEED;
1757 } else { // go back to start position
1758 VectorCopy( self->s.angles, dang );
1759 yawspeed = MG42_IDLEYAWSPEED;
1760 }
1761
1762 if ( dang[0] < 0 && dang[0] < -( self->varc ) ) {
1763 clamped = qtrue;
1764 dang[0] = -( self->varc );
1765 }
1766
1767 // NOTE to self this change has damaged visualy all mg42 behind sandbags
1768 if ( other && other->r.svFlags & SVF_CASTAI ) {
1769 if ( self->spawnflags & 1 ) {
1770 if ( dang[0] > 0 && dang[0] > 20.0 ) {
1771 clamped = qtrue;
1772 dang[0] = 20.0;
1773 }
1774 } else if ( dang[0] > 0 && dang[0] > 10.0 ) {
1775 clamped = qtrue;
1776 dang[0] = 10.0;
1777 }
1778 } else
1779 {
1780 if ( self->spawnflags & 1 ) {
1781 if ( dang[0] > 0 && dang[0] > ( self->varc / 2 ) ) {
1782 clamped = qtrue;
1783 dang[0] = self->varc / 2;
1784 }
1785 } else if ( dang[0] > 0 && dang[0] > ( self->varc / 2 ) ) {
1786 clamped = qtrue;
1787 dang[0] = self->varc / 2;
1788 }
1789 }
1790
1791 // G_Printf ("dang[0] = %5.2f\n", dang[0]);
1792
1793 if ( !Q_stricmp( self->classname, "misc_mg42" ) || !( self->active ) ) {
1794 diff = AngleDifference( dang[YAW], self->s.angles[YAW] );
1795 if ( fabs( diff ) > self->harc ) {
1796 clamped = qtrue;
1797 if ( diff > 0 ) {
1798 dang[YAW] = AngleMod( self->s.angles[YAW] + self->harc );
1799 } else {
1800 dang[YAW] = AngleMod( self->s.angles[YAW] - self->harc );
1801 }
1802 }
1803
1804 // dang is now the ideal angles
1805 for ( i = 0; i < 3; i++ ) {
1806 BG_EvaluateTrajectory( &self->s.apos, level.time, self->r.currentAngles );
1807 diff = AngleDifference( dang[i], self->r.currentAngles[i] );
1808 if ( fabs( diff ) > ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) ) {
1809 clamped = qtrue;
1810 if ( diff > 0 ) {
1811 dang[i] = AngleMod( self->r.currentAngles[i] + ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) );
1812 } else {
1813 dang[i] = AngleMod( self->r.currentAngles[i] - ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) );
1814 }
1815 }
1816 }
1817 }
1818
1819 if ( other && other->r.svFlags & SVF_CASTAI ) {
1820 clamped = qfalse;
1821 }
1822
1823 // sanity check the angles again to make sure we don't go passed the harc whilst trying to get to the other side
1824 // diff = AngleDifference( dang[YAW], self->s.angles[YAW] );
1825 diff = AngleDifference( self->s.angles[YAW], dang[YAW] );
1826 // if (fabs(diff) > self->harc) {
1827 if ( fabs( diff ) > self->harc && other && other->r.svFlags & SVF_CASTAI ) {
1828 clamped = qtrue;
1829
1830 if ( diff > 0 ) {
1831 dang[YAW] = AngleMod( self->s.angles[YAW] + self->harc );
1832 } else {
1833 dang[YAW] = AngleMod( self->s.angles[YAW] - self->harc );
1834 }
1835
1836 // G_Printf ("dang %5.2f ang %5.2f diff %5.2f\n", dang[YAW], self->s.angles[YAW], diff);
1837
1838 }
1839 // else
1840 // G_Printf ("not clamped cang %5.2f\n", self->TargetAngles[YAW]);
1841
1842
1843 if ( other && clamped ) {
1844 // we only do this to keep the input angles close to the weapon, this doesn't actually
1845 // effect the view
1846 SetClientViewAngle( other, dang );
1847
1848 //if they are an AI, they should dismount now
1849 if ( other->r.svFlags & SVF_CASTAI ) {
1850 if ( !other->mg42ClampTime ) {
1851 other->mg42ClampTime = level.time;
1852 } else if ( other->mg42ClampTime < level.time - 750 ) {
1853 other->active = qfalse;
1854 }
1855 }
1856 } else if ( other ) {
1857 other->mg42ClampTime = 0;
1858 }
1859
1860
1861 if ( g_mg42arc.integer ) {
1862 G_Printf( "varc = %5.2f\n", dang[0] );
1863 }
1864 }
1865
1866 // NOTE: this only effects the external view of the user, when using the mg42, the
1867 // view position is set on the client-side to keep it firm behind the gun with
1868 // interpolation
clamp_playerbehindgun(gentity_t * self,gentity_t * other,vec3_t dang)1869 void clamp_playerbehindgun( gentity_t *self, gentity_t *other, vec3_t dang ) {
1870 vec3_t forward, right, up;
1871 vec3_t point;
1872
1873
1874 AngleVectors( self->s.apos.trBase, forward, right, up );
1875 VectorMA( self->r.currentOrigin, -36, forward, point );
1876
1877 point[2] = other->r.currentOrigin[2];
1878 trap_UnlinkEntity( other );
1879 VectorCopy( point, other->client->ps.origin );
1880
1881 // save results of pmove
1882 BG_PlayerStateToEntityState( &other->client->ps, &other->s, qtrue );
1883
1884 // use the precise origin for linking
1885 VectorCopy( other->client->ps.origin, other->r.currentOrigin );
1886
1887 trap_LinkEntity( other );
1888 }
1889
1890 #define MG42_SPREAD 200
1891 #define MG42_DAMAGE 18
1892 #define MG42_DAMAGE_AI 9
1893 #define FIREARC 120
1894
1895 #define FLAK_SPREAD 100
1896 #define FLAK_DAMAGE 36
1897
mg42_touch(gentity_t * self,gentity_t * other,trace_t * trace)1898 void mg42_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
1899 vec3_t dang;
1900 int i;
1901
1902 if ( !self->active ) {
1903 return;
1904 }
1905
1906 if ( other->active ) {
1907 for ( i = 0; i < 3; i++ )
1908 dang[i] = SHORT2ANGLE( other->client->pers.cmd.angles[i] );
1909
1910 // the gun should go to our current angles next time it thinks
1911 VectorCopy( dang, self->TargetAngles );
1912 //VectorCopy( other->client->ps.viewangles, self->TargetAngles );
1913
1914 // now tell the client to lock the view in the direction of the gun
1915 //if (other->r.svFlags & SVF_CASTAI) {
1916 other->client->ps.viewlocked = 1;
1917 other->client->ps.viewlocked_entNum = self->s.number;
1918 //}
1919
1920 if ( self->s.frame ) {
1921 other->client->ps.gunfx = 1;
1922 } else {
1923 other->client->ps.gunfx = 0;
1924 }
1925
1926 // clamp the mg42 to fire arc
1927 VectorCopy( other->client->ps.viewangles, self->TargetAngles );
1928
1929 clamp_hweapontofirearc( self, other, dang );
1930
1931 // clamp player behind the gun
1932 clamp_playerbehindgun( self, other, dang );
1933
1934 VectorCopy( dang, self->TargetAngles );
1935 }
1936 }
1937
mg42_track(gentity_t * self,gentity_t * other)1938 void mg42_track( gentity_t *self, gentity_t *other ) {
1939 vec3_t dang;
1940 int i;
1941 qboolean validshot = qfalse;
1942 qboolean is_flak = qfalse;
1943 vec3_t forward, right, up;
1944 vec3_t muzzle;
1945
1946 if ( !Q_stricmp( self->classname, "misc_flak" ) ) {
1947 is_flak = qtrue;
1948 }
1949
1950 if ( !self->active ) {
1951 return;
1952 }
1953
1954 if ( other->active ) {
1955 if ( ( !( level.time % 100 ) ) && ( other->client ) && ( other->client->buttons & BUTTON_ATTACK ) ) {
1956 other->client->ps.viewlocked = 1;
1957
1958 if ( self->s.frame && !is_flak ) {
1959 // G_Printf ("gun: destroyed = %d\n", self->s.frame);
1960 G_AddEvent( self, EV_GENERAL_SOUND, snd_noammo );
1961 other->client->ps.gunfx = 1;
1962 } else
1963 {
1964 AngleVectors( self->s.apos.trBase, forward, right, up );
1965 VectorCopy( self->s.pos.trBase, muzzle );
1966
1967 if ( !Q_stricmp( self->classname, "misc_mg42" ) ) {
1968 VectorMA( muzzle, 16, forward, muzzle );
1969 VectorMA( muzzle, 16, up, muzzle );
1970 validshot = qtrue;
1971 } else if ( !Q_stricmp( self->classname, "misc_flak" ) ) {
1972 if ( self->delay < level.time ) {
1973 self->delay = level.time + 250;
1974
1975 self->count++;
1976
1977 if ( self->count > 4 ) {
1978 self->count = 1;
1979 }
1980
1981 // guns 1 and 2 were switched
1982 if ( self->count == 2 ) {
1983 VectorMA( muzzle, 72, forward, muzzle );
1984 VectorMA( muzzle, 31, up, muzzle );
1985 VectorMA( muzzle, 22, right, muzzle );
1986 } else if ( self->count == 1 ) {
1987 VectorMA( muzzle, 72, forward, muzzle );
1988 VectorMA( muzzle, 31, up, muzzle );
1989 VectorMA( muzzle, -22, right, muzzle );
1990 } else if ( self->count == 3 ) {
1991 VectorMA( muzzle, 72, forward, muzzle );
1992 VectorMA( muzzle, 10, up, muzzle );
1993 VectorMA( muzzle, 22, right, muzzle );
1994 } else if ( self->count == 4 ) {
1995 VectorMA( muzzle, 72, forward, muzzle );
1996 VectorMA( muzzle, 10, up, muzzle );
1997 VectorMA( muzzle, -22, right, muzzle );
1998 }
1999
2000 validshot = qtrue;
2001 self->s.frame++;
2002 }
2003 }
2004 // snap to integer coordinates for more efficient network bandwidth usage
2005 SnapVector( muzzle );
2006
2007 if ( validshot ) {
2008 if ( !( other->r.svFlags & SVF_CASTAI ) ) {
2009 if ( is_flak ) {
2010 Fire_Lead( self, other, FLAK_SPREAD, FLAK_DAMAGE, muzzle, self->s.apos.trBase );
2011 } else
2012 {
2013 Fire_Lead( self, other, MG42_SPREAD, MG42_DAMAGE, muzzle, self->s.apos.trBase );
2014 }
2015 } else
2016 {
2017 if ( self->damage ) {
2018 Fire_Lead( self, other, MG42_SPREAD / self->accuracy, self->damage, muzzle, self->s.apos.trBase );
2019 } else {
2020 Fire_Lead( self, other, MG42_SPREAD / self->accuracy, MG42_DAMAGE_AI, muzzle, self->s.apos.trBase );
2021 }
2022
2023 }
2024
2025 // play character anim
2026 BG_AnimScriptEvent( &other->client->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue );
2027
2028 other->client->ps.viewlocked = 2; // this enable screen jitter when firing
2029 }
2030 }
2031 }
2032
2033 // move to the position over the next frame
2034 VectorCopy( self->TargetAngles, dang );
2035 VectorSubtract( dang, self->s.apos.trBase, self->s.apos.trDelta );
2036 for ( i = 0; i < 3; i++ ) {
2037 self->s.apos.trDelta[i] = AngleNormalize180( self->s.apos.trDelta[i] );
2038 }
2039 VectorScale( self->s.apos.trDelta, 1000 / 50, self->s.apos.trDelta );
2040 self->s.apos.trTime = level.time;
2041 self->s.apos.trDuration = 50;
2042 }
2043 }
2044
2045 #define GUN1_IDLE 0
2046 #define GUN2_IDLE 4
2047 #define GUN3_IDLE 8
2048 #define GUN4_IDLE 12
2049
2050 #define GUN1_LASTFIRE 3
2051 #define GUN2_LASTFIRE 7
2052 #define GUN3_LASTFIRE 11
2053 #define GUN4_LASTFIRE 15
2054
Flak_Animate(gentity_t * ent)2055 void Flak_Animate( gentity_t *ent ) {
2056 //G_Printf ("frame %i\n", ent->s.frame);
2057
2058 if ( ent->s.frame == GUN1_IDLE
2059 || ent->s.frame == GUN2_IDLE
2060 || ent->s.frame == GUN3_IDLE
2061 || ent->s.frame == GUN4_IDLE ) {
2062 return;
2063 }
2064
2065 if ( ent->count == 1 ) {
2066 if ( ent->s.frame == GUN1_LASTFIRE ) {
2067 ent->s.frame = GUN2_IDLE;
2068 } else if ( ent->s.frame > GUN1_IDLE ) {
2069 ent->s.frame++;
2070 }
2071 } else if ( ent->count == 2 ) {
2072 if ( ent->s.frame == GUN2_LASTFIRE ) {
2073 ent->s.frame = GUN3_IDLE;
2074 } else if ( ent->s.frame > GUN2_IDLE ) {
2075 ent->s.frame++;
2076 }
2077 } else if ( ent->count == 3 ) {
2078 if ( ent->s.frame == GUN3_LASTFIRE ) {
2079 ent->s.frame = GUN4_IDLE;
2080 } else if ( ent->s.frame > GUN3_IDLE ) {
2081 ent->s.frame++;
2082 }
2083 } else if ( ent->count == 4 ) {
2084 if ( ent->s.frame == GUN4_LASTFIRE ) {
2085 ent->s.frame = GUN1_IDLE;
2086 } else if ( ent->s.frame > GUN4_IDLE ) {
2087 ent->s.frame++;
2088 }
2089 }
2090 }
2091
2092 #define USEMG42_DISTANCE 46
mg42_think(gentity_t * self)2093 void mg42_think( gentity_t *self ) {
2094 vec3_t vec;
2095 gentity_t *owner;
2096 int i;
2097 float len;
2098 float usedist;
2099 qboolean is_flak = qfalse;
2100
2101 if ( !Q_stricmp( self->classname, "misc_flak" ) ) {
2102 is_flak = qtrue;
2103 Flak_Animate( self );
2104 }
2105
2106 VectorClear( vec );
2107
2108 owner = &g_entities[self->r.ownerNum];
2109
2110 // move to the current angles
2111 BG_EvaluateTrajectory( &self->s.apos, level.time, self->s.apos.trBase );
2112
2113 if ( owner->client ) {
2114 VectorSubtract( self->r.currentOrigin, owner->r.currentOrigin, vec );
2115 len = VectorLength( vec );
2116
2117 if ( owner->r.svFlags & SVF_CASTAI ) {
2118 usedist = USEMG42_DISTANCE;
2119 } else {
2120 // kinda dumb since the player had to be close enough to activate it to get this
2121 // far and the start point for the difference is calculated differently each place anyway
2122 // usedist = 96;
2123 usedist = 999; // always allow the touch by player
2124
2125 }
2126 if ( len < usedist && ( owner->active == 1 ) && owner->health > 0 ) {
2127 self->active = qtrue;
2128 if ( is_flak ) {
2129 owner->client->ps.persistant[PERS_HWEAPON_USE] = 2;
2130 } else {
2131 owner->client->ps.persistant[PERS_HWEAPON_USE] = 1;
2132 }
2133 mg42_track( self, owner );
2134 self->nextthink = level.time + 50;
2135
2136 if ( !( owner->r.svFlags & SVF_CASTAI ) ) {
2137 clamp_playerbehindgun( self, owner, vec3_origin );
2138 }
2139
2140 //G_Printf ("len %5.2f\n", len);
2141 /*
2142 if (owner->r.svFlags & SVF_CASTAI)
2143 {
2144 gentity_t *player;
2145 vec3_t temp;
2146
2147 player = AICast_FindEntityForName ("player");
2148
2149 if (player)
2150 {
2151 VectorCopy (self->s.angles, temp);
2152 VectorCopy (self->s.angles2, self->s.angles);
2153 if (!(infront (self, player)))
2154 {
2155
2156 if (visible (player, self))
2157 {
2158 self->use (self, NULL, NULL);
2159 // G_Printf ("force use dismount cause not infront\n");
2160 }
2161
2162 }
2163
2164 VectorCopy (temp, self->s.angles);
2165
2166 }
2167
2168 }
2169 */
2170 return;
2171 }
2172
2173 // G_Printf ("FAILED len %5.2f\n", len);
2174
2175
2176 }
2177
2178 // slowly rotate back to position
2179 //clamp_hweapontofirearc( self, NULL, vec );
2180 // move to the position over the next frame
2181 VectorSubtract( self->s.angles, self->s.apos.trBase, self->s.apos.trDelta );
2182 for ( i = 0; i < 3; i++ ) {
2183 self->s.apos.trDelta[i] = AngleNormalize180( self->s.apos.trDelta[i] );
2184 }
2185 VectorScale( self->s.apos.trDelta, 400 / 50, self->s.apos.trDelta );
2186 self->s.apos.trTime = level.time;
2187 self->s.apos.trDuration = 50;
2188
2189 self->nextthink = level.time + 50;
2190
2191 // only let them go when it's pointing forward
2192 if ( owner->client ) {
2193 if ( fabs( AngleNormalize180( self->s.angles[YAW] - self->s.apos.trBase[YAW] ) ) > 10 ) {
2194 BG_EvaluateTrajectory( &self->s.apos, self->nextthink, owner->client->ps.viewangles );
2195 return; // still waiting
2196 }
2197 }
2198
2199 self->active = qfalse;
2200
2201 if ( owner->client ) {
2202 owner->client->ps.eFlags &= ~EF_MG42_ACTIVE; // whoops, missed this
2203 owner->client->ps.persistant[PERS_HWEAPON_USE] = 0;
2204 owner->client->ps.viewlocked = 0; // let them look around
2205 owner->active = qfalse;
2206 owner->client->ps.gunfx = 0;
2207 }
2208
2209 self->r.ownerNum = self->s.number;
2210
2211
2212 }
2213
mg42_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod)2214 void mg42_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {
2215 gentity_t *gun;
2216 gentity_t *owner;
2217
2218 // owner = &g_entities[self->r.ownerNum];
2219
2220 G_Sound( self, self->soundPos3 ); // death sound
2221
2222 // DHM - Nerve :: self->chain not set if no tripod
2223 if ( self->chain ) {
2224 gun = self->chain;
2225 } else {
2226 gun = self;
2227 }
2228 // dhm - end
2229
2230 owner = &g_entities[gun->r.ownerNum];
2231
2232 if ( gun && self->health <= 0 ) {
2233 gun->s.frame = 2;
2234 gun->takedamage = qfalse;
2235
2236
2237 // DHM - Nerve :: health is used in repairing later
2238 if ( g_gametype.integer == GT_WOLF ) {
2239 gun->health = 0;
2240 self->health = 0;
2241 }
2242 // dhm - end
2243 }
2244
2245 self->takedamage = qfalse;
2246
2247 if ( owner && owner->client ) {
2248 owner->client->ps.persistant[PERS_HWEAPON_USE] = 0;
2249 self->r.ownerNum = self->s.number;
2250 owner->client->ps.viewlocked = 0; // let them look around
2251 owner->active = qfalse;
2252 owner->client->ps.gunfx = 0;
2253
2254 self->active = qfalse;
2255 gun->active = qfalse;
2256 }
2257
2258
2259 trap_LinkEntity( self );
2260 }
2261
mg42_use(gentity_t * ent,gentity_t * other,gentity_t * activator)2262 void mg42_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
2263 gentity_t *owner;
2264
2265 owner = &g_entities[ent->r.ownerNum];
2266
2267 if ( owner && owner->client ) {
2268 owner->client->ps.persistant[PERS_HWEAPON_USE] = 0;
2269 ent->r.ownerNum = ent->s.number;
2270 owner->client->ps.viewlocked = 0; // let them look around
2271 owner->active = qfalse;
2272 owner->client->ps.gunfx = 0;
2273 }
2274
2275 // G_Printf ("mg42 called use function\n");
2276
2277 trap_LinkEntity( ent );
2278 }
2279
2280 /*
2281 ==============
2282 mg42_spawn
2283 ==============
2284 */
mg42_spawn(gentity_t * ent)2285 void mg42_spawn( gentity_t *ent ) {
2286 gentity_t *base, *gun;
2287 vec3_t offset;
2288
2289 ent->soundPos3 = G_SoundIndex( "sound/weapons/mg42/mg42_death.wav" ); // die sound
2290
2291 //if (!(ent->spawnflags & 2)) // no tripod
2292 {
2293 base = G_Spawn();
2294
2295 if ( !( ent->spawnflags & 2 ) ) { // no tripod
2296 base->clipmask = CONTENTS_SOLID;
2297 base->r.contents = CONTENTS_SOLID;
2298 base->r.svFlags = SVF_USE_CURRENT_ORIGIN;
2299 base->s.eType = ET_GENERAL;
2300
2301
2302 base->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/mg42b.md3" );
2303 }
2304
2305 VectorSet( base->r.mins, -8, -8, -8 );
2306 VectorSet( base->r.maxs, 8, 8, 48 );
2307 VectorCopy( ent->s.origin, offset );
2308 offset[2] -= 24;
2309 G_SetOrigin( base, offset );
2310 base->s.apos.trType = TR_STATIONARY;
2311 base->s.apos.trTime = 0;
2312 base->s.apos.trDuration = 0;
2313 base->s.dmgFlags = HINT_MG42; // identify this for cursorhints
2314 VectorCopy( ent->s.angles, base->s.angles );
2315 VectorCopy( base->s.angles, base->s.apos.trBase );
2316 VectorCopy( base->s.angles, base->s.apos.trDelta );
2317 base->health = ent->health;
2318 base->target = ent->target; //----(SA) added so mounting mg42 can trigger targets
2319 base->takedamage = qtrue;
2320 base->die = mg42_die;
2321 base->soundPos3 = ent->soundPos3; //----(SA)
2322 base->activateArc = ent->activateArc; //----(SA) added
2323 trap_LinkEntity( base );
2324 }
2325
2326 gun = G_Spawn();
2327 gun->classname = "misc_mg42";
2328 gun->clipmask = CONTENTS_SOLID;
2329 gun->r.contents = CONTENTS_TRIGGER;
2330 gun->r.svFlags = SVF_USE_CURRENT_ORIGIN;
2331 gun->s.eType = ET_MG42;
2332
2333 // DHM - Don't need to specify here, handled in G_CheckForCursorHints
2334 //gun->s.dmgFlags = HINT_MG42; // identify this for cursorhints
2335
2336 gun->touch = mg42_touch;
2337 gun->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/mg42a.md3" );
2338 VectorCopy( ent->s.origin, offset );
2339 offset[2] += 24;
2340 G_SetOrigin( gun, offset );
2341 VectorSet( gun->r.mins, -24, -24, -8 );
2342 VectorSet( gun->r.maxs, 24, 24, 48 );
2343 gun->s.apos.trTime = 0;
2344 gun->s.apos.trDuration = 0;
2345 VectorCopy( ent->s.angles, gun->s.angles );
2346 VectorCopy( gun->s.angles, gun->s.apos.trBase );
2347 VectorCopy( gun->s.angles, gun->s.apos.trDelta );
2348
2349 VectorCopy( ent->s.angles, gun->s.angles2 );
2350
2351 gun->think = mg42_think;
2352 gun->nextthink = level.time + FRAMETIME;
2353 gun->s.number = gun - g_entities;
2354 gun->harc = ent->harc;
2355 gun->varc = ent->varc;
2356 gun->s.apos.trType = TR_LINEAR_STOP; // interpolate the angles
2357 gun->takedamage = qtrue;
2358 gun->targetname = ent->targetname; // need this for scripting
2359 gun->damage = ent->damage;
2360 gun->health = ent->health; //----(SA) added
2361 gun->accuracy = ent->accuracy;
2362 gun->target = ent->target; //----(SA) added so mounting mg42 can trigger targets
2363 gun->use = mg42_use;
2364 gun->die = mg42_die; // JPW NERVE we want it to be called for non-tripod machineguns too (for mp_beach etc)
2365 gun->soundPos3 = ent->soundPos3; //----(SA)
2366 gun->activateArc = ent->activateArc; //----(SA) added
2367
2368 if ( !( ent->spawnflags & 2 ) ) { // no tripod
2369 gun->mg42BaseEnt = base->s.number;
2370 } else {
2371 gun->mg42BaseEnt = -1;
2372 }
2373
2374 gun->spawnflags = ent->spawnflags;
2375
2376 trap_LinkEntity( gun );
2377
2378 if ( !( ent->spawnflags & 2 ) ) { // no tripod
2379 base->chain = gun;
2380 }
2381
2382 G_FreeEntity( ent );
2383
2384
2385 muzzleflashmodel = G_ModelIndex( "models/weapons2/machinegun/mg42_flash.md3" );
2386
2387 }
2388
2389 /*QUAKED misc_mg42 (1 0 0) (-16 -16 -24) (16 16 24) HIGH NOTRIPOD
2390 harc - horizonal fire arc. Default is 115
2391 varc - vertical fire arc. Default is 45
2392 grabarc - activatable arc behind the gun. if not specified, it uses the old default grabbing dynamics
2393 health - how much damage can it take. Default is 50
2394 damage - determines how much the weapon will inflict if a non player uses it
2395 accuracy - all guns are 100% accurate a value of 0.5 would make it 50%
2396 */
SP_mg42(gentity_t * self)2397 void SP_mg42( gentity_t *self ) {
2398 char *damage;
2399 char *accuracy;
2400 float grabarc;
2401
2402 if ( !self->harc ) {
2403 self->harc = 115;
2404 } else
2405 {
2406 if ( self->harc < 45 ) {
2407 self->harc = 45;
2408 }
2409 }
2410
2411 if ( !self->varc ) {
2412 self->varc = 90.0;
2413 }
2414
2415 if ( !self->health ) {
2416 self->health = 100;
2417 }
2418
2419 self->think = mg42_spawn;
2420 self->nextthink = level.time + FRAMETIME;
2421
2422 snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" );
2423
2424 G_SpawnFloat( "grabarc", "0", &grabarc ); // half arc, so actually activatable over 120 deg
2425 self->activateArc = grabarc;
2426
2427
2428 if ( G_SpawnString( "damage", "0", &damage ) ) {
2429 self->damage = atoi( damage );
2430 }
2431
2432 G_SpawnString( "accuracy", "1.0", &accuracy );
2433
2434 self->accuracy = atof( accuracy );
2435
2436 if ( !self->accuracy ) {
2437 self->accuracy = 1;
2438 }
2439 // JPW NERVE
2440 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
2441 if ( !self->damage ) {
2442 self->damage = 25;
2443 }
2444 }
2445 // jpw
2446 }
2447
2448
flak_spawn(gentity_t * ent)2449 void flak_spawn( gentity_t *ent ) {
2450 gentity_t *gun;
2451 vec3_t offset;
2452
2453 gun = G_Spawn();
2454 gun->classname = "misc_flak";
2455 gun->clipmask = CONTENTS_SOLID;
2456 gun->r.contents = CONTENTS_TRIGGER;
2457 gun->r.svFlags = SVF_USE_CURRENT_ORIGIN;
2458 gun->s.eType = ET_GENERAL;
2459 gun->touch = mg42_touch;
2460 gun->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/flak_a.md3" );
2461 VectorCopy( ent->s.origin, offset );
2462 G_SetOrigin( gun, offset );
2463 VectorSet( gun->r.mins, -24, -24, -8 );
2464 VectorSet( gun->r.maxs, 24, 24, 48 );
2465 gun->s.apos.trTime = 0;
2466 gun->s.apos.trDuration = 0;
2467 VectorCopy( ent->s.angles, gun->s.angles );
2468 VectorCopy( gun->s.angles, gun->s.apos.trBase );
2469 VectorCopy( gun->s.angles, gun->s.apos.trDelta );
2470 gun->think = mg42_think;
2471 gun->nextthink = level.time + FRAMETIME;
2472 gun->s.number = gun - g_entities;
2473 gun->harc = ent->harc;
2474 gun->varc = ent->varc;
2475 gun->s.apos.trType = TR_LINEAR_STOP; // interpolate the angles
2476 gun->takedamage = qtrue;
2477 gun->targetname = ent->targetname; // need this for scripting
2478 gun->mg42BaseEnt = ent->s.number;
2479
2480 trap_LinkEntity( gun );
2481
2482 }
2483
2484 /*QUAKED misc_flak (1 0 0) (-32 -32 0) (32 32 100)
2485 */
SP_misc_flak(gentity_t * self)2486 void SP_misc_flak( gentity_t *self ) {
2487
2488 if ( !self->harc ) {
2489 self->harc = 180;
2490 } else
2491 {
2492 if ( self->harc < 90 ) {
2493 self->harc = 115;
2494 }
2495 }
2496
2497 if ( !self->varc ) {
2498 self->varc = 90.0;
2499 }
2500
2501 if ( !self->health ) {
2502 self->health = 100;
2503 }
2504
2505 self->think = flak_spawn;
2506 self->nextthink = level.time + FRAMETIME;
2507
2508 snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" );
2509 }
2510
2511 /*QUAKED misc_spawner (.3 .7 .8) (-8 -8 -8) (8 8 8)
2512 use the pickup name
2513 when this entity gets used it will spawn an item
2514 that matches its spawnitem field
2515 e.i.
2516 spawnitem
2517 9mm
2518 */
2519
misc_spawner_think(gentity_t * ent)2520 void misc_spawner_think( gentity_t *ent ) {
2521
2522 gitem_t *item;
2523 gentity_t *drop = NULL;
2524
2525 item = BG_FindItem( ent->spawnitem );
2526
2527 drop = Drop_Item( ent, item, 0, qfalse );
2528
2529 if ( !drop ) {
2530 G_Printf( "-----> WARNING <-------\n" );
2531 G_Printf( "misc_spawner used at %s failed to drop!\n", vtos( ent->r.currentOrigin ) );
2532 }
2533
2534 }
2535
misc_spawner_use(gentity_t * ent,gentity_t * other,gentity_t * activator)2536 void misc_spawner_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
2537
2538 ent->think = misc_spawner_think;
2539 ent->nextthink = level.time + FRAMETIME;
2540
2541 // VectorCopy (other->r.currentOrigin, ent->r.currentOrigin);
2542 // VectorCopy (ent->r.currentOrigin, ent->s.pos.trBase);
2543
2544 // VectorCopy (other->r.currentAngles, ent->r.currentAngles);
2545
2546 trap_LinkEntity( ent );
2547 }
2548
SP_misc_spawner(gentity_t * ent)2549 void SP_misc_spawner( gentity_t *ent ) {
2550 if ( !ent->spawnitem ) {
2551 G_Printf( "-----> WARNING <-------\n" );
2552 G_Printf( "misc_spawner at loc %s has no spawnitem!\n", vtos( ent->s.origin ) );
2553 return;
2554 }
2555
2556 ent->use = misc_spawner_use;
2557
2558 trap_LinkEntity( ent );
2559
2560 }
2561
2562 // (SA) removed dead code 9/7/01
2563
firetrail_die(gentity_t * ent)2564 void firetrail_die( gentity_t *ent ) {
2565 G_FreeEntity( ent );
2566 }
2567
firetrail_use(gentity_t * ent,gentity_t * other,gentity_t * activator)2568 void firetrail_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
2569 if ( ent->s.eType == ET_RAMJET ) {
2570 ent->s.eType = ET_GENERAL;
2571 } else {
2572 ent->s.eType = ET_RAMJET;
2573 }
2574
2575 trap_LinkEntity( ent );
2576
2577 }
2578
2579 /*QUAKED misc_tagemitter (.4 .9 .7) (-16 -16 -16) (16 16 16)
2580 This entity must target the script mover it will attach to
2581 'use' to turn on/off
2582 alert entity call to kill it
2583 */
2584
tagemitter_die(gentity_t * ent)2585 void tagemitter_die( gentity_t *ent ) {
2586 G_FreeEntity( ent );
2587 }
2588
tagemitter_use(gentity_t * ent,gentity_t * other,gentity_t * activator)2589 void tagemitter_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
2590 if ( ent->s.eType == ET_EFFECT3 ) {
2591 ent->s.eType = ET_GENERAL;
2592 } else {
2593 ent->s.eType = ET_EFFECT3;
2594 }
2595
2596 trap_LinkEntity( ent );
2597
2598 }
2599
misc_tagemitter_finishspawning(gentity_t * ent)2600 void misc_tagemitter_finishspawning( gentity_t *ent ) {
2601 gentity_t *emitter, *parent;
2602
2603 parent = G_Find( NULL, FOFS( targetname ), ent->target );
2604 if ( !parent ) {
2605 G_Error( "misc_tagemitter: can't find parent script mover with targetname \"%s\"\n", ent->target );
2606 }
2607
2608 emitter = ent->target_ent;
2609
2610 emitter->classname = "misc_tagemitter";
2611 emitter->r.contents = 0;
2612 emitter->s.eType = ET_GENERAL;
2613 emitter->tagParent = parent;
2614
2615 emitter->use = tagemitter_use;
2616 emitter->AIScript_AlertEntity = tagemitter_die;
2617 emitter->targetname = ent->targetname;
2618 G_ProcessTagConnect( emitter, qtrue );
2619 // trap_LinkEntity( emitter );
2620
2621 ent->target_ent = NULL;
2622 }
2623
2624
SP_misc_tagemitter(gentity_t * ent)2625 void SP_misc_tagemitter( gentity_t *ent ) {
2626 char *tagName;
2627
2628 ent->think = misc_tagemitter_finishspawning; // so it can find it's target
2629 ent->nextthink = level.time + 100;
2630
2631 if ( !G_SpawnString( "tag", NULL, &tagName ) ) {
2632 G_Error( "misc_tagemitter: no 'tag' specified\n" );
2633 }
2634
2635 ent->target_ent = G_Spawn(); // spawn the emitter
2636 ent->target_ent->tagName = G_Alloc( strlen( tagName ) + 1 );
2637 Q_strncpyz( ent->target_ent->tagName, tagName, strlen( tagName ) + 1 );
2638
2639 ent->tagName = G_Alloc( strlen( tagName ) + 1 );
2640 Q_strncpyz( ent->tagName, tagName, strlen( tagName ) + 1 );
2641
2642 }
2643
2644
2645 /*QUAKED misc_firetrails (.4 .9 .7) (-16 -16 -16) (16 16 16)
2646 This entity must target the plane its going to be attached to
2647
2648 its use function will turn the fire stream effect on and off
2649
2650 an alert entity call will kill it
2651 */
2652
misc_firetrails_finishspawning(gentity_t * ent)2653 void misc_firetrails_finishspawning( gentity_t *ent ) {
2654 gentity_t *left, *right, *airplane;
2655
2656 airplane = G_Find( NULL, FOFS( targetname ), ent->target );
2657 if ( !airplane ) {
2658 G_Error( "can't find airplane with targetname \"%s\" for firetrails", ent->target );
2659 }
2660
2661 // left fire trail
2662 left = G_Spawn();
2663 left->classname = "left_firetrail";
2664 left->r.contents = 0;
2665 left->s.eType = ET_RAMJET;
2666 left->s.modelindex = G_ModelIndex( "models/ammo/rocket/rocket.md3" );
2667 left->tagParent = airplane;
2668 left->tagName = "tag_engine1"; // tag to connect to
2669 left->use = firetrail_use;
2670 left->AIScript_AlertEntity = firetrail_die;
2671 left->targetname = ent->targetname;
2672 G_ProcessTagConnect( left, qtrue );
2673 trap_LinkEntity( left );
2674
2675 // right fire trail
2676 right = G_Spawn();
2677 right->classname = "right_firetrail";
2678 right->r.contents = 0;
2679 right->s.eType = ET_RAMJET;
2680 right->s.modelindex = G_ModelIndex( "models/ammo/rocket/rocket.md3" );
2681 right->tagParent = airplane;
2682 right->tagName = "tag_engine2"; // tag to connect to
2683 right->use = firetrail_use;
2684 right->AIScript_AlertEntity = firetrail_die;
2685 right->targetname = ent->targetname;
2686 G_ProcessTagConnect( right, qtrue );
2687 trap_LinkEntity( right );
2688
2689 }
2690
SP_misc_firetrails(gentity_t * ent)2691 void SP_misc_firetrails( gentity_t *ent ) {
2692 ent->think = misc_firetrails_finishspawning;
2693 ent->nextthink = level.time + 100;
2694
2695 }
2696