1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 // g_misc.c
25 
26 #include "g_local.h"
27 #include "g_functions.h"
28 #include "g_nav.h"
29 #include "g_items.h"
30 #include "../cgame/cg_local.h"
31 #include "b_local.h"
32 
33 extern gentity_t *G_FindDoorTrigger( gentity_t *door );
34 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
35 extern void SetMiscModelDefaults( gentity_t *ent, useFunc_t use_func, const char *material, int solid_mask,int animFlag,
36 									qboolean take_damage, qboolean damage_model);
37 
38 #define MAX_AMMO_GIVE 4
39 
40 
41 
42 /*QUAKED func_group (0 0 0) ?
43 Used to group brushes together just for editor convenience.  They are turned into normal brushes by the utilities.
44 
45 q3map_onlyvertexlighting 1	=	brush only gets vertex lighting (reduces bsp size!)
46 */
47 
48 
49 /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) LIGHT
50 Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
51 
52 LIGHT - If this info_null is only targeted by a non-switchable light (a light without a targetname), it does NOT spawn in at all and doesn't count towards the # of entities on the map, even at map spawn/load
53 */
SP_info_null(gentity_t * self)54 void SP_info_null( gentity_t *self ) {
55 	if ( (self->spawnflags&1) )
56 	{//only used as a light target, so bugger off
57 		G_FreeEntity( self );
58 		return;
59 	}
60 	//FIXME: store targetname and vector (origin) in a list for further reference... remove after 1st second of game?
61 	G_SetOrigin( self, self->s.origin );
62 	self->e_ThinkFunc = thinkF_G_FreeEntity;
63 	//Give other ents time to link
64 	self->nextthink = level.time + START_TIME_REMOVE_ENTS;
65 }
66 
67 
68 /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
69 Used as a positional target for in-game calculation, like jumppad targets.
70 target_position does the same thing
71 */
SP_info_notnull(gentity_t * self)72 void SP_info_notnull( gentity_t *self ){
73 	//FIXME: store in ref_tag system?
74 	G_SetOrigin( self, self->s.origin );
75 }
76 
77 
78 /*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
79 Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
80 "light" overrides the default 300 intensity.
81 Nonlinear checkbox gives inverse square falloff instead of linear
82 Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
83 Lights pointed at a target will be spotlights.
84 "radius" overrides the default 64 unit radius of a spotlight at the target point.
85 "fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
86 */
87 
88 /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence START_OFF
89 Non-displayed light.
90 "light" overrides the default 300 intensity. - affects size
91 a negative "light" will subtract the light's color
92 'Linear' checkbox gives linear falloff instead of inverse square
93 'noIncidence' checkbox makes lighting smoother
94 Lights pointed at a target will be spotlights.
95 "radius" overrides the default 64 unit radius of a spotlight at the target point.
96 "scale" multiplier for the light intensity - does not affect size (default 1)
97 		greater than 1 is brighter, between 0 and 1 is dimmer.
98 "color" sets the light's color
99 "targetname" to indicate a switchable light - NOTE that all lights with the same targetname will be grouped together and act as one light (ie: don't mix colors, styles or start_off flag)
100 "style" to specify a specify light style, even for switchable lights!
101 "style_off" light style to use when switched off (Only for switchable lights)
102 
103    1 FLICKER (first variety)
104    2 SLOW STRONG PULSE
105    3 CANDLE (first variety)
106    4 FAST STROBE
107    5 GENTLE PULSE 1
108    6 FLICKER (second variety)
109    7 CANDLE (second variety)
110    8 CANDLE (third variety)
111    9 SLOW STROBE (fourth variety)
112    10 FLUORESCENT FLICKER
113    11 SLOW PULSE NOT FADE TO BLACK
114    12 FAST PULSE FOR JEREMY
115    13 Test Blending
116 */
misc_lightstyle_set(gentity_t * ent)117 static void misc_lightstyle_set ( gentity_t *ent)
118 {
119 	const int mLightStyle = ent->count;
120 	const int mLightSwitchStyle = ent->bounceCount;
121 	const int mLightOffStyle = ent->fly_sound_debounce_time;
122 	if (!ent->misc_dlight_active)
123 	{	//turn off
124 		if (mLightOffStyle)	//i have a light style i'd like to use when off
125 		{
126 			char lightstyle[32];
127 			gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+0, lightstyle, 32);
128 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
129 
130 			gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+1, lightstyle, 32);
131 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
132 
133 			gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+2, lightstyle, 32);
134 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
135 		}else
136 		{
137 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "a");
138 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "a");
139 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "a");
140 		}
141 	}
142 	else
143 	{	//Turn myself on now
144 		if (mLightSwitchStyle)	//i have a light style i'd like to use when on
145 		{
146 			char lightstyle[32];
147 			gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+0, lightstyle, 32);
148 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
149 
150 			gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+1, lightstyle, 32);
151 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
152 
153 			gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+2, lightstyle, 32);
154 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
155 		}
156 		else
157 		{
158 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "z");
159 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "z");
160 			gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "z");
161 		}
162 	}
163 }
SP_light(gentity_t * self)164 void SP_light( gentity_t *self ) {
165 	if (!self->targetname )
166 	{//if i don't have a light style switch, the i go away
167 		G_FreeEntity( self );
168 		return;
169 	}
170 
171 	G_SpawnInt( "style", "0", &self->count );
172 	G_SpawnInt( "switch_style", "0", &self->bounceCount );
173 	G_SpawnInt( "style_off", "0", &self->fly_sound_debounce_time );
174 	G_SetOrigin( self, self->s.origin );
175 	gi.linkentity( self );
176 
177 	self->e_UseFunc = useF_misc_dlight_use;
178 	self->e_clThinkFunc = clThinkF_NULL;
179 
180 	self->s.eType = ET_GENERAL;
181 	self->misc_dlight_active = qfalse;
182 	self->svFlags |= SVF_NOCLIENT;
183 
184 	if ( !(self->spawnflags & 4) )
185 	{	//turn myself on now
186 		self->misc_dlight_active = qtrue;
187 	}
188 	misc_lightstyle_set (self);
189 }
190 
misc_dlight_use(gentity_t * ent,gentity_t * other,gentity_t * activator)191 void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
192 {
193 	G_ActivateBehavior(ent,BSET_USE);
194 
195 	ent->misc_dlight_active = (qboolean)!ent->misc_dlight_active;	//toggle
196 	misc_lightstyle_set (ent);
197 }
198 
199 
200 /*
201 =================================================================================
202 
203 TELEPORTERS
204 
205 =================================================================================
206 */
207 
TeleportPlayer(gentity_t * player,vec3_t origin,vec3_t angles)208 void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles )
209 {
210 	if ( player->NPC && ( player->NPC->aiFlags&NPCAI_FORM_TELE_NAV ) )
211 	{
212 		//My leader teleported, I was trying to catch up, take this off
213 		player->NPC->aiFlags &= ~NPCAI_FORM_TELE_NAV;
214 
215 	}
216 
217 	// unlink to make sure it can't possibly interfere with G_KillBox
218 	gi.unlinkentity (player);
219 
220 	VectorCopy ( origin, player->client->ps.origin );
221 	player->client->ps.origin[2] += 1;
222 	VectorCopy ( player->client->ps.origin, player->currentOrigin );
223 
224 	// spit the player out
225 	AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
226 	VectorScale( player->client->ps.velocity, 0, player->client->ps.velocity );
227 	//player->client->ps.pm_time = 160;		// hold time
228 	//player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
229 
230 	// toggle the teleport bit so the client knows to not lerp
231 	player->client->ps.eFlags ^= EF_TELEPORT_BIT;
232 
233 	// set angles
234 	SetClientViewAngle( player, angles );
235 
236 	// kill anything at the destination
237 	G_KillBox (player);
238 
239 	// save results of pmove
240 	PlayerStateToEntityState( &player->client->ps, &player->s );
241 
242 	gi.linkentity (player);
243 }
244 
TeleportMover(gentity_t * mover,vec3_t origin,vec3_t diffAngles,qboolean snapAngle)245 void TeleportMover( gentity_t *mover, vec3_t origin, vec3_t diffAngles, qboolean snapAngle )
246 {//FIXME: need an effect
247 	vec3_t		oldAngle, newAngle;
248 	float		speed;
249 
250 	// unlink to make sure it can't possibly interfere with G_KillBox
251 	gi.unlinkentity (mover);
252 
253 	//reposition it
254 	VectorCopy( origin, mover->s.pos.trBase );
255 	VectorCopy( origin, mover->currentOrigin );
256 
257 	//Maintain their previous speed, but adjusted for new direction
258 	if ( snapAngle )
259 	{//not a diffAngle, actually an absolute angle
260 		vec3_t	dir;
261 
262 		VectorCopy( diffAngles, newAngle );
263 		AngleVectors( newAngle, dir, NULL, NULL );
264 		VectorNormalize( dir );//necessary?
265 		speed = VectorLength( mover->s.pos.trDelta );
266 		VectorScale( dir, speed, mover->s.pos.trDelta );
267 		mover->s.pos.trTime = level.time;
268 
269 		VectorSubtract( newAngle, mover->s.apos.trBase, diffAngles );
270 		VectorCopy( newAngle, mover->s.apos.trBase );
271 	}
272 	else
273 	{
274 		speed = VectorNormalize( mover->s.pos.trDelta );
275 
276 		vectoangles( mover->s.pos.trDelta, oldAngle );
277 		VectorAdd( oldAngle, diffAngles, newAngle );
278 
279 		AngleVectors( newAngle, mover->s.pos.trDelta, NULL, NULL );
280 		VectorNormalize( mover->s.pos.trDelta );
281 
282 		VectorScale( mover->s.pos.trDelta, speed, mover->s.pos.trDelta );
283 		mover->s.pos.trTime = level.time;
284 
285 		//Maintain their previous angles, but adjusted to new orientation
286 		VectorAdd( mover->s.apos.trBase, diffAngles, mover->s.apos.trBase );
287 	}
288 
289 	//Maintain their previous anglespeed, but adjusted to new orientation
290 	speed = VectorNormalize( mover->s.apos.trDelta );
291 	VectorAdd( mover->s.apos.trDelta, diffAngles, mover->s.apos.trDelta );
292 	VectorNormalize( mover->s.apos.trDelta );
293 	VectorScale( mover->s.apos.trDelta, speed, mover->s.apos.trDelta );
294 
295 	mover->s.apos.trTime = level.time;
296 
297 	//Tell them it was teleported this move
298 	mover->s.eFlags |= EF_TELEPORT_BIT;
299 
300 	// kill anything at the destination
301 	//G_KillBox (mover);
302 	//FIXME: call touch func instead of killbox?
303 
304 	gi.linkentity (mover);
305 }
306 
teleporter_touch(gentity_t * self,gentity_t * other,trace_t * trace)307 void teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace)
308 {
309 	gentity_t		*dest;
310 
311 	if (!other->client)
312 		return;
313 	dest = 	G_PickTarget( self->target );
314 	if (!dest) {
315 		gi.Printf ("Couldn't find teleporter destination\n");
316 		return;
317 	}
318 
319 	TeleportPlayer( other, dest->s.origin, dest->s.angles );
320 }
321 
322 /*QUAK-D misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
323 Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
324 */
SP_misc_teleporter(gentity_t * ent)325 void SP_misc_teleporter (gentity_t *ent)
326 {
327 	gentity_t		*trig;
328 
329 	if (!ent->target)
330 	{
331 		gi.Printf ("teleporter without a target.\n");
332 		G_FreeEntity( ent );
333 		return;
334 	}
335 
336 	ent->s.modelindex = G_ModelIndex( "models/objects/dmspot.md3" );
337 	ent->s.clientNum = 1;
338 //	ent->s.loopSound = G_SoundIndex("sound/world/amb10.wav");
339 	ent->contents = CONTENTS_SOLID;
340 
341 	G_SetOrigin( ent, ent->s.origin );
342 
343 	VectorSet (ent->mins, -32, -32, -24);
344 	VectorSet (ent->maxs, 32, 32, -16);
345 	gi.linkentity (ent);
346 
347 	trig = G_Spawn ();
348 	trig->e_TouchFunc = touchF_teleporter_touch;
349 	trig->contents = CONTENTS_TRIGGER;
350 	trig->target = ent->target;
351 	trig->owner = ent;
352 	G_SetOrigin( trig, ent->s.origin );
353 	VectorSet (trig->mins, -8, -8, 8);
354 	VectorSet (trig->maxs, 8, 8, 24);
355 	gi.linkentity (trig);
356 
357 }
358 
359 /*QUAK-D misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) - - NODRAW
360 Point teleporters at these.
361 */
SP_misc_teleporter_dest(gentity_t * ent)362 void SP_misc_teleporter_dest( gentity_t *ent ) {
363 	if ( ent->spawnflags & 4 ){
364 		return;
365 	}
366 
367 	G_SetOrigin( ent, ent->s.origin );
368 
369 	gi.linkentity (ent);
370 }
371 
372 
373 //===========================================================
374 
375 /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) RMG SOLID
376 "model"		arbitrary .md3 or .ase file to display
377 "_frame"	"x" which frame from an animated md3
378 "modelscale"	"x" uniform scale
379 "modelscale_vec" "x y z" scale model in each axis
380 "_remap"	"from to" remap a shader in this model
381 turns into BSP triangles - not solid by default (click SOLID or use _clipmodel shader)
382 */
SP_misc_model(gentity_t * ent)383 void SP_misc_model( gentity_t *ent ) {
384 	G_FreeEntity( ent );
385 }
386 
387 /*QUAKED misc_model_static (1 0 0) (-16 -16 0) (16 16 16)
388 "model"		arbitrary .md3 file to display
389 "_frame"	"x" which frame from an animated md3
390 "modelscale"	"x" uniform scale
391 "modelscale_vec" "x y z" scale model in each axis
392 "zoffset"	units to offset vertical culling position by, can be
393 			negative or positive. This does not affect the actual
394 			position of the model, only the culling position. Use
395 			it for models with stupid origins that go below the
396 			ground and whatnot.
397 
398 loaded as a model in the renderer - does not take up precious bsp space!
399 */
400 extern void CG_CreateMiscEntFromGent(gentity_t *ent, const vec3_t scale, float zOff); //cg_main.cpp
SP_misc_model_static(gentity_t * ent)401 void SP_misc_model_static(gentity_t *ent)
402 {
403 	char		*value;
404 	float		temp;
405 	float		zOff;
406 	vec3_t		scale;
407 
408 	G_SpawnString("modelscale_vec", "1 1 1", &value);
409 	sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] );
410 
411 	G_SpawnFloat( "modelscale", "0", &temp);
412 	if (temp != 0.0f)
413 	{
414 		scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp;
415 	}
416 
417 	G_SpawnFloat( "zoffset", "0", &zOff);
418 
419 	if (!ent->model)
420 	{
421 		Com_Error( ERR_DROP,"misc_model_static at %s with out a MODEL!\n", vtos(ent->s.origin) );
422 	}
423 	//we can be horrible and cheat since this is SP!
424 	CG_CreateMiscEntFromGent(ent, scale, zOff);
425 	G_FreeEntity( ent );
426 }
427 
428 //===========================================================
429 
setCamera(gentity_t * ent)430 void setCamera ( gentity_t *ent )
431 {
432 	vec3_t		dir;
433 	gentity_t	*target = 0;
434 
435 	// frame holds the rotate speed
436 	if ( ent->owner->spawnflags & 1 )
437 	{
438 		ent->s.frame = 25;
439 	}
440 	else if ( ent->owner->spawnflags & 2 )
441 	{
442 		ent->s.frame = 75;
443 	}
444 
445 	// clientNum holds the rotate offset
446 	ent->s.clientNum = ent->owner->s.clientNum;
447 
448 	VectorCopy( ent->owner->s.origin, ent->s.origin2 );
449 
450 	// see if the portal_camera has a target
451 	if (ent->owner->target) {
452 		target = G_PickTarget( ent->owner->target );
453 	}
454 	if ( target )
455 	{
456 		VectorSubtract( target->s.origin, ent->owner->s.origin, dir );
457 		VectorNormalize( dir );
458 	}
459 	else
460 	{
461 		G_SetMovedir( ent->owner->s.angles, dir );
462 	}
463 
464 	ent->s.eventParm = DirToByte( dir );
465 }
466 
cycleCamera(gentity_t * self)467 void cycleCamera( gentity_t *self )
468 {
469 	self->owner = G_Find( self->owner, FOFS(targetname), self->target );
470 	if  ( self->owner == NULL )
471 	{
472 		//Uh oh! Not targeted at any ents!  Or reached end of list?  Which is it?
473 		//for now assume reached end of list and are cycling
474 		self->owner = G_Find( self->owner, FOFS(targetname), self->target );
475 		if  ( self->owner == NULL )
476 		{//still didn't find one
477 			gi.Printf( "Couldn't find target for misc_portal_surface\n" );
478 			G_FreeEntity( self );
479 			return;
480 		}
481 	}
482 	setCamera( self );
483 
484 	if ( self->e_ThinkFunc == thinkF_cycleCamera )
485 	{
486 		if ( self->owner->wait > 0 )
487 		{
488 			self->nextthink = level.time + self->owner->wait;
489 		}
490 		else
491 		{
492 			self->nextthink = level.time + self->wait;
493 		}
494 	}
495 }
496 
misc_portal_use(gentity_t * self,gentity_t * other,gentity_t * activator)497 void misc_portal_use( gentity_t *self, gentity_t *other, gentity_t *activator )
498 {
499 	cycleCamera( self );
500 }
501 
locateCamera(gentity_t * ent)502 void locateCamera( gentity_t *ent )
503 {//FIXME: make this fadeout with distance from misc_camera_portal
504 
505 	ent->owner = G_Find(NULL, FOFS(targetname), ent->target);
506 	if ( !ent->owner )
507 	{
508 		gi.Printf( "Couldn't find target for misc_portal_surface\n" );
509 		G_FreeEntity( ent );
510 		return;
511 	}
512 
513 	setCamera( ent );
514 
515 	if ( !ent->targetname )
516 	{//not targetted, so auto-cycle
517 		if ( G_Find(ent->owner, FOFS(targetname), ent->target) != NULL  )
518 		{//targeted at more than one thing
519 			ent->e_ThinkFunc = thinkF_cycleCamera;
520 			if ( ent->owner->wait > 0 )
521 			{
522 				ent->nextthink = level.time + ent->owner->wait;
523 			}
524 			else
525 			{
526 				ent->nextthink = level.time + ent->wait;
527 			}
528 		}
529 	}
530 }
531 
532 /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
533 The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
534 This must be within 64 world units of the surface!
535 
536 targetname - When used, cycles to the next misc_portal_camera it's targeted
537 wait - makes it auto-cycle between all cameras it's pointed at at intevervals of specified number of seconds.
538 
539   cameras will be cycled through in the order they were created on the map.
540 */
SP_misc_portal_surface(gentity_t * ent)541 void SP_misc_portal_surface(gentity_t *ent)
542 {
543 	VectorClear( ent->mins );
544 	VectorClear( ent->maxs );
545 	gi.linkentity (ent);
546 
547 	ent->svFlags = SVF_PORTAL;
548 	ent->s.eType = ET_PORTAL;
549 	ent->wait *= 1000;
550 
551 	if ( !ent->target )
552 	{//mirror?
553 		VectorCopy( ent->s.origin, ent->s.origin2 );
554 	}
555 	else
556 	{
557 		ent->e_ThinkFunc = thinkF_locateCamera;
558 		ent->nextthink = level.time + 100;
559 
560 		if ( ent->targetname )
561 		{
562 			ent->e_UseFunc = useF_misc_portal_use;
563 		}
564 	}
565 }
566 
567 /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate
568 The target for a misc_portal_surface.  You can set either angles or target another entity (NOT an info_null) to determine the direction of view.
569 "roll" an angle modifier to orient the camera around the target vector;
570 */
SP_misc_portal_camera(gentity_t * ent)571 void SP_misc_portal_camera(gentity_t *ent) {
572 	float	roll;
573 
574 	VectorClear( ent->mins );
575 	VectorClear( ent->maxs );
576 	gi.linkentity (ent);
577 
578 	G_SpawnFloat( "roll", "0", &roll );
579 
580 	ent->s.clientNum = roll/360.0 * 256;
581 	ent->wait *= 1000;
582 }
583 
584 void G_SubBSPSpawnEntitiesFromString(const char *entityString, vec3_t posOffset, vec3_t angOffset);
585 
586 /*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16)
587 "bspmodel"		arbitrary .bsp file to display
588 */
SP_misc_bsp(gentity_t * ent)589 void SP_misc_bsp(gentity_t *ent)
590 {
591 	char	temp[MAX_QPATH];
592 	char	*out;
593 	float	newAngle;
594 	int		tempint;
595 
596 	G_SpawnFloat( "angle", "0", &newAngle );
597 	if (newAngle != 0.0)
598 	{
599 		ent->s.angles[1] = newAngle;
600 	}
601 	// don't support rotation any other way
602 	ent->s.angles[0] = 0.0;
603 	ent->s.angles[2] = 0.0;
604 
605 	G_SpawnString("bspmodel", "", &out);
606 
607 	ent->s.eFlags = EF_PERMANENT;
608 
609 	// Mainly for debugging
610 	G_SpawnInt( "spacing", "0", &tempint);
611 	ent->s.time2 = tempint;
612 	G_SpawnInt( "flatten", "0", &tempint);
613 	ent->s.time = tempint;
614 
615 	Com_sprintf(temp, MAX_QPATH, "#%s", out);
616 	gi.SetBrushModel( ent, temp );  // SV_SetBrushModel -- sets mins and maxs
617 	G_BSPIndex(temp);
618 
619 	level.mNumBSPInstances++;
620 	Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances);
621 	VectorCopy(ent->s.origin, level.mOriginAdjust);
622 	level.mRotationAdjust = ent->s.angles[1];
623 	level.mTargetAdjust = temp;
624 	level.hasBspInstances = qtrue;
625 	level.mBSPInstanceDepth++;
626 
627 	VectorCopy( ent->s.origin, ent->s.pos.trBase );
628 	VectorCopy( ent->s.origin, ent->currentOrigin );
629 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
630 	VectorCopy( ent->s.angles, ent->currentAngles );
631 
632 	ent->s.eType = ET_MOVER;
633 
634 	gi.linkentity (ent);
635 
636 	const char *ents = gi.SetActiveSubBSP(ent->s.modelindex);
637 	if (ents)
638 	{
639 		G_SubBSPSpawnEntitiesFromString(ents, ent->s.origin, ent->s.angles);
640 	}
641 	gi.SetActiveSubBSP(-1);
642 
643 	level.mBSPInstanceDepth--;
644 }
645 
646 #define MAX_INSTANCE_TYPES		16
647 
648 void AddSpawnField(char *field, char *value);
649 
650 /*QUAKED terrain (1.0 1.0 1.0) ? NOVEHDMG
651 
652 NOVEHDMG - don't damage vehicles upon impact with this terrain
653 
654 Terrain entity
655 It will stretch to the full height of the brush
656 
657 numPatches - integer number of patches to split the terrain brush into (default 200)
658 terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8)
659 seed - integer seed for random terrain generation (default 0)
660 textureScale - float scale of texture (default 0.005)
661 heightmap - name of heightmap data image to use, located in heightmaps/xxx.png. (must be PNG format)
662 terrainDef - defines how the game textures the terrain (file is base/ext_data/rmg/xxx.terrain - default is grassyhills)
663 instanceDef - defines which bsp instances appear
664 miscentDef - defines which client models spawn on the terrain (file is base/ext_data/rmg/xxx.miscents)
665 densityMap - how dense the client models are packed
666 
667 */
SP_terrain(gentity_t * ent)668 void SP_terrain(gentity_t *ent)
669 {
670 	G_FreeEntity( ent );
671 }
672 
673 //rww - Called by skyportal entities. This will check through entities and flag them
674 //as portal ents if they are in the same pvs as a skyportal entity and pass
675 //a direct point trace check between origins. I really wanted to use an eFlag for
676 //flagging portal entities, but too many entities like to reset their eFlags.
677 //Note that this was not part of the original wolf sky portal stuff.
G_PortalifyEntities(gentity_t * ent)678 void G_PortalifyEntities(gentity_t *ent)
679 {
680 	int i = 0;
681 	gentity_t *scan = NULL;
682 
683 	while (i < MAX_GENTITIES)
684 	{
685 		scan = &g_entities[i];
686 
687 		if (scan && scan->inuse && scan->s.number != ent->s.number && gi.inPVS(ent->s.origin, scan->currentOrigin))
688 		{
689 			trace_t tr;
690 
691 			gi.trace(&tr, ent->s.origin, vec3_origin, vec3_origin, scan->currentOrigin, ent->s.number, CONTENTS_SOLID, G2_NOCOLLIDE, 0);
692 
693 			if (tr.fraction == 1.0 || (tr.entityNum == scan->s.number && tr.entityNum != ENTITYNUM_NONE && tr.entityNum != ENTITYNUM_WORLD))
694 			{
695 				scan->s.isPortalEnt = qtrue; //he's flagged now
696 			}
697 		}
698 
699 		i++;
700 	}
701 
702 	ent->e_ThinkFunc = thinkF_G_FreeEntity; //the portal entity is no longer needed because its information is stored in a config string.
703 	ent->nextthink = level.time;
704 }
705 
706 /*QUAKED misc_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16)
707 To have the portal sky fogged, enter any of the following values:
708 "fogcolor" (r g b) (values 0.0-1.0)
709 "fognear" distance from entity to start fogging
710 "fogfar" distance from entity that fog is opaque
711 rww - NOTE: fog doesn't work with these currently (at least not in this way).
712 Use a fog brush instead.
713 */
SP_misc_skyportal(gentity_t * ent)714 void SP_misc_skyportal (gentity_t *ent)
715 {
716 	vec3_t	fogv;	//----(SA)
717 	int		fogn;	//----(SA)
718 	int		fogf;	//----(SA)
719 	int		isfog = 0;	// (SA)
720 
721 	isfog += G_SpawnVector ("fogcolor", "0 0 0", fogv);
722 	isfog += G_SpawnInt ("fognear", "0", &fogn);
723 	isfog += G_SpawnInt ("fogfar", "300", &fogf);
724 
725 	gi.SetConfigstring( CS_SKYBOXORG, va("%.2f %.2f %.2f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) );
726 
727 	ent->e_ThinkFunc = thinkF_G_PortalifyEntities;
728 	ent->nextthink = level.time + 1050; //give it some time first so that all other entities are spawned.
729 }
730 
731 extern qboolean G_ClearViewEntity( gentity_t *ent );
732 extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
733 extern void SP_fx_runner( gentity_t *ent );
camera_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)734 void camera_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
735 {
736 	if ( player && player->client && player->client->ps.viewEntity == self->s.number )
737 	{
738 		G_UseTargets2( self, player, self->target4 );
739 		G_ClearViewEntity( player );
740 		G_Sound( player, self->soundPos2 );
741 	}
742 	G_UseTargets2( self, player, self->closetarget );
743 	//FIXME: explosion fx/sound
744 	//leave sparks at origin- where base's pole is still at?
745 	gentity_t *sparks = G_Spawn();
746 	if ( sparks )
747 	{
748 		sparks->fxFile = "sparks/spark";
749 		sparks->delay = 100;
750 		sparks->random = 500;
751 		sparks->s.angles[0] = 180;//point down
752 		VectorCopy( self->s.origin, sparks->s.origin );
753 		SP_fx_runner( sparks );
754 	}
755 
756 	//bye!
757 	self->takedamage = qfalse;
758 	self->contents = 0;
759 	self->s.eFlags |= EF_NODRAW;
760 	self->s.modelindex = 0;
761 }
762 
camera_use(gentity_t * self,gentity_t * other,gentity_t * activator)763 void camera_use( gentity_t *self, gentity_t *other, gentity_t *activator )
764 {
765 	if ( !activator || !activator->client || activator->s.number )
766 	{//really only usable by the player
767 		return;
768 	}
769 	self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms
770 
771 	// FIXME: I guess we are allowing them to switch to a dead camera.  Maybe we should conditionally do this though?
772 	if ( /*self->health <= 0 ||*/ (player && player->client && player->client->ps.viewEntity == self->s.number) )
773 	{//I'm already viewEntity, or I'm destroyed, find next
774 		gentity_t *next = NULL;
775 		if ( self->target2 != NULL )
776 		{
777 			next = G_Find( NULL, FOFS(targetname), self->target2 );
778 		}
779 		if ( next )
780 		{//found another one
781 			if ( !Q_stricmp( "misc_camera", next->classname ) )
782 			{//make sure it's another camera
783 				camera_use( next, other, activator );
784 			}
785 		}
786 		else //if ( self->health > 0 )
787 		{//I was the last (only?) one, clear out the viewentity
788 			G_UseTargets2( self, activator, self->target4 );
789 			G_ClearViewEntity( activator );
790 			G_Sound( activator, self->soundPos2 );
791 		}
792 	}
793 	else
794 	{//set me as view entity
795 		G_UseTargets2( self, activator, self->target3 );
796 		self->s.eFlags |= EF_NODRAW;
797 		self->s.modelindex = 0;
798 		G_SetViewEntity( activator, self );
799 		G_Sound( activator, self->soundPos1 );
800 	}
801 }
802 
camera_aim(gentity_t * self)803 void camera_aim( gentity_t *self )
804 {
805 	self->nextthink = level.time + FRAMETIME;
806 	if ( player && player->client && player->client->ps.viewEntity == self->s.number )
807 	{//I am the viewEntity
808 		if ( player->client->usercmd.forwardmove || player->client->usercmd.rightmove || player->client->usercmd.upmove )
809 		{//player wants to back out of camera
810 			G_UseTargets2( self, player, self->target4 );
811 			G_ClearViewEntity( player );
812 			G_Sound( player, self->soundPos2 );
813 			self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms
814 			if ( player->client->usercmd.upmove > 0 )
815 			{//stop player from doing anything for a half second after
816 				player->aimDebounceTime = level.time + 500;
817 			}
818 		}
819 		else if ( self->painDebounceTime < level.time )
820 		{//check for use button
821 			if ( (player->client->usercmd.buttons&BUTTON_USE) )
822 			{//player pressed use button, wants to cycle to next
823 				camera_use( self, player, player );
824 			}
825 		}
826 		else
827 		{//don't draw me when being looked through
828 			self->s.eFlags |= EF_NODRAW;
829 			self->s.modelindex = 0;
830 		}
831 	}
832 	else if ( self->health > 0 )
833 	{//still alive, can draw me again
834 		self->s.eFlags &= ~EF_NODRAW;
835 		self->s.modelindex = self->s.modelindex3;
836 	}
837 	//update my aim
838 	if ( self->target )
839 	{
840 		gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
841 		if ( targ )
842 		{
843 			vec3_t angles, dir;
844 			VectorSubtract( targ->currentOrigin, self->currentOrigin, dir );
845 			vectoangles( dir, angles );
846 			//FIXME: if a G2 model, do a bone override..???
847 			VectorCopy( self->currentAngles, self->s.apos.trBase );
848 
849 			for( int i = 0; i < 3; i++ )
850 			{
851 				angles[i] = AngleNormalize180( angles[i] );
852 				self->s.apos.trDelta[i] = AngleNormalize180( (angles[i]-self->currentAngles[i])*10 );
853 			}
854 			//VectorSubtract( angles, self->currentAngles, self->s.apos.trDelta );
855 			//VectorScale( self->s.apos.trDelta, 10, self->s.apos.trDelta );
856 			self->s.apos.trTime = level.time;
857 			self->s.apos.trDuration = FRAMETIME;
858 			VectorCopy( angles, self->currentAngles );
859 
860 			if ( DistanceSquared( self->currentAngles, self->lastAngles ) > 0.01f ) // if it moved at all, start a loop sound? not exactly the "bestest" solution
861 			{
862 				self->s.loopSound = G_SoundIndex( "sound/movers/objects/cameramove_lp2" );
863 			}
864 			else
865 			{
866 				self->s.loopSound = 0; // not moving so don't bother
867 			}
868 
869 			VectorCopy( self->currentAngles, self->lastAngles );
870 			//G_SetAngles( self, angles );
871 		}
872 	}
873 }
874 /*QUAKED misc_camera (0 0 1) (-8 -8 -12) (8 8 16) VULNERABLE
875 A model in the world that can be used by the player to look through it's viewpoint
876 
877 There will be a video overlay instead of the regular HUD and the FOV will be wider
878 
879 VULNERABLE - allow camera to be destroyed
880 
881 "target" - camera will remain pointed at this entity (if it's a train or some other moving object, it will keep following it)
882 "target2" - when player is in this camera and hits the use button, it will cycle to this next camera (if no target2, returns to normal view )
883 "target3" - thing to use when player enters this camera view
884 "target4" - thing to use when player leaves this camera view
885 "closetarget" - (sigh...) yet another target, fired this when it's destroyed
886 "wait"    - how long to wait between being used (default 0.5)
887 */
SP_misc_camera(gentity_t * self)888 void SP_misc_camera( gentity_t *self )
889 {
890 	G_SpawnFloat( "wait", "0.5", &self->wait );
891 
892 	//FIXME: spawn base, too
893 	gentity_t *base = G_Spawn();
894 	if ( base )
895 	{
896 		base->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam_base.md3" );
897 		VectorCopy( self->s.origin, base->s.origin );
898 		base->s.origin[2] += 16;
899 		G_SetOrigin( base, base->s.origin );
900 		G_SetAngles( base, self->s.angles );
901 		gi.linkentity( base );
902 	}
903 	self->s.modelindex3 = self->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam.md3" );
904 	self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" );
905 	self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" );
906 	G_SoundIndex( "sound/movers/objects/cameramove_lp2" );
907 
908 	G_SetOrigin( self, self->s.origin );
909 	G_SetAngles( self, self->s.angles );
910 	self->s.apos.trType = TR_LINEAR_STOP;//TR_INTERPOLATE;//
911 	self->alt_fire = qtrue;
912 	VectorSet( self->mins, -8, -8, -12 );
913 	VectorSet( self->maxs, 8, 8, 0 );
914 	self->contents = CONTENTS_SOLID;
915 	gi.linkentity( self );
916 
917 	self->fxID = G_EffectIndex( "sparks/spark" );
918 
919 	if ( self->spawnflags & 1 ) // VULNERABLE
920 	{
921 		self->takedamage = qtrue;
922 	}
923 
924 	self->health = 10;
925 	self->e_DieFunc = dieF_camera_die;
926 
927 	self->e_UseFunc = useF_camera_use;
928 
929 	self->e_ThinkFunc = thinkF_camera_aim;
930 	self->nextthink = level.time + START_TIME_LINK_ENTS;
931 }
932 /*
933 ======================================================================
934 
935   SHOOTERS
936 
937 ======================================================================
938 */
939 
Use_Shooter(gentity_t * ent,gentity_t * other,gentity_t * activator)940 void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator )
941 {
942 	G_ActivateBehavior(ent,BSET_USE);
943 }
944 
InitShooter(gentity_t * ent,int weapon)945 void InitShooter( gentity_t *ent, int weapon ) {
946 	ent->e_UseFunc = useF_Use_Shooter;
947 	ent->s.weapon = weapon;
948 
949 	RegisterItem( FindItemForWeapon( (weapon_t) weapon ) );
950 
951 	G_SetMovedir( ent->s.angles, ent->movedir );
952 
953 	if ( !ent->random ) {
954 		ent->random = 1.0;
955 	}
956 	ent->random = sin( M_PI * ent->random / 180 );
957 	// target might be a moving object, so we can't set movedir for it
958 	if ( ent->target ) {
959 		G_SetEnemy(ent, G_PickTarget( ent->target ));
960 	}
961 	gi.linkentity( ent );
962 }
963 
964 /*QUAK-ED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
965 Fires at either the target or the current direction.
966 "random" the number of degrees of deviance from the taget. (1.0 default)
967 */
SP_shooter_rocket(gentity_t * ent)968 void SP_shooter_rocket( gentity_t *ent )
969 {
970 //	InitShooter( ent, WP_TETRION_DISRUPTOR );
971 }
972 
973 /*QUAK-ED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16)
974 Fires at either the target or the current direction.
975 "random" is the number of degrees of deviance from the taget. (1.0 default)
976 */
SP_shooter_plasma(gentity_t * ent)977 void SP_shooter_plasma( gentity_t *ent )
978 {
979 	InitShooter( ent, WP_BRYAR_PISTOL);
980 }
981 
982 /*QUAK-ED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
983 Fires at either the target or the current direction.
984 "random" is the number of degrees of deviance from the taget. (1.0 default)
985 */
SP_shooter_grenade(gentity_t * ent)986 void SP_shooter_grenade( gentity_t *ent )
987 {
988 //	InitShooter( ent, WP_GRENADE_LAUNCHER);
989 }
990 
991 
992 /*QUAKED object_cargo_barrel1 (1 0 0) (-16 -16 -16) (16 16 29) SMALLER KLINGON NO_SMOKE POWDERKEG
993 Cargo Barrel
994 if given a targetname, using it makes it explode
995 
996 SMALLER - (-8, -8, -16) (8, 8, 8)
997 KLINGON - klingon style barrel
998 NO_SMOKE - will not leave lingering smoke cloud when killed
999 POWDERKEG - wooden explosive barrel
1000 
1001 health		 default = 20
1002 splashDamage default = 100
1003 splashRadius default = 200
1004 */
SP_object_cargo_barrel1(gentity_t * ent)1005 void SP_object_cargo_barrel1(gentity_t *ent)
1006 {
1007 	if(ent->spawnflags & 8)
1008 	{
1009 		ent->s.modelindex = G_ModelIndex( "/models/mapobjects/cargo/barrel_wood2.md3" );
1010 //		ent->sounds = G_SoundIndex("sound/weapons/explosions/explode3.wav");
1011 	}
1012 	else if(ent->spawnflags & 2)
1013 	{
1014 		ent->s.modelindex = G_ModelIndex( "/models/mapobjects/scavenger/k_barrel.md3" );
1015 //		ent->sounds = G_SoundIndex("sound/weapons/explosions/explode4.wav");
1016 	}
1017 	else
1018 	{
1019 		ent->s.modelindex = G_ModelIndex( va("/models/mapobjects/cargo/barrel%i.md3", Q_irand( 0, 2 )) );
1020 //		ent->sounds = G_SoundIndex("sound/weapons/explosions/explode1.wav");
1021 	}
1022 
1023 	ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE;
1024 
1025 	if ( ent->spawnflags & 1 )
1026 	{
1027 		VectorSet (ent->mins, -8, -8, -16);
1028 		VectorSet (ent->maxs, 8, 8, 8);
1029 	}
1030 	else
1031 	{
1032 		VectorSet (ent->mins, -16, -16, -16);
1033 		VectorSet (ent->maxs, 16, 16, 29);
1034 	}
1035 
1036 	G_SetOrigin( ent, ent->s.origin );
1037 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
1038 
1039 	if(!ent->health)
1040 		ent->health = 20;
1041 
1042 	if(!ent->splashDamage)
1043 		ent->splashDamage = 100;
1044 
1045 	if(!ent->splashRadius)
1046 		ent->splashRadius = 200;
1047 
1048 	ent->takedamage = qtrue;
1049 
1050 	ent->e_DieFunc = dieF_ExplodeDeath_Wait;
1051 
1052 	if(ent->targetname)
1053 		ent->e_UseFunc = useF_GoExplodeDeath;
1054 
1055 	gi.linkentity (ent);
1056 }
1057 
1058 
1059 /*QUAKED misc_dlight (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) STARTOFF FADEON FADEOFF PULSE
1060 Dynamic light, toggles on and off when used
1061 
1062 STARTOFF - Starts off
1063 FADEON - Fades from 0 Radius to start Radius
1064 FADEOFF - Fades from current Radius to 0 Radius before turning off
1065 PULSE - This flag must be checked if you want it to fade/switch between start and final RGBA, otherwise it will just sit at startRGBA
1066 
1067 ownername - Will display the light at the origin of the entity with this targetname
1068 
1069 startRGBA - Red Green Blue Radius to start with - This MUST be set or your light won't do anything
1070 
1071 These next values are used only if you want to fade/switch between 2 values (PULSE flag on)
1072 finalRGBA - Red Green Blue Radius to end with
1073 speed - how long to take to fade from start to final and final to start.  Also how long to fade on and off if appropriate flags are checked (seconds)
1074 finaltime - how long to hold at final (seconds)
1075 starttime - how long to hold at start (seconds)
1076 
1077 TODO: Add random to speed/radius?
1078 */
SP_misc_dlight(gentity_t * ent)1079 void SP_misc_dlight(gentity_t *ent)
1080 {
1081 	G_SetOrigin( ent, ent->s.origin );
1082 	gi.linkentity( ent );
1083 
1084 	ent->speed *= 1000;
1085 	ent->wait *= 1000;
1086 	ent->radius *= 1000;
1087 
1088 	//FIXME: attach self to a train or something?
1089 	ent->e_UseFunc = useF_misc_dlight_use;
1090 
1091 	ent->misc_dlight_active = qfalse;
1092 	ent->e_clThinkFunc = clThinkF_NULL;
1093 
1094 	ent->s.eType = ET_GENERAL;
1095 	//Delay first think so we can find owner
1096 	if ( ent->ownername )
1097 	{
1098 		ent->e_ThinkFunc = thinkF_misc_dlight_think;
1099 		ent->nextthink = level.time + START_TIME_LINK_ENTS;
1100 	}
1101 
1102 	if ( !(ent->spawnflags & 1) )
1103 	{//Turn myself on now
1104 		GEntity_UseFunc( ent, ent, ent );
1105 	}
1106 }
1107 
misc_dlight_use_old(gentity_t * ent,gentity_t * other,gentity_t * activator)1108 void misc_dlight_use_old ( gentity_t *ent, gentity_t *other, gentity_t *activator )
1109 {
1110 	G_ActivateBehavior(ent,BSET_USE);
1111 
1112 	if ( ent->misc_dlight_active )
1113 	{//We're on, turn off
1114 		if ( ent->spawnflags & 4 )
1115 		{//fade off
1116 			ent->pushDebounceTime = 3;
1117 		}
1118 		else
1119 		{
1120 			ent->misc_dlight_active = qfalse;
1121 			ent->e_clThinkFunc = clThinkF_NULL;
1122 
1123 			ent->s.eType = ET_GENERAL;
1124 			ent->svFlags &= ~SVF_BROADCAST;
1125 		}
1126 	}
1127 	else
1128 	{
1129 		//Start at start regardless of when we were turned off
1130 		if ( ent->spawnflags & 4 )
1131 		{//fade on
1132 			ent->pushDebounceTime = 2;
1133 		}
1134 		else
1135 		{//Just start on
1136 			ent->pushDebounceTime = 0;
1137 		}
1138 		ent->painDebounceTime = level.time;
1139 
1140 		ent->misc_dlight_active = qtrue;
1141 
1142 		ent->e_ThinkFunc = thinkF_misc_dlight_think;
1143 		ent->nextthink = level.time + FRAMETIME;
1144 
1145 		ent->e_clThinkFunc = clThinkF_CG_DLightThink;
1146 
1147 		ent->s.eType = ET_THINKER;
1148 		ent->svFlags |= SVF_BROADCAST;// Broadcast to all clients
1149 	}
1150 }
1151 
misc_dlight_think(gentity_t * ent)1152 void misc_dlight_think ( gentity_t *ent )
1153 {
1154 	//Stay Attached to owner
1155 	if ( ent->owner )
1156 	{
1157 		G_SetOrigin( ent, ent->owner->currentOrigin );
1158 		gi.linkentity( ent );
1159 	}
1160 	else if ( ent->ownername )
1161 	{
1162 		ent->owner = G_Find( NULL, FOFS(targetname), ent->ownername );
1163 		ent->ownername = NULL;
1164 	}
1165 	ent->nextthink = level.time + FRAMETIME;
1166 }
1167 
1168 
station_pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,const vec3_t point,int damage,int mod,int hitLoc)1169 void station_pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
1170 {
1171 //	self->s.modelindex = G_ModelIndex("/models/mapobjects/stasis/plugin2_in.md3");
1172 //	self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1173 //	self->s.eFlags |= EF_ANIM_ONCE;
1174 //	gi.linkentity (self);
1175 	self->s.modelindex = self->s.modelindex2;
1176 	gi.linkentity (self);
1177 }
1178 
1179 // --------------------------------------------------------------------
1180 //
1181 //   HEALTH/ARMOR plugin functions
1182 //
1183 // --------------------------------------------------------------------
1184 
1185 void health_use( gentity_t *self, gentity_t *other, gentity_t *activator);
1186 int ITM_AddArmor (gentity_t *ent, int count);
1187 int ITM_AddHealth (gentity_t *ent, int count);
1188 
health_shutdown(gentity_t * self)1189 void health_shutdown( gentity_t *self )
1190 {
1191 	if (!(self->s.eFlags & EF_ANIM_ONCE))
1192 	{
1193 		self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1194 		self->s.eFlags |= EF_ANIM_ONCE;
1195 
1196 		// Switch to and animate its used up model.
1197 		if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2.md3"))
1198 		{
1199 			self->s.modelindex = self->s.modelindex2;
1200 		}
1201 		else if (!Q_stricmp(self->model,"models/mapobjects/borg/plugin2.md3"))
1202 		{
1203 			self->s.modelindex = self->s.modelindex2;
1204 		}
1205 		else if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2_floor.md3"))
1206 		{
1207 			self->s.modelindex = self->s.modelindex2;
1208 //			G_Sound(self, G_SoundIndex("sound/ambience/stasis/shrinkage1.wav") );
1209 		}
1210 		else if (!Q_stricmp(self->model,"models/mapobjects/forge/panels.md3"))
1211 		{
1212 			self->s.modelindex = self->s.modelindex2;
1213 		}
1214 
1215 		gi.linkentity (self);
1216 	}
1217 }
1218 
health_think(gentity_t * ent)1219 void health_think( gentity_t *ent )
1220 {
1221 	int dif;
1222 
1223 	// He's dead, Jim. Don't give him health
1224 	if (ent->enemy->health<1)
1225 	{
1226 		ent->count = 0;
1227 		ent->e_ThinkFunc = thinkF_NULL;
1228 	}
1229 
1230 	// Still has power to give
1231 	if (ent->count > 0)
1232 	{
1233 		// For every 3 points of health, you get 1 point of armor
1234 		// BUT!!! after health is filled up, you get the full energy going to armor
1235 
1236 		dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] - ent->enemy->health;
1237 
1238 		if (dif > 3 )
1239 		{
1240 			dif= 3;
1241 		}
1242 		else if (dif < 0)
1243 		{
1244 			dif= 0;
1245 		}
1246 
1247 		if (dif > ent->count)	// Can't give more than count
1248 		{
1249 			dif = ent->count;
1250 		}
1251 
1252 		if ((ITM_AddHealth (ent->enemy,dif)) && (dif>0))
1253 		{
1254 			ITM_AddArmor (ent->enemy,1);	// 1 armor for every 3 health
1255 
1256 			ent->count-=dif;
1257 			ent->nextthink = level.time + 10;
1258 		}
1259 		else	// User has taken all health he can hold, see about giving it all to armor
1260 		{
1261 			dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] -
1262 				ent->enemy->client->ps.stats[STAT_ARMOR];
1263 
1264 			if (dif > 3)
1265 			{
1266 				dif = 3;
1267 			}
1268 			else if (dif < 0)
1269 			{
1270 				dif= 0;
1271 			}
1272 
1273 			if (ent->count < dif)	// Can't give more than count
1274 			{
1275 				dif = ent->count;
1276 			}
1277 
1278 			if ((!ITM_AddArmor(ent->enemy,dif)) || (dif<=0))
1279 			{
1280 				ent->e_UseFunc = useF_health_use;
1281 				ent->e_ThinkFunc = thinkF_NULL;
1282 			}
1283 			else
1284 			{
1285 				ent->count-=dif;
1286 				ent->nextthink = level.time + 10;
1287 			}
1288 		}
1289 	}
1290 
1291 	if (ent->count < 1)
1292 	{
1293 		health_shutdown(ent);
1294 	}
1295 }
1296 
misc_model_useup(gentity_t * self,gentity_t * other,gentity_t * activator)1297 void misc_model_useup( gentity_t *self, gentity_t *other, gentity_t *activator)
1298 {
1299 	G_ActivateBehavior(self,BSET_USE);
1300 
1301 	self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1302 	self->s.eFlags |= EF_ANIM_ONCE;
1303 
1304 	// Switch to and animate its used up model.
1305 	self->s.modelindex = self->s.modelindex2;
1306 
1307 	gi.linkentity (self);
1308 
1309 	// Use target when used
1310 	if (self->spawnflags & 8)
1311 	{
1312 		G_UseTargets( self, activator );
1313 	}
1314 
1315 	self->e_UseFunc = useF_NULL;
1316 	self->e_ThinkFunc = thinkF_NULL;
1317 	self->nextthink = -1;
1318 }
1319 
health_use(gentity_t * self,gentity_t * other,gentity_t * activator)1320 void health_use( gentity_t *self, gentity_t *other, gentity_t *activator)
1321 {//FIXME: Heal entire team?  Or only those that are undying...?
1322 	int dif;
1323 	int dif2;
1324 	int hold;
1325 
1326 	G_ActivateBehavior(self,BSET_USE);
1327 
1328 	if (self->e_ThinkFunc != thinkF_NULL)
1329 	{
1330 		self->e_ThinkFunc = thinkF_NULL;
1331 	}
1332 	else
1333 	{
1334 
1335 		if (other->client)
1336 		{
1337 			// He's dead, Jim. Don't give him health
1338 			if (other->client->ps.stats[STAT_HEALTH]<1)
1339 			{
1340 				dif = 1;
1341 				self->count = 0;
1342 			}
1343 			else
1344 			{	// Health
1345 				dif = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_HEALTH];
1346 				// Armor
1347 				dif2 = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_ARMOR];
1348 				hold = (dif2 - dif);
1349 				// For every 3 points of health, you get 1 point of armor
1350 				// BUT!!! after health is filled up, you get the full energy going to armor
1351 				if (hold>0)	// Need more armor than health
1352 				{
1353 					// Calculate total amount of station energy needed.
1354 
1355 					hold = dif / 3;	//	For every 3 points of health, you get 1 point of armor
1356 					dif2 -= hold;
1357 					dif2 += dif;
1358 
1359 					dif = dif2;
1360 				}
1361 			}
1362 		}
1363 		else
1364 		{	// Being triggered to be used up
1365 			dif = 1;
1366 			self->count = 0;
1367 		}
1368 
1369 		// Does player already have full health and full armor?
1370 		if (dif > 0)
1371 		{
1372 //			G_Sound(self, G_SoundIndex("sound/player/suithealth.wav") );
1373 
1374 			if ((dif >= self->count) || (self->count<1)) // use it all up?
1375 			{
1376 				health_shutdown(self);
1377 			}
1378 			// Use target when used
1379 			if (self->spawnflags & 8)
1380 			{
1381 				G_UseTargets( self, activator );
1382 			}
1383 
1384 			self->e_UseFunc = useF_NULL;
1385 			self->enemy = other;
1386 			self->e_ThinkFunc = thinkF_health_think;
1387 			self->nextthink = level.time + 50;
1388 		}
1389 		else
1390 		{
1391 //			G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
1392 		}
1393 	}
1394 }
1395 
1396 // --------------------------------------------------------------------
1397 //
1398 //   AMMO plugin functions
1399 //
1400 // --------------------------------------------------------------------
1401 void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator);
1402 int Add_Ammo2 (gentity_t *ent, int ammoType, int count);
1403 
ammo_shutdown(gentity_t * self)1404 void ammo_shutdown( gentity_t *self )
1405 {
1406 	if (!(self->s.eFlags & EF_ANIM_ONCE))
1407 	{
1408 		self->s.eFlags &= ~ EF_ANIM_ALLFAST;
1409 		self->s.eFlags |= EF_ANIM_ONCE;
1410 
1411 		gi.linkentity (self);
1412 	}
1413 }
ammo_think(gentity_t * ent)1414 void ammo_think( gentity_t *ent )
1415 {
1416 	int dif;
1417 
1418 	// Still has ammo to give
1419 	if (ent->count > 0 && ent->enemy )
1420 	{
1421 		dif = ammoData[AMMO_BLASTER].max  - ent->enemy->client->ps.ammo[AMMO_BLASTER];
1422 
1423 		if (dif > 2 )
1424 		{
1425 			dif= 2;
1426 		}
1427 		else if (dif < 0)
1428 		{
1429 			dif= 0;
1430 		}
1431 
1432 		if (ent->count < dif)	// Can't give more than count
1433 		{
1434 			dif = ent->count;
1435 		}
1436 
1437 		// Give player ammo
1438 		if (Add_Ammo2(ent->enemy,AMMO_BLASTER,dif) && (dif!=0))
1439 		{
1440 			ent->count-=dif;
1441 			ent->nextthink = level.time + 10;
1442 		}
1443 		else	// User has taken all ammo he can hold
1444 		{
1445 			ent->e_UseFunc = useF_ammo_use;
1446 			ent->e_ThinkFunc = thinkF_NULL;
1447 		}
1448 	}
1449 
1450 	if (ent->count < 1)
1451 	{
1452 		ammo_shutdown(ent);
1453 	}
1454 }
1455 
1456 //------------------------------------------------------------
ammo_use(gentity_t * self,gentity_t * other,gentity_t * activator)1457 void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator)
1458 {
1459 	int dif;
1460 
1461 	G_ActivateBehavior(self,BSET_USE);
1462 
1463 	if (self->e_ThinkFunc != thinkF_NULL)
1464 	{
1465 		if (self->e_UseFunc != useF_NULL)
1466 		{
1467 			self->e_ThinkFunc = thinkF_NULL;
1468 		}
1469 	}
1470 	else
1471 	{
1472 		if (other->client)
1473 		{
1474 			dif = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER];
1475 		}
1476 		else
1477 		{	// Being triggered to be used up
1478 			dif = 1;
1479 			self->count = 0;
1480 		}
1481 
1482 		// Does player already have full ammo?
1483 		if (dif > 0)
1484 		{
1485 //			G_Sound(self, G_SoundIndex("sound/player/suitenergy.wav") );
1486 
1487 			if ((dif >= self->count) || (self->count<1)) // use it all up?
1488 			{
1489 				ammo_shutdown(self);
1490 			}
1491 		}
1492 		else
1493 		{
1494 //			G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
1495 		}
1496 		// Use target when used
1497 		if (self->spawnflags & 8)
1498 		{
1499 			G_UseTargets( self, activator );
1500 		}
1501 
1502 		self->e_UseFunc = useF_NULL;
1503 		G_SetEnemy( self, other );
1504 		self->e_ThinkFunc = thinkF_ammo_think;
1505 		self->nextthink = level.time + 50;
1506 	}
1507 }
1508 
1509 //------------------------------------------------------------
mega_ammo_use(gentity_t * self,gentity_t * other,gentity_t * activator)1510 void mega_ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator )
1511 {
1512 	G_ActivateBehavior( self, BSET_USE );
1513 
1514 	// Use target when used
1515 	G_UseTargets( self, activator );
1516 
1517 	// first use, adjust the max ammo a person can hold for each type of ammo
1518 	ammoData[AMMO_BLASTER].max	= 999;
1519 	ammoData[AMMO_POWERCELL].max		= 999;
1520 
1521 	// Set up our count with whatever the max difference will be
1522 	if ( other->client->ps.ammo[AMMO_POWERCELL] > other->client->ps.ammo[AMMO_BLASTER] )
1523 		self->count = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER];
1524 	else
1525 		self->count = ammoData[AMMO_POWERCELL].max - other->client->ps.ammo[AMMO_POWERCELL];
1526 
1527 //	G_Sound( self, G_SoundIndex("sound/player/superenergy.wav") );
1528 
1529 	// Clear our usefunc, then think until our ammo is full
1530 	self->e_UseFunc = useF_NULL;
1531 	G_SetEnemy( self, other );
1532 	self->e_ThinkFunc = thinkF_mega_ammo_think;
1533 	self->nextthink = level.time + 50;
1534 
1535 	self->s.frame = 0;
1536 	self->s.eFlags |= EF_ANIM_ONCE;
1537 }
1538 
1539 //------------------------------------------------------------
mega_ammo_think(gentity_t * self)1540 void mega_ammo_think( gentity_t *self )
1541 {
1542 	int	ammo_add = 5;
1543 
1544 	// If the middle model is done animating, and we haven't switched to the last model yet...
1545 	//		chuck up the last model.
1546 
1547 	if (!Q_stricmp(self->model,"models/mapobjects/forge/power_up_boss.md3"))	// Because the normal forge_ammo model can use this too
1548 	{
1549 		if ( self->s.frame > 16 && self->s.modelindex != self->s.modelindex2 )
1550 			self->s.modelindex = self->s.modelindex2;
1551 	}
1552 
1553 	if ( self->enemy && self->count > 0 )
1554 	{
1555 		// Add an equal ammount of ammo to each type
1556 		self->enemy->client->ps.ammo[AMMO_BLASTER]		+= ammo_add;
1557 		self->enemy->client->ps.ammo[AMMO_POWERCELL]	+= ammo_add;
1558 
1559 		// Now cap to prevent overflows
1560 		if ( self->enemy->client->ps.ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max )
1561 			self->enemy->client->ps.ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max;
1562 
1563 		if ( self->enemy->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
1564 			self->enemy->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
1565 
1566 		// Decrement the count given counter
1567 		self->count -= ammo_add;
1568 
1569 		// If we've given all we should, prevent giving any more, even if they player is no longer full
1570 		if ( self->count <= 0 )
1571 		{
1572 			self->count = 0;
1573 			self->e_ThinkFunc = thinkF_NULL;
1574 			self->nextthink = -1;
1575 		}
1576 		else
1577 			self->nextthink = 20;
1578 	}
1579 }
1580 
1581 
1582 //------------------------------------------------------------
switch_models(gentity_t * self,gentity_t * other,gentity_t * activator)1583 void switch_models( gentity_t *self, gentity_t *other, gentity_t *activator )
1584 {
1585 	// FIXME: need a sound here!!
1586 	if ( self->s.modelindex2 )
1587 		self->s.modelindex = self->s.modelindex2;
1588 }
1589 
1590 //------------------------------------------------------------
touch_ammo_crystal_tigger(gentity_t * self,gentity_t * other,trace_t * trace)1591 void touch_ammo_crystal_tigger( gentity_t *self, gentity_t *other, trace_t *trace )
1592 {
1593 	if ( !other->client )
1594 		return;
1595 
1596 	// dead people can't pick things up
1597 	if ( other->health < 1 )
1598 		return;
1599 
1600 	// Only player can pick it up
1601 	if ( other->s.number != 0 )
1602 	{
1603 		return;
1604 	}
1605 
1606 	if ( other->client->ps.ammo[ AMMO_POWERCELL ] >= ammoData[AMMO_POWERCELL].max )
1607 	{
1608 		return;		// can't hold any more
1609 	}
1610 
1611 	// Add the ammo
1612 	other->client->ps.ammo[AMMO_POWERCELL] += self->owner->count;
1613 
1614 	if ( other->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
1615 	{
1616 		other->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
1617 	}
1618 
1619 	// Trigger once only
1620 	self->e_TouchFunc = touchF_NULL;
1621 
1622 	// swap the model to the version without the crystal and ditch the infostring
1623 	self->owner->s.modelindex = self->owner->s.modelindex2;
1624 
1625 	// play the normal pickup sound
1626 //	G_AddEvent( other, EV_ITEM_PICKUP, ITM_AMMO_CRYSTAL_BORG );
1627 
1628 	// fire item targets
1629 	G_UseTargets( self->owner, other );
1630 }
1631 
1632 //------------------------------------------------------------
spawn_ammo_crystal_trigger(gentity_t * ent)1633 void spawn_ammo_crystal_trigger( gentity_t *ent )
1634 {
1635 	gentity_t	*other;
1636 	vec3_t		mins, maxs;
1637 
1638 	// Set the base bounds
1639 	VectorCopy( ent->s.origin, mins );
1640 	VectorCopy( ent->s.origin, maxs );
1641 
1642 	// Now add an area of influence around the thing
1643 	for ( int i = 0; i < 3; i++ )
1644 	{
1645 		maxs[i] += 48;
1646 		mins[i] -= 48;
1647 	}
1648 
1649 	// create a trigger with this size
1650 	other = G_Spawn( );
1651 
1652 	VectorCopy( mins, other->mins );
1653 	VectorCopy( maxs, other->maxs );
1654 
1655 	// set up the other bits that the engine needs to know
1656 	other->owner = ent;
1657 	other->contents = CONTENTS_TRIGGER;
1658 	other->e_TouchFunc = touchF_touch_ammo_crystal_tigger;
1659 
1660 	gi.linkentity( other );
1661 }
1662 
misc_replicator_item_remove(gentity_t * self,gentity_t * other,gentity_t * activator)1663 void misc_replicator_item_remove ( gentity_t *self, gentity_t *other, gentity_t *activator )
1664 {
1665 	self->s.eFlags |= EF_NODRAW;
1666 	//self->contents = 0;
1667 	self->s.modelindex = 0;
1668 	self->e_UseFunc = useF_misc_replicator_item_spawn;
1669 	//FIXME: pickup sound?
1670 	if ( activator->client )
1671 	{
1672 		activator->health += 5;
1673 		if ( activator->health > activator->client->ps.stats[STAT_MAX_HEALTH] )	// Past max health
1674 		{
1675 			activator->health = activator->client->ps.stats[STAT_MAX_HEALTH];
1676 		}
1677 	}
1678 }
1679 
misc_replicator_item_finish_spawn(gentity_t * self)1680 void misc_replicator_item_finish_spawn( gentity_t *self )
1681 {
1682 	//self->contents = CONTENTS_ITEM;
1683 	//FIXME: blinks out for a couple frames when transporter effect is done?
1684 	self->e_UseFunc = useF_misc_replicator_item_remove;
1685 }
1686 
misc_replicator_item_spawn(gentity_t * self,gentity_t * other,gentity_t * activator)1687 void misc_replicator_item_spawn ( gentity_t *self, gentity_t *other, gentity_t *activator )
1688 {
1689 	switch ( Q_irand( 1, self->count ) )
1690 	{
1691 	case 1:
1692 		self->s.modelindex = self->bounceCount;
1693 		break;
1694 	case 2:
1695 		self->s.modelindex = self->fly_sound_debounce_time;
1696 		break;
1697 	case 3:
1698 		self->s.modelindex = self->painDebounceTime;
1699 		break;
1700 	case 4:
1701 		self->s.modelindex = self->disconnectDebounceTime;
1702 		break;
1703 	case 5:
1704 		self->s.modelindex = self->attackDebounceTime;
1705 		break;
1706 	case 6://max
1707 		self->s.modelindex = self->pushDebounceTime;
1708 		break;
1709 	}
1710 	self->s.eFlags &= ~EF_NODRAW;
1711 	self->e_ThinkFunc = thinkF_misc_replicator_item_finish_spawn;
1712 	self->nextthink = level.time + 4000;//shorter?
1713 	self->e_UseFunc = useF_NULL;
1714 
1715 	gentity_t *tent = G_TempEntity( self->currentOrigin, EV_REPLICATOR );
1716 	tent->owner = self;
1717 }
1718 
1719 /*QUAK-ED misc_replicator_item (0.2 0.8 0.2) (-4 -4 0) (4 4 8)
1720 When used. this will "spawn" an entity with a random model from the ones provided below...
1721 
1722 Using it again removes the item as if it were picked up.
1723 
1724 model  - first random model key
1725 model2  - second random model key
1726 model3  - third random model key
1727 model4  - fourth random model key
1728 model5  - fifth random model key
1729 model6  - sixth random model key
1730 
1731 NOTE: do not skip one of these model names, start with the lowest and fill in each next highest one with a value.  A gap will cause the item to not work correctly.
1732 
1733 NOTE: if you use an invalid model, it will still try to use it and show the NULL axis model (or nothing at all)
1734 
1735 targetname - how you refer to it for using it
1736 */
SP_misc_replicator_item(gentity_t * self)1737 void SP_misc_replicator_item ( gentity_t *self )
1738 {
1739 	if ( self->model )
1740 	{
1741 		self->bounceCount = G_ModelIndex( self->model );
1742 		self->count++;
1743 		if ( self->model2 )
1744 		{
1745 			self->fly_sound_debounce_time = G_ModelIndex( self->model2 );
1746 			self->count++;
1747 			if ( self->target )
1748 			{
1749 				self->painDebounceTime = G_ModelIndex( self->target );
1750 				self->count++;
1751 				if ( self->target2 )
1752 				{
1753 					self->disconnectDebounceTime = G_ModelIndex( self->target2 );
1754 					self->count++;
1755 					if ( self->target3 )
1756 					{
1757 						self->attackDebounceTime = G_ModelIndex( self->target3 );
1758 						self->count++;
1759 						if ( self->target4 )
1760 						{
1761 							self->pushDebounceTime = G_ModelIndex( self->target4 );
1762 							self->count++;
1763 						}
1764 					}
1765 				}
1766 			}
1767 		}
1768 	}
1769 //	G_SoundIndex( "sound/movers/switches/replicator.wav" );
1770 	self->e_UseFunc = useF_misc_replicator_item_spawn;
1771 
1772 	self->s.eFlags |= EF_NODRAW;
1773 	//self->contents = 0;
1774 
1775 	VectorSet( self->mins, -4, -4, 0 );
1776 	VectorSet( self->maxs, 4, 4, 8 );
1777 	G_SetOrigin( self, self->s.origin );
1778 	G_SetAngles( self, self->s.angles );
1779 
1780 	gi.linkentity( self );
1781 }
1782 
1783 /*QUAKED misc_trip_mine (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) START_ON BROADCAST START_OFF
1784 Place in a map and point the angles at whatever surface you want it to attach to.
1785 
1786 START_ON - If you give it a targetname to make it toggle-able, but want it to start on, set this flag
1787 BROADCAST - ONLY USE THIS IF YOU HAVE TO!  causes the trip wire and loop sound to always happen, use this if the beam drops out in certain situations
1788 START_OFF - If you give it a targetname, it will start completely off (laser AND base unit)  until used.
1789 
1790 The trip mine will attach to that surface and fire it's beam away from the surface at an angle perpendicular to it.
1791 
1792 targetname - starts off, when used, turns on (toggles)
1793 
1794 FIXME: sometimes we want these to not be shootable... maybe just put them behind a force field?
1795 */
1796 extern void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace );
1797 extern void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner );
1798 
misc_trip_mine_activate(gentity_t * self,gentity_t * other,gentity_t * activator)1799 void misc_trip_mine_activate( gentity_t *self, gentity_t *other, gentity_t *activator )
1800 {
1801 	if ( self->e_ThinkFunc == thinkF_laserTrapThink )
1802 	{
1803 		self->s.eFlags &= ~EF_FIRING;
1804 		self->s.loopSound = 0;
1805 		self->e_ThinkFunc = thinkF_NULL;
1806 		self->nextthink = -1;
1807 	}
1808 	else
1809 	{
1810 		self->e_ThinkFunc = thinkF_laserTrapThink;
1811 		self->nextthink = level.time + FRAMETIME;
1812 
1813 		self->s.eFlags &= ~EF_NODRAW;
1814 		self->contents = CONTENTS_SHOTCLIP;//CAN'T USE CONTENTS_SOLID because only ARCHITECTURE is contents_solid!!!
1815 		self->takedamage = qtrue;
1816 	}
1817 }
1818 
SP_misc_trip_mine(gentity_t * self)1819 void SP_misc_trip_mine( gentity_t *self )
1820 {
1821 	vec3_t	forward, end;
1822 	trace_t	trace;
1823 
1824 	AngleVectors( self->s.angles, forward, NULL, NULL );
1825 	VectorMA( self->s.origin, 128, forward, end );
1826 
1827 	gi.trace( &trace, self->s.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1828 
1829 	if ( trace.allsolid || trace.startsolid )
1830 	{
1831 		Com_Error( ERR_DROP,"misc_trip_mine at %s in solid\n", vtos(self->s.origin) );
1832 		G_FreeEntity( self );
1833 		return;
1834 	}
1835 	if ( trace.fraction == 1.0 )
1836 	{
1837 		Com_Error( ERR_DROP,"misc_trip_mine at %s pointed at no surface\n", vtos(self->s.origin) );
1838 		G_FreeEntity( self );
1839 		return;
1840 	}
1841 
1842 	RegisterItem( FindItemForWeapon( WP_TRIP_MINE ));	//precache the weapon
1843 
1844 	self->count = 2/*TRIPWIRE_STYLE*/;
1845 
1846 	vectoangles( trace.plane.normal, end );
1847 	G_SetOrigin( self, trace.endpos );
1848 	G_SetAngles( self, end );
1849 
1850 	CreateLaserTrap( self, trace.endpos, self );
1851 	touchLaserTrap( self, self, &trace );
1852 	self->e_ThinkFunc = thinkF_NULL;
1853 	self->nextthink = -1;
1854 
1855 	if ( !self->targetname || (self->spawnflags&1) )
1856 	{//starts on
1857 		misc_trip_mine_activate( self, self, self );
1858 	}
1859 	if ( self->targetname )
1860 	{
1861 		self->e_UseFunc = useF_misc_trip_mine_activate;
1862 	}
1863 
1864 	if (( self->spawnflags & 2 )) // broadcast...should only be used in very rare cases.  could fix networking, perhaps, but james suggested this because it's easier
1865 	{
1866 		self->svFlags |= SVF_BROADCAST;
1867 	}
1868 
1869 	// Whether to start completelly off or not.
1870 	if  ( self->targetname && self->spawnflags & 4 )
1871 	{
1872 		self->s.eFlags = EF_NODRAW;
1873 		self->contents = 0;
1874 		self->takedamage = qfalse;
1875 	}
1876 
1877 	gi.linkentity( self );
1878 }
1879 
1880 /*QUAKED misc_maglock (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x x
1881 Place facing a door (using the angle, not a targetname) and it will lock that door.  Can only be destroyed by lightsaber and will automatically unlock the door it's attached to
1882 
1883 NOTE: place these half-way in the door to make it flush with the door's surface.
1884 
1885 "target"	thing to use when destoryed (not doors - it automatically unlocks the door it was angled at)
1886 "health"	default is 10
1887 */
maglock_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)1888 void maglock_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
1889 {
1890 	//unlock our door if we're the last lock pointed at the door
1891 	if ( self->activator )
1892 	{
1893 		self->activator->lockCount--;
1894 		if ( !self->activator->lockCount )
1895 		{
1896 			self->activator->svFlags &= ~SVF_INACTIVE;
1897 		}
1898 	}
1899 
1900 	//use targets
1901 	G_UseTargets( self, attacker );
1902 	//die
1903 	WP_Explode( self );
1904 }
SP_misc_maglock(gentity_t * self)1905 void SP_misc_maglock ( gentity_t *self )
1906 {
1907 	//NOTE: May have to make these only work on doors that are either untargeted
1908 	//		or are targeted by a trigger, not doors fired off by scripts, counters
1909 	//		or other such things?
1910 	self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
1911 	self->fxID = G_EffectIndex( "maglock/explosion" );
1912 
1913 	G_SetOrigin( self, self->s.origin );
1914 
1915 	self->e_ThinkFunc = thinkF_maglock_link;
1916 	//FIXME: for some reason, when you re-load a level, these fail to find their doors...?  Random?  Testing an additional 200ms after the START_TIME_FIND_LINKS
1917 	self->nextthink = level.time + START_TIME_FIND_LINKS+200;//START_TIME_FIND_LINKS;//because we need to let the doors link up and spawn their triggers first!
1918 }
maglock_link(gentity_t * self)1919 void maglock_link( gentity_t *self )
1920 {
1921 	//find what we're supposed to be attached to
1922 	vec3_t	forward, start, end;
1923 	trace_t	trace;
1924 
1925 	AngleVectors( self->s.angles, forward, NULL, NULL );
1926 	VectorMA( self->s.origin, 128, forward, end );
1927 	VectorMA( self->s.origin, -4, forward, start );
1928 
1929 	gi.trace( &trace, start, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1930 
1931 	if ( trace.allsolid || trace.startsolid )
1932 	{
1933 		Com_Error( ERR_DROP,"misc_maglock at %s in solid\n", vtos(self->s.origin) );
1934 		G_FreeEntity( self );
1935 		return;
1936 	}
1937 	if ( trace.fraction == 1.0 )
1938 	{
1939 		self->e_ThinkFunc = thinkF_maglock_link;
1940 		self->nextthink = level.time + 100;
1941 		/*
1942 		Com_Error( ERR_DROP,"misc_maglock at %s pointed at no surface\n", vtos(self->s.origin) );
1943 		G_FreeEntity( self );
1944 		*/
1945 		return;
1946 	}
1947 	gentity_t *traceEnt = &g_entities[trace.entityNum];
1948 	if ( trace.entityNum >= ENTITYNUM_WORLD || !traceEnt || Q_stricmp( "func_door", traceEnt->classname ) )
1949 	{
1950 		self->e_ThinkFunc = thinkF_maglock_link;
1951 		self->nextthink = level.time + 100;
1952 		//Com_Error( ERR_DROP,"misc_maglock at %s not pointed at a door\n", vtos(self->s.origin) );
1953 		//G_FreeEntity( self );
1954 		return;
1955 	}
1956 
1957 	//check the traceEnt, make sure it's a door and give it a lockCount and deactivate it
1958 	//find the trigger for the door
1959 	self->activator = G_FindDoorTrigger( traceEnt );
1960 	if ( !self->activator )
1961 	{
1962 		self->activator = traceEnt;
1963 	}
1964 	self->activator->lockCount++;
1965 	self->activator->svFlags |= SVF_INACTIVE;
1966 
1967 	//now position and orient it
1968 	vectoangles( trace.plane.normal, end );
1969 	G_SetOrigin( self, trace.endpos );
1970 	G_SetAngles( self, end );
1971 
1972 	//make it hittable
1973 	//FIXME: if rotated/inclined this bbox may be off... but okay if we're a ghoul model?
1974 	//self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
1975 	VectorSet( self->mins, -8, -8, -8 );
1976 	VectorSet( self->maxs, 8, 8, 8 );
1977 	self->contents = CONTENTS_CORPSE;
1978 
1979 	//make it destroyable
1980 	self->flags |= FL_SHIELDED;//only damagable by lightsabers
1981 	self->takedamage = qtrue;
1982 	self->health = 10;
1983 	self->e_DieFunc = dieF_maglock_die;
1984 	//self->fxID = G_EffectIndex( "maglock/explosion" );
1985 
1986 	gi.linkentity( self );
1987 }
1988 
1989 /*
1990 ================
1991 EnergyShieldStationSettings
1992 ================
1993 */
EnergyShieldStationSettings(gentity_t * ent)1994 void EnergyShieldStationSettings(gentity_t *ent)
1995 {
1996 	G_SpawnInt( "count", "0", &ent->count );
1997 
1998 	if (!ent->count)
1999 	{
2000 		switch (g_spskill->integer)
2001 		{
2002 		case 0:	//	EASY
2003 			ent->count = 100;
2004 			break;
2005 		case 1:	//	MEDIUM
2006 			ent->count = 75;
2007 			break;
2008 		default :
2009 		case 2:	//	HARD
2010 			ent->count = 50;
2011 			break;
2012 		}
2013 	}
2014 }
2015 
2016 /*
2017 ================
2018 shield_power_converter_use
2019 ================
2020 */
shield_power_converter_use(gentity_t * self,gentity_t * other,gentity_t * activator)2021 void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2022 {
2023 	int dif,add;
2024 
2025 	if ( !activator || activator->s.number != 0 )
2026 	{
2027 		//only the player gets to use these
2028 		return;
2029 	}
2030 
2031 	G_ActivateBehavior( self,BSET_USE );
2032 
2033 	if ( self->setTime < level.time )
2034 	{
2035 		self->setTime = level.time + 100;
2036 
2037 		dif = 100 - activator->client->ps.stats[STAT_ARMOR]; // FIXME: define for max armor?
2038 
2039 		if ( dif > 0 && self->count )	// Already at full armor?..and do I even have anything to give
2040 		{
2041 			if ( dif > MAX_AMMO_GIVE )
2042 			{
2043 				add = MAX_AMMO_GIVE;
2044 			}
2045 			else
2046 			{
2047 				add = dif;
2048 			}
2049 
2050 			if ( self->count < add )
2051 			{
2052 				add = self->count;
2053 			}
2054 
2055 			self->count -= add;
2056 
2057 			activator->client->ps.stats[STAT_ARMOR] += add;
2058 
2059 			self->s.loopSound = G_SoundIndex( "sound/interface/shieldcon_run.wav" );
2060 		}
2061 
2062 		if ( self->count <= 0 )
2063 		{
2064 			// play empty sound
2065 			self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2066 			G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_empty.mp3" ));
2067 			self->s.loopSound = 0;
2068 
2069 			if ( self->s.eFlags & EF_SHADER_ANIM )
2070 			{
2071 	 			self->s.frame = 1;
2072 			}
2073 		}
2074 		else if ( activator->client->ps.stats[STAT_ARMOR] >= 100 ) // FIXME: define for max
2075 		{
2076 			// play full sound
2077 			G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_done.mp3" ));
2078 			self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2079 			self->s.loopSound = 0;
2080 		}
2081 	}
2082 
2083 	if ( self->s.loopSound )
2084 	{
2085 		// we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop
2086 		//	this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off
2087 		self->e_ThinkFunc = thinkF_poll_converter;
2088 		self->nextthink = level.time + 500;
2089 	}
2090 	else
2091 	{
2092 		// sound is already off, so we don't need to "think" about it.
2093 		self->e_ThinkFunc = thinkF_NULL;
2094 		self->nextthink = 0;
2095 	}
2096 
2097 	if ( activator->client->ps.stats[STAT_ARMOR] > 0 )
2098 	{
2099 		activator->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
2100 	}
2101 }
2102 
2103 
2104 /*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET
2105 model="models/items/psd_big.md3"
2106 Gives shield energy when used.
2107 
2108 USETARGET - when used it fires off target
2109 
2110 "health" - how much health the model has - default 60 (zero makes non-breakable)
2111 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2112 "target" - what to use when it dies
2113 "paintarget" - target to fire when hit (but not destroyed)
2114 "count" - the amount of ammo given when used (default 100)
2115 */
2116 //------------------------------------------------------------
SP_misc_model_shield_power_converter(gentity_t * ent)2117 void SP_misc_model_shield_power_converter( gentity_t *ent )
2118 {
2119 	SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2120 
2121 	ent->takedamage = qfalse;
2122 
2123 	EnergyShieldStationSettings(ent);
2124 
2125 	G_SoundIndex("sound/interface/shieldcon_run.wav");
2126 	G_SoundIndex("sound/interface/shieldcon_done.mp3");
2127 	G_SoundIndex("sound/interface/shieldcon_empty.mp3");
2128 
2129 	ent->s.modelindex = G_ModelIndex("models/items/psd_big.md3");	// Precache model
2130 	ent->s.modelindex2 = G_ModelIndex("models/items/psd_big.md3");	// Precache model
2131 }
2132 
bomb_planted_use(gentity_t * self,gentity_t * other,gentity_t * activator)2133 void bomb_planted_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2134 {
2135 	if ( self->count == 2 )
2136 	{
2137 		self->s.eFlags &= ~EF_NODRAW;
2138 		self->contents = CONTENTS_SOLID;
2139 		self->count = 1;
2140 		self->s.loopSound = self->noise_index;
2141 	}
2142 	else if ( self->count == 1 )
2143 	{
2144 		self->count = 0;
2145 		// play disarm sound
2146 		self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2147 		G_Sound( self, G_SoundIndex("sound/weapons/overchargeend"));
2148 		self->s.loopSound = 0;
2149 
2150 		// this pauses the shader on one frame (more or less)
2151 		self->s.eFlags |= EF_DISABLE_SHADER_ANIM;
2152 
2153 		// this starts the animation for the model
2154 		self->s.eFlags |= EF_ANIM_ONCE;
2155 		self->s.frame = 0;
2156 
2157 		//use targets
2158 		G_UseTargets( self, activator );
2159 	}
2160 }
2161 
2162 /*QUAKED misc_model_bomb_planted (1 0 0) (-16 -16 0) (16 16 70) x x x USETARGET
2163 model="models/map_objects/factory/bomb_new_deact.md3"
2164 Planted by evil men for evil purposes.
2165 
2166 "health" - please don't shoot the thermonuclear device
2167 "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
2168 */
2169 //------------------------------------------------------------
SP_misc_model_bomb_planted(gentity_t * ent)2170 void SP_misc_model_bomb_planted( gentity_t *ent )
2171 {
2172 	VectorSet( ent->mins, -16, -16, 0 );
2173 	VectorSet( ent->maxs, 16, 16, 70 );
2174 
2175 	SetMiscModelDefaults( ent, useF_bomb_planted_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2176 
2177 	ent->takedamage = qfalse;
2178 
2179 	G_SoundIndex("sound/weapons/overchargeend");
2180 
2181 	ent->s.modelindex = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3");	// Precache model
2182 	ent->s.modelindex2 = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3");	// Precache model
2183 	ent->noise_index = G_SoundIndex("sound/interface/ammocon_run");
2184 	ent->s.loopSound = ent->noise_index;
2185 	//ent->s.eFlags |= EF_SHADER_ANIM;
2186 	//ent->s.frame = ent->startFrame = 0;
2187 	ent->count = 1;
2188 
2189 	// If we have a targetname, we're are invisible until we are spawned in by being used.
2190 	if ( ent->targetname )
2191 	{
2192 		ent->s.eFlags = EF_NODRAW;
2193 		ent->contents = 0;
2194 		ent->count = 2;
2195 		ent->s.loopSound = 0;
2196 	}
2197 
2198 	int forceVisible = 0;
2199 	G_SpawnInt( "forcevisible", "0", &forceVisible );
2200 	if ( forceVisible )
2201 	{//can see these through walls with force sight, so must be broadcast
2202 		//ent->svFlags |= SVF_BROADCAST;
2203 		ent->s.eFlags |= EF_FORCE_VISIBLE;
2204 	}
2205 }
2206 
beacon_deploy(gentity_t * ent)2207 void beacon_deploy( gentity_t *ent )
2208 {
2209 	ent->e_ThinkFunc = thinkF_beacon_think;
2210 	ent->nextthink = level.time + FRAMETIME * 0.5f;
2211 
2212 	ent->s.frame = 0;
2213 	ent->startFrame = 0;
2214 	ent->endFrame = 30;
2215 	ent->loopAnim = qfalse;
2216 }
2217 
beacon_think(gentity_t * ent)2218 void beacon_think( gentity_t *ent )
2219 {
2220 	ent->nextthink = level.time + FRAMETIME * 0.5f;
2221 
2222 	// Deploy animation complete? Stop thinking and just animate signal forever.
2223 	if ( ent->s.frame == 30 )
2224 	{
2225 		ent->e_ThinkFunc = thinkF_NULL;
2226 		ent->nextthink = -1;
2227 
2228 		ent->startFrame = 31;
2229 		ent->endFrame = 60;
2230 		ent->loopAnim = qtrue;
2231 
2232 		ent->s.loopSound = ent->noise_index;
2233 	}
2234 }
2235 
beacon_use(gentity_t * self,gentity_t * other,gentity_t * activator)2236 void beacon_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2237 {
2238 	// Every time it's used it will be toggled on or off.
2239 	if ( self->count == 0 )
2240 	{
2241 		self->s.eFlags &= ~EF_NODRAW;
2242 		self->contents = CONTENTS_SOLID;
2243 		self->count = 1;
2244 		self->svFlags = SVF_ANIMATING;
2245 		beacon_deploy( self );
2246 	}
2247 	else
2248 	{
2249 		self->s.eFlags = EF_NODRAW;
2250 		self->contents = 0;
2251 		self->count = 0;
2252 		self->s.loopSound = 0;
2253 		self->svFlags = 0;
2254 	}
2255 }
2256 
2257 /*QUAKED misc_model_beacon (1 0 0) (-16 -16 0) (16 16 24) x x x
2258 model="models/map_objects/wedge/beacon.md3"
2259 An animating beacon model.
2260 
2261 "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
2262 */
2263 //------------------------------------------------------------
SP_misc_model_beacon(gentity_t * ent)2264 void SP_misc_model_beacon( gentity_t *ent )
2265 {
2266 	VectorSet( ent->mins, -16, -16, 0 );
2267 	VectorSet( ent->maxs, 16, 16, 24 );
2268 
2269 	SetMiscModelDefaults( ent, useF_beacon_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2270 
2271 	ent->takedamage = qfalse;
2272 
2273 	//G_SoundIndex("sound/weapons/overchargeend");
2274 
2275 	ent->s.modelindex = G_ModelIndex("models/map_objects/wedge/beacon.md3");	// Precache model
2276 	ent->s.modelindex2 = G_ModelIndex("models/map_objects/wedge/beacon.md3");	// Precache model
2277 	ent->noise_index = G_SoundIndex("sound/interface/ammocon_run");
2278 
2279 	// If we have a targetname, we're are invisible until we are spawned in by being used.
2280 	if ( ent->targetname )
2281 	{
2282 		ent->s.eFlags = EF_NODRAW;
2283 		ent->contents = 0;
2284 		ent->s.loopSound = 0;
2285 		ent->count = 0;
2286 	}
2287 	else
2288 	{
2289 		ent->count = 1;
2290 		beacon_deploy( ent );
2291 	}
2292 
2293 	int forceVisible = 0;
2294 	G_SpawnInt( "forcevisible", "0", &forceVisible );
2295 	if ( forceVisible )
2296 	{//can see these through walls with force sight, so must be broadcast
2297 		//ent->svFlags |= SVF_BROADCAST;
2298 		ent->s.eFlags |= EF_FORCE_VISIBLE;
2299 	}
2300 }
2301 
2302 /*QUAKED misc_shield_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET
2303 model="models/items/a_shield_converter.md3"
2304 Gives shield energy when used.
2305 
2306 USETARGET - when used it fires off target
2307 
2308 "health" - how much health the model has - default 60 (zero makes non-breakable)
2309 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2310 "target" - what to use when it dies
2311 "paintarget" - target to fire when hit (but not destroyed)
2312 "count" - the amount of ammo given when used (default 100)
2313 */
2314 //------------------------------------------------------------
SP_misc_shield_floor_unit(gentity_t * ent)2315 void SP_misc_shield_floor_unit( gentity_t *ent )
2316 {
2317 	VectorSet( ent->mins, -16, -16, 0 );
2318 	VectorSet( ent->maxs, 16, 16, 32 );
2319 
2320 	SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2321 
2322 	ent->takedamage = qfalse;
2323 
2324 	EnergyShieldStationSettings(ent);
2325 
2326 	G_SoundIndex("sound/interface/shieldcon_run.wav");
2327 	G_SoundIndex("sound/interface/shieldcon_done.mp3");
2328 	G_SoundIndex("sound/interface/shieldcon_empty.mp3");
2329 
2330 	ent->s.modelindex = G_ModelIndex( "models/items/a_shield_converter.md3" );	// Precache model
2331 	ent->s.eFlags |= EF_SHADER_ANIM;
2332 }
2333 
2334 
2335 /*
2336 ================
2337 EnergyAmmoShieldStationSettings
2338 ================
2339 */
EnergyAmmoStationSettings(gentity_t * ent)2340 void EnergyAmmoStationSettings(gentity_t *ent)
2341 {
2342 	G_SpawnInt( "count", "0", &ent->count );
2343 
2344 	if (!ent->count)
2345 	{
2346 		switch (g_spskill->integer)
2347 		{
2348 		case 0:	//	EASY
2349 			ent->count = 100;
2350 			break;
2351 		case 1:	//	MEDIUM
2352 			ent->count = 75;
2353 			break;
2354 		default :
2355 		case 2:	//	HARD
2356 			ent->count = 50;
2357 			break;
2358 		}
2359 	}
2360 }
2361 
2362 // There has to be an easier way to turn off the looping sound...but
2363 //	it's the night before beta and my brain is fried
2364 //------------------------------------------------------------------
poll_converter(gentity_t * self)2365 void poll_converter( gentity_t *self )
2366 {
2367 	self->s.loopSound = 0;
2368 	self->nextthink = 0;
2369 	self->e_ThinkFunc = thinkF_NULL;
2370 }
2371 
2372 /*
2373 ================
2374 ammo_power_converter_use
2375 ================
2376 */
ammo_power_converter_use(gentity_t * self,gentity_t * other,gentity_t * activator)2377 void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
2378 {
2379 	int			add;
2380 	int			difBlaster,difPowerCell,difMetalBolts;
2381 	playerState_t *ps;
2382 
2383 	if ( !activator || activator->s.number != 0 )
2384 	{//only the player gets to use these
2385 		return;
2386 	}
2387 
2388 	G_ActivateBehavior( self, BSET_USE );
2389 
2390 	ps = &activator->client->ps;
2391 
2392 	if ( self->setTime < level.time )
2393 	{
2394 		difBlaster		= ammoData[AMMO_BLASTER].max - ps->ammo[AMMO_BLASTER];
2395 		difPowerCell	= ammoData[AMMO_POWERCELL].max - ps->ammo[AMMO_POWERCELL];
2396 		difMetalBolts	= ammoData[AMMO_METAL_BOLTS].max - ps->ammo[AMMO_METAL_BOLTS];
2397 
2398 		// Has it got any power left...and can we even use any of it?
2399 		if ( self->count && ( difBlaster > 0 || difPowerCell > 0 || difMetalBolts > 0 ))
2400 		{
2401 			// at least one of the ammo types could stand to take on a bit more ammo
2402 			self->setTime = level.time + 100;
2403 			self->s.loopSound = G_SoundIndex( "sound/interface/ammocon_run.wav" );
2404 
2405 			// dole out ammo in little packets
2406 			if ( self->count > MAX_AMMO_GIVE )
2407 			{
2408 				add = MAX_AMMO_GIVE;
2409 			}
2410 			else if ( self->count < 0 )
2411 			{
2412 				add = 0;
2413 			}
2414 			else
2415 			{
2416 				add = self->count;
2417 			}
2418 
2419 			// all weapons fill at same rate...
2420 			ps->ammo[AMMO_BLASTER]		+= add;
2421 			ps->ammo[AMMO_POWERCELL]	+= add;
2422 			ps->ammo[AMMO_METAL_BOLTS]	+= add;
2423 
2424 			// ...then get clamped to max
2425 			if ( ps->ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max )
2426 			{
2427 				ps->ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max;
2428 			}
2429 
2430 			if ( ps->ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
2431 			{
2432 				ps->ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
2433 			}
2434 
2435 			if ( ps->ammo[AMMO_METAL_BOLTS] > ammoData[AMMO_METAL_BOLTS].max )
2436 			{
2437 				ps->ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max;
2438 			}
2439 
2440 			self->count -= add;
2441 		}
2442 
2443 		if ( self->count <= 0 )
2444 		{
2445 			// play empty sound
2446 			self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2447 			G_Sound( self, G_SoundIndex( "sound/interface/ammocon_empty.mp3" ));
2448 			self->s.loopSound = 0;
2449 
2450 			if ( self->s.eFlags & EF_SHADER_ANIM )
2451 			{
2452 				self->s.frame = 1;
2453 			}
2454 		}
2455 		else if  ( ps->ammo[AMMO_BLASTER] >= ammoData[AMMO_BLASTER].max
2456 						&& ps->ammo[AMMO_POWERCELL] >= ammoData[AMMO_POWERCELL].max
2457 						&& ps->ammo[AMMO_METAL_BOLTS] >= ammoData[AMMO_METAL_BOLTS].max )
2458 		{
2459 			// play full sound
2460 			G_Sound( self, G_SoundIndex( "sound/interface/ammocon_done.wav" ));
2461 			self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
2462 			self->s.loopSound = 0;
2463 		}
2464 	}
2465 
2466 
2467 	if ( self->s.loopSound )
2468 	{
2469 		// we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop
2470 		//	this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off
2471 		self->e_ThinkFunc = thinkF_poll_converter;
2472 		self->nextthink = level.time + 500;
2473 	}
2474 	else
2475 	{
2476 		// sound is already off, so we don't need to "think" about it.
2477 		self->e_ThinkFunc = thinkF_NULL;
2478 		self->nextthink = 0;
2479 	}
2480 }
2481 
2482 /*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET
2483 model="models/items/power_converter.md3"
2484 Gives ammo energy when used.
2485 
2486 USETARGET - when used it fires off target
2487 
2488 "health" - how much health the model has - default 60 (zero makes non-breakable)
2489 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2490 "target" - what to use when it dies
2491 "paintarget" - target to fire when hit (but not destroyed)
2492 "count" - the amount of ammo given when used (default 100)
2493 */
2494 //------------------------------------------------------------
SP_misc_model_ammo_power_converter(gentity_t * ent)2495 void SP_misc_model_ammo_power_converter( gentity_t *ent )
2496 {
2497 	SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2498 
2499 	ent->takedamage = qfalse;
2500 
2501 	EnergyAmmoStationSettings(ent);
2502 
2503 	G_SoundIndex("sound/interface/ammocon_run.wav");
2504 	G_SoundIndex("sound/interface/ammocon_done.mp3");
2505 	G_SoundIndex("sound/interface/ammocon_empty.mp3");
2506 
2507 	ent->s.modelindex = G_ModelIndex("models/items/power_converter.md3");	// Precache model
2508 	ent->s.modelindex2 = G_ModelIndex("models/items/power_converter.md3");	// Precache model
2509 }
2510 
2511 /*QUAKED misc_ammo_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET
2512 model="models/items/a_pwr_converter.md3"
2513 Gives ammo energy when used.
2514 
2515 USETARGET - when used it fires off target
2516 
2517 "health" - how much health the model has - default 60 (zero makes non-breakable)
2518 "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
2519 "target" - what to use when it dies
2520 "paintarget" - target to fire when hit (but not destroyed)
2521 "count" - the amount of ammo given when used (default 100)
2522 */
2523 //------------------------------------------------------------
SP_misc_ammo_floor_unit(gentity_t * ent)2524 void SP_misc_ammo_floor_unit( gentity_t *ent )
2525 {
2526 	VectorSet( ent->mins, -16, -16, 0 );
2527 	VectorSet( ent->maxs, 16, 16, 32 );
2528 
2529 	SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2530 
2531 	ent->takedamage = qfalse;
2532 
2533 	EnergyAmmoStationSettings(ent);
2534 
2535 	G_SoundIndex("sound/interface/ammocon_run.wav");
2536 	G_SoundIndex("sound/interface/ammocon_done.mp3");
2537 	G_SoundIndex("sound/interface/ammocon_empty.mp3");
2538 
2539 	ent->s.modelindex = G_ModelIndex("models/items/a_pwr_converter.md3");	// Precache model
2540 	ent->s.eFlags |= EF_SHADER_ANIM;
2541 }
2542 
2543 
2544 /*
2545 ================
2546 welder_think
2547 ================
2548 */
welder_think(gentity_t * self)2549 void welder_think( gentity_t *self )
2550 {
2551 	self->nextthink = level.time + 200;
2552 	vec3_t		org,
2553 				dir;
2554 	mdxaBone_t	boltMatrix;
2555 
2556 	if( self->svFlags & SVF_INACTIVE )
2557 	{
2558 		return;
2559 	}
2560 
2561 	int newBolt;
2562 
2563 	// could alternate between the two... or make it random... ?
2564 	newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash" );
2565 //	newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash01" );
2566 
2567 	if ( newBolt != -1 )
2568 	{
2569 		G_Sound( self, self->noise_index );
2570 	//	G_PlayEffect( "blueWeldSparks", self->playerModel, newBolt, self->s.number);
2571 		// The welder gets rotated around a lot, and since the origin is offset by 352 I have to make this super expensive call to position the hurt...
2572 		gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, newBolt,
2573 					&boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time),
2574 					NULL, self->s.modelScale );
2575 
2576 		gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
2577 		VectorSubtract( self->currentOrigin, org, dir );
2578 		VectorNormalize( dir );
2579 		// we want the  welder effect to face inwards....
2580 		G_PlayEffect( "sparks/blueWeldSparks", org, dir );
2581 		G_RadiusDamage( org, self, 10, 45, self, MOD_UNKNOWN );
2582 	}
2583 
2584 }
2585 
2586 /*
2587 ================
2588 welder_use
2589 ================
2590 */
welder_use(gentity_t * self,gentity_t * other,gentity_t * activator)2591 void welder_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2592 {
2593 	// Toggle on and off
2594 	if( self->spawnflags & 1 )
2595 	{
2596 		self->nextthink = level.time + FRAMETIME;
2597 	}
2598 	else
2599 	{
2600 		self->nextthink = -1;
2601 	}
2602 	self->spawnflags = (self->spawnflags ^ 1);
2603 
2604 }
2605 
2606 /*QUAKED misc_model_welder (1 0 0) (-16 -16 -16) (16 16 16) START_OFF
2607 model="models/map_objects/cairn/welder.md3"
2608 When 'on' emits sparks from it's welding points
2609 
2610 START_OFF  - welder starts off, using it toggles it on/off
2611 
2612 */
2613 //------------------------------------------------------------
SP_misc_model_welder(gentity_t * ent)2614 void SP_misc_model_welder( gentity_t *ent )
2615 {
2616 	VectorSet( ent->mins, 336, -16, 0 );
2617 	VectorSet( ent->maxs, 368, 16, 32 );
2618 
2619 	SetMiscModelDefaults( ent, useF_welder_use, "4", CONTENTS_SOLID, 0, qfalse, qfalse );
2620 
2621 	ent->takedamage = qfalse;
2622 	ent->contents = 0;
2623 	G_EffectIndex( "sparks/blueWeldSparks" );
2624 	ent->noise_index = G_SoundIndex( "sound/movers/objects/welding.wav" );
2625 
2626 	ent->s.modelindex = G_ModelIndex( "models/map_objects/cairn/welder.glm" );
2627 //	ent->s.modelindex2 = G_ModelIndex( "models/map_objects/cairn/welder.md3" );
2628 	ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/cairn/welder.glm", ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
2629 	ent->s.radius = 400.0f;// the origin of the welder is offset by approximately 352, so we need the big radius
2630 
2631 	ent->e_ThinkFunc = thinkF_welder_think;
2632 
2633 	ent->nextthink = level.time + 1000;
2634 	if( ent->spawnflags & 1 )
2635 	{
2636 		ent->nextthink = -1;
2637 	}
2638 }
2639 
2640 
2641 /*
2642 ================
2643 jabba_cam_use
2644 ================
2645 */
jabba_cam_use(gentity_t * self,gentity_t * other,gentity_t * activator)2646 void jabba_cam_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2647 {
2648 	if( self->spawnflags & 1 )
2649 	{
2650 		self->spawnflags &= ~1;
2651 		gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 15, 0, BONE_ANIM_OVERRIDE_FREEZE, -1.5f, (cg.time?cg.time:level.time), -1, 0 );
2652 
2653 	}
2654 	else
2655 	{
2656 		self->spawnflags |= 1;
2657 		gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 1.5f, (cg.time?cg.time:level.time), -1, 0 );
2658 	}
2659 }
2660 
2661 /*QUAKED misc_model_jabba_cam (1 0 0) ( 0 -8 0) (60 8 16) EXTENDED
2662 model="models/map_objects/nar_shaddar/jabacam.md3"
2663 
2664 The eye camera that popped out of Jabba's front door
2665 
2666   EXTENDED - Starts in the extended position
2667 
2668   targetname - Toggles it on/off
2669 
2670 */
2671 //-----------------------------------------------------
SP_misc_model_jabba_cam(gentity_t * ent)2672 void SP_misc_model_jabba_cam( gentity_t *ent )
2673 //-----------------------------------------------------
2674 {
2675 
2676 	VectorSet( ent->mins, -60.0f, -8.0f, 0.0f );
2677 	VectorSet( ent->maxs, 60.0f, 8.0f, 16.0f );
2678 
2679 	SetMiscModelDefaults( ent, useF_jabba_cam_use, "4", 0, 0, qfalse, qfalse );
2680 	G_SetAngles( ent, ent->s.angles );
2681 
2682 	ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/jabacam/jabacam.glm" );
2683 	ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/nar_shaddar/jabacam/jabacam.glm", ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
2684 	ent->s.radius = 150.0f;  //......
2685 	VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
2686 
2687 	ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
2688 
2689 	ent->e_UseFunc = useF_jabba_cam_use;
2690 
2691 	ent->takedamage = qfalse;
2692 
2693 	// start extended..
2694 	if ( ent->spawnflags & 1 )
2695 	{
2696 		gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 );
2697 	}
2698 
2699 	gi.linkentity( ent );
2700 }
2701 
2702 //------------------------------------------------------------------------
misc_use(gentity_t * self,gentity_t * other,gentity_t * activator)2703 void misc_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2704 {
2705 	misc_model_breakable_die( self, other, activator, 100, MOD_UNKNOWN );
2706 }
2707 
2708 /*QUAKED misc_exploding_crate (1 0 0.25) (-24 -24 0) (24 24 64)
2709 model="models/map_objects/nar_shaddar/crate_xplode.md3"
2710 Basic exploding crate
2711 
2712 "health" - how much health the model has - default 40 (zero makes non-breakable)
2713 
2714 "splashRadius" - radius to do damage in - default 128
2715 "splashDamage" - amount of damage to do when it explodes - default 50
2716 
2717 "targetname" - auto-explodes
2718 "target" - what to use when it dies
2719 
2720 */
2721 //------------------------------------------------------------
SP_misc_exploding_crate(gentity_t * ent)2722 void SP_misc_exploding_crate( gentity_t *ent )
2723 {
2724 	G_SpawnInt( "health", "40", &ent->health );
2725 	G_SpawnInt( "splashRadius", "128", &ent->splashRadius );
2726 	G_SpawnInt( "splashDamage", "50", &ent->splashDamage );
2727 
2728 	ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/crate_xplode.md3" );
2729 	G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
2730 	G_EffectIndex( "chunks/metalexplode" );
2731 
2732 	VectorSet( ent->mins, -24, -24, 0 );
2733 	VectorSet( ent->maxs, 24, 24, 64 );
2734 
2735 	ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//CONTENTS_SOLID;
2736 	ent->takedamage = qtrue;
2737 
2738 	G_SetOrigin( ent, ent->s.origin );
2739 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
2740 	gi.linkentity (ent);
2741 
2742 	if ( ent->targetname )
2743 	{
2744 		ent->e_UseFunc = useF_misc_use;
2745 	}
2746 
2747 	ent->material = MAT_CRATE1;
2748 	ent->e_DieFunc = dieF_misc_model_breakable_die;//ExplodeDeath;
2749 }
2750 
2751 /*QUAKED misc_gas_tank (1 0 0.25) (-4 -4 0) (4 4 40)
2752 model="models/map_objects/imp_mine/tank.md3"
2753 Basic exploding oxygen tank
2754 
2755 "health" - how much health the model has - default 20 (zero makes non-breakable)
2756 
2757 "splashRadius" - radius to do damage in - default 48
2758 "splashDamage" - amount of damage to do when it explodes - default 32
2759 
2760 "targetname" - auto-explodes
2761 "target" - what to use when it dies
2762 
2763 */
2764 
gas_random_jet(gentity_t * self)2765 void gas_random_jet( gentity_t *self )
2766 {
2767 	vec3_t pt;
2768 
2769 	VectorCopy( self->currentOrigin, pt );
2770 	pt[2] += 50;
2771 
2772 	G_PlayEffect( "env/mini_gasjet", pt );
2773 
2774 	self->nextthink = level.time + Q_flrand(0.0f, 1.0f) * 16000 + 12000; // do this rarely
2775 }
2776 
2777 //------------------------------------------------------------
GasBurst(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,const vec3_t point,int damage,int mod,int hitLoc)2778 void GasBurst( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc )
2779 {
2780 	vec3_t pt;
2781 
2782 	VectorCopy( self->currentOrigin, pt );
2783 	pt[2] += 46;
2784 
2785 	G_PlayEffect( "env/mini_flamejet", pt );
2786 
2787 	// do some damage to anything that may be standing on top of it when it bursts into flame
2788 	pt[2] += 32;
2789 	G_RadiusDamage( pt, self, 32, 32, self, MOD_UNKNOWN );
2790 
2791 	//  only get one burst
2792 	self->e_PainFunc = painF_NULL;
2793 }
2794 
2795 //------------------------------------------------------------
SP_misc_gas_tank(gentity_t * ent)2796 void SP_misc_gas_tank( gentity_t *ent )
2797 {
2798 	G_SpawnInt( "health", "20", &ent->health );
2799 	G_SpawnInt( "splashRadius", "48", &ent->splashRadius );
2800 	G_SpawnInt( "splashDamage", "32", &ent->splashDamage );
2801 
2802 	ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/tank.md3" );
2803 	G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
2804 	G_EffectIndex( "chunks/metalexplode" );
2805 	G_EffectIndex( "env/mini_flamejet" );
2806 	G_EffectIndex( "env/mini_gasjet" );
2807 
2808 	VectorSet( ent->mins, -4, -4, 0 );
2809 	VectorSet( ent->maxs, 4, 4, 40 );
2810 
2811 	ent->contents = CONTENTS_SOLID;
2812 	ent->takedamage = qtrue;
2813 
2814 	G_SetOrigin( ent, ent->s.origin );
2815 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
2816 	gi.linkentity (ent);
2817 
2818 	ent->e_PainFunc = painF_GasBurst;
2819 
2820 	if ( ent->targetname )
2821 	{
2822 		ent->e_UseFunc = useF_misc_use;
2823 	}
2824 
2825 	ent->material = MAT_METAL3;
2826 
2827 	ent->e_DieFunc = dieF_misc_model_breakable_die;
2828 
2829 	ent->e_ThinkFunc = thinkF_gas_random_jet;
2830 	ent->nextthink = level.time + Q_flrand(0.0f, 1.0f) * 12000 + 6000; // do this rarely
2831 }
2832 
2833 /*QUAKED misc_crystal_crate (1 0 0.25) (-34 -34 0) (34 34 44) NON_SOLID
2834 model="models/map_objects/imp_mine/crate_open.md3"
2835 Open crate of crystals that explode when shot
2836 
2837 NON_SOLID - can only be shot
2838 
2839 "health" - how much health the crate has, default 80
2840 
2841 "splashRadius" - radius to do damage in, default 80
2842 "splashDamage" - amount of damage to do when it explodes, default 40
2843 
2844 "targetname" - auto-explodes
2845 "target" - what to use when it dies
2846 
2847 */
2848 
2849 //------------------------------------------------------------
CrystalCratePain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,const vec3_t point,int damage,int mod,int hitLoc)2850 void CrystalCratePain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc )
2851 {
2852 	vec3_t pt;
2853 
2854 	VectorCopy( self->currentOrigin, pt );
2855 	pt[2] += 36;
2856 
2857 	G_PlayEffect( "env/crystal_crate", pt );
2858 
2859 	// do some damage, heh
2860 	pt[2] += 32;
2861 	G_RadiusDamage( pt, self, 16, 32, self, MOD_UNKNOWN );
2862 
2863 }
2864 
2865 //------------------------------------------------------------
SP_misc_crystal_crate(gentity_t * ent)2866 void SP_misc_crystal_crate( gentity_t *ent )
2867 {
2868 	G_SpawnInt( "health", "80", &ent->health );
2869 	G_SpawnInt( "splashRadius", "80", &ent->splashRadius );
2870 	G_SpawnInt( "splashDamage", "40", &ent->splashDamage );
2871 
2872 	ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/crate_open.md3" );
2873 	ent->fxID = G_EffectIndex( "thermal/explosion" ); // FIXME: temp
2874 	G_EffectIndex( "env/crystal_crate" );
2875 	G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
2876 
2877 	VectorSet( ent->mins, -34, -34, 0 );
2878 	VectorSet( ent->maxs, 34, 34, 44 );
2879 
2880 	//Blocks movement
2881 	ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
2882 
2883 	if ( ent->spawnflags & 1 )  // non-solid
2884 	{	// Override earlier contents flag...
2885 		//Can only be shot
2886 		ent->contents = CONTENTS_SHOTCLIP;
2887 	}
2888 
2889 
2890 	ent->takedamage = qtrue;
2891 
2892 	G_SetOrigin( ent, ent->s.origin );
2893 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
2894 	gi.linkentity (ent);
2895 
2896 	ent->e_PainFunc = painF_CrystalCratePain;
2897 
2898 	if ( ent->targetname )
2899 	{
2900 		ent->e_UseFunc = useF_misc_use;
2901 	}
2902 
2903 	ent->material = MAT_CRATE2;
2904 
2905 	ent->e_DieFunc = dieF_misc_model_breakable_die;
2906 }
2907 
2908 /*QUAKED misc_atst_drivable (1 0 0.25) (-40 -40 -24) (40 40 248)
2909 model="models/players/atst/model.glm"
2910 
2911 Drivable ATST, when used by player, they become the ATST.  When the player hits use again, they get out.
2912 
2913 "health" - how much health the atst has - default 800
2914 
2915 "target" - what to use when it dies
2916 */
misc_atst_setanim(gentity_t * self,int bone,int anim)2917 void misc_atst_setanim( gentity_t *self, int bone, int anim )
2918 {
2919 	if ( bone < 0 || anim < 0 )
2920 	{
2921 		return;
2922 	}
2923 	int	firstFrame = -1;
2924 	int	lastFrame = -1;
2925 	float animSpeed = 0;
2926 	//try to get anim ranges from the animation.cfg for an AT-ST
2927 	for ( int i = 0; i < level.numKnownAnimFileSets; i++ )
2928 	{
2929 		if ( !Q_stricmp( "atst", level.knownAnimFileSets[i].filename ) )
2930 		{
2931 			firstFrame = level.knownAnimFileSets[i].animations[anim].firstFrame;
2932 			lastFrame = firstFrame+level.knownAnimFileSets[i].animations[anim].numFrames;
2933 			animSpeed = 50.0f / level.knownAnimFileSets[i].animations[anim].frameLerp;
2934 			break;
2935 		}
2936 	}
2937 	if ( firstFrame != -1 && lastFrame != -1 && animSpeed != 0 )
2938 	{
2939 		if (!gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame,
2940 								lastFrame, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeed,
2941 								(cg.time?cg.time:level.time), -1, 150 ))
2942 		{
2943 			gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame,
2944 								lastFrame, BONE_ANIM_OVERRIDE_FREEZE, animSpeed,
2945 								(cg.time?cg.time:level.time), -1, 150 );
2946 		}
2947 	}
2948 }
misc_atst_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)2949 void misc_atst_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
2950 {//ATST was destroyed while you weren't in it
2951 	//can't be used anymore
2952 	self->e_UseFunc = useF_NULL;
2953 	//sigh... remove contents so we don't block the player's path...
2954 	self->contents = CONTENTS_CORPSE;
2955 	self->takedamage = qfalse;
2956 	self->maxs[2] = 48;
2957 
2958 	//FIXME: match to slope
2959 	vec3_t effectPos;
2960 	VectorCopy( self->currentOrigin, effectPos );
2961 	effectPos[2] -= 15;
2962 	G_PlayEffect( "explosions/droidexplosion1", effectPos );
2963 //	G_PlayEffect( "small_chunks", effectPos );
2964 	//set these to defaults that work in a worst-case scenario (according to current animation.cfg)
2965 	gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone );
2966 	misc_atst_setanim( self, self->rootBone, BOTH_DEATH1 );
2967 }
2968 
2969 extern void G_DriveATST( gentity_t *ent, gentity_t *atst );
2970 extern void SetClientViewAngle( gentity_t *ent, vec3_t angle );
2971 extern qboolean PM_InSlopeAnim( int anim );
misc_atst_use(gentity_t * self,gentity_t * other,gentity_t * activator)2972 void misc_atst_use( gentity_t *self, gentity_t *other, gentity_t *activator )
2973 {
2974 	if ( !activator || activator->s.number )
2975 	{//only player can do this
2976 		return;
2977 	}
2978 
2979 	int	tempLocDmg[HL_MAX];
2980 	int	hl, tempHealth;
2981 
2982 	if ( activator->client->NPC_class != CLASS_ATST )
2983 	{//get in the ATST
2984 		if ( activator->client->ps.groundEntityNum != self->s.number )
2985 		{//can only get in if on top of me...
2986 			//we *could* even check for the hatch surf...?
2987 			return;
2988 		}
2989 		//copy origin
2990 		G_SetOrigin( activator, self->currentOrigin );
2991 
2992 		//copy angles
2993 		VectorCopy( self->s.angles2, self->currentAngles );
2994 		G_SetAngles( activator, self->currentAngles );
2995 		SetClientViewAngle( activator, self->currentAngles );
2996 
2997 		//set player to my g2 instance
2998 		gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone );
2999 		G_DriveATST( activator, self );
3000 		activator->activator = self;
3001 		self->s.eFlags |= EF_NODRAW;
3002 		self->svFlags |= SVF_NOCLIENT;
3003 		self->contents = 0;
3004 		self->takedamage = qfalse;
3005 		//transfer armor
3006 		tempHealth = self->health;
3007 		self->health = activator->client->ps.stats[STAT_ARMOR];
3008 		activator->client->ps.stats[STAT_ARMOR] = tempHealth;
3009 		//transfer locationDamage
3010 		for ( hl = HL_NONE; hl < HL_MAX; hl++ )
3011 		{
3012 			tempLocDmg[hl] = activator->locationDamage[hl];
3013 			activator->locationDamage[hl] = self->locationDamage[hl];
3014 			self->locationDamage[hl] = tempLocDmg[hl];
3015 		}
3016 		if ( !self->s.number )
3017 		{
3018 			CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 );
3019 		}
3020 	}
3021 	else
3022 	{//get out of ATST
3023 		int legsAnim = activator->client->ps.legsAnim;
3024 		if ( legsAnim != BOTH_STAND1
3025 			&& !PM_InSlopeAnim( legsAnim )
3026 			&& legsAnim != BOTH_TURN_RIGHT1 && legsAnim != BOTH_TURN_LEFT1 )
3027 		{//can't get out of it while it's still moving
3028 			return;
3029 		}
3030 		//FIXME: after a load/save, this crashes, BAD... somewhere in G2
3031 		G_SetOrigin( self, activator->currentOrigin );
3032 		VectorSet( self->currentAngles, 0, activator->client->ps.legsYaw, 0 );
3033 		//self->currentAngles[PITCH] = activator->currentAngles[ROLL] = 0;
3034 		G_SetAngles( self, self->currentAngles );
3035 		VectorCopy( activator->currentAngles, self->s.angles2 );
3036 		//remove my G2
3037 		if ( self->playerModel >= 0 )
3038 		{
3039 			gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel );
3040 			self->playerModel = -1;
3041 		}
3042 		//copy player's
3043 		gi.G2API_CopyGhoul2Instance( activator->ghoul2, self->ghoul2, -1 );
3044 		self->playerModel = 0;//assumption
3045 		//reset player to kyle
3046 		G_DriveATST( activator, NULL );
3047 		activator->activator = NULL;
3048 		self->s.eFlags &= ~EF_NODRAW;
3049 		self->svFlags &= ~SVF_NOCLIENT;
3050 		self->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;
3051 		self->takedamage = qtrue;
3052 		//transfer armor
3053 		tempHealth = self->health;
3054 		self->health = activator->client->ps.stats[STAT_ARMOR];
3055 		activator->client->ps.stats[STAT_ARMOR] = tempHealth;
3056 		//transfer locationDamage
3057 		for ( hl = HL_NONE; hl < HL_MAX; hl++ )
3058 		{
3059 			tempLocDmg[hl] = self->locationDamage[hl];
3060 			self->locationDamage[hl] = activator->locationDamage[hl];
3061 			activator->locationDamage[hl] = tempLocDmg[hl];
3062 		}
3063 		//link me back in
3064 		gi.linkentity ( self );
3065 		//put activator on top of me?
3066 		vec3_t	newOrg = {activator->currentOrigin[0], activator->currentOrigin[1], activator->currentOrigin[2] + (self->maxs[2]-self->mins[2]) + 1 };
3067 		G_SetOrigin( activator, newOrg );
3068 		//open the hatch
3069 		misc_atst_setanim( self, self->craniumBone, BOTH_STAND2 );
3070 		gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_hatchcover", 0 );
3071 		G_Sound( self, G_SoundIndex( "sound/chars/atst/atst_hatch_open" ));
3072 	}
3073 }
3074 
SP_misc_atst_drivable(gentity_t * ent)3075 void SP_misc_atst_drivable( gentity_t *ent )
3076 {
3077 	extern void NPC_ATST_Precache(void);
3078 	extern void NPC_PrecacheAnimationCFG( const char *NPC_type );
3079 
3080 	ent->s.modelindex = G_ModelIndex( "models/players/atst/model.glm" );
3081 	ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/players/atst/model.glm", ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0 );
3082 	ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
3083 	ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );	//FIXME: need to somehow set the anim/frame to the equivalent of BOTH_STAND1...  use to be that BOTH_STAND1 was the first frame of the glm, but not anymore
3084 	ent->s.radius = 320;
3085 	VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
3086 
3087 	//register my weapons, sounds and model
3088 	RegisterItem( FindItemForWeapon( WP_ATST_MAIN ));	//precache the weapon
3089 	RegisterItem( FindItemForWeapon( WP_ATST_SIDE ));	//precache the weapon
3090 	//HACKHACKHACKTEMP - until ATST gets real weapons of it's own?
3091 	RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN ));	//precache the weapon
3092 //	RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER ));	//precache the weapon
3093 //	RegisterItem( FindItemForWeapon( WP_BOWCASTER ));	//precache the weapon
3094 	//HACKHACKHACKTEMP - until ATST gets real weapons of it's own?
3095 
3096 	G_SoundIndex( "sound/chars/atst/atst_hatch_open" );
3097 	G_SoundIndex( "sound/chars/atst/atst_hatch_close" );
3098 
3099 	NPC_ATST_Precache();
3100 	ent->NPC_type = (char *)"atst";
3101 	NPC_PrecacheAnimationCFG( ent->NPC_type );
3102 	//open the hatch
3103 	misc_atst_setanim( ent, ent->rootBone, BOTH_STAND2 );
3104 	gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "head_hatchcover", 0 );
3105 
3106 	VectorSet( ent->mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 );
3107 	VectorSet( ent->maxs, ATST_MAXS0, ATST_MAXS1, ATST_MAXS2 );
3108 
3109 	ent->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;
3110 	ent->flags |= FL_SHIELDED;
3111 	ent->takedamage = qtrue;
3112 	if ( !ent->health )
3113 	{
3114 		ent->health = 800;
3115 	}
3116 	ent->s.radius = 320;
3117 
3118 	ent->max_health = ent->health; // cg_draw needs this
3119 
3120 	G_SetOrigin( ent, ent->s.origin );
3121 	G_SetAngles( ent, ent->s.angles );
3122 	VectorCopy( ent->currentAngles, ent->s.angles2 );
3123 
3124 	gi.linkentity ( ent );
3125 
3126 	//FIXME: test the origin to make sure I'm clear?
3127 
3128 	ent->e_UseFunc = useF_misc_atst_use;
3129 	ent->svFlags |= SVF_PLAYER_USABLE;
3130 
3131 	//make it able to take damage and die when you're not in it...
3132 	//do an explosion and play the death anim, remove use func.
3133 	ent->e_DieFunc = dieF_misc_atst_die;
3134 }
3135 
3136 extern int	G_FindConfigstringIndex( const char *name, int start, int max, qboolean create );
3137 
3138 /*QUAKED misc_weather_zone (0 .5 .8) ?
3139 Determines a region to check for weather contents - (optional, used to reduce load times)
3140 Place surrounding your inside/outside brushes.  It will not check for weather contents outside of these zones.
3141 */
SP_misc_weather_zone(gentity_t * ent)3142 void SP_misc_weather_zone( gentity_t *ent )
3143 {
3144 	gi.SetBrushModel(ent, ent->model);
3145 
3146 	char	temp[256];
3147 	sprintf( temp, "zone ( %f %f %f ) ( %f %f %f )",
3148 		ent->mins[0], ent->mins[1], ent->mins[2],
3149 		ent->maxs[0], ent->maxs[1], ent->maxs[2] );
3150 
3151 	G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue);
3152 
3153 //	gi.WE_AddWeatherZone(ent->mins, ent->maxs);
3154 	G_FreeEntity(ent);
3155 }
3156 
SP_misc_cubemap(gentity_t * ent)3157 void SP_misc_cubemap( gentity_t *ent )
3158 {
3159 	G_FreeEntity( ent );
3160 }
3161