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