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 #include "g_headers.h"
25 
26 #include "g_local.h"
27 #include "g_functions.h"
28 #include "bg_public.h"
29 
30 extern cvar_t *g_spskill;
31 
32 //
33 // Helper functions
34 //
35 //------------------------------------------------------------
SetMiscModelModels(char * modelNameString,gentity_t * ent,qboolean damage_model)36 void SetMiscModelModels( char *modelNameString, gentity_t *ent, qboolean damage_model )
37 {
38 	char	damageModel[MAX_QPATH];
39 	char	chunkModel[MAX_QPATH];
40 	int		len;
41 
42 	//Main model
43 	ent->s.modelindex = G_ModelIndex( modelNameString );
44 
45 	if ( damage_model )
46 	{
47 		len = strlen( modelNameString ) - 4; // extract the extension
48 
49 		//Dead/damaged model
50 		strncpy( damageModel, modelNameString, len );
51 		damageModel[len] = 0;
52 		strncpy( chunkModel, damageModel, sizeof(chunkModel));
53 		strcat( damageModel, "_d1.md3" );
54 		ent->s.modelindex2 = G_ModelIndex( damageModel );
55 
56 		ent->spawnflags |= 4; // deadsolid
57 
58 		//Chunk model
59 		strcat( chunkModel, "_c1.md3" );
60 		ent->s.modelindex3 = G_ModelIndex( chunkModel );
61 	}
62 }
63 
64 //------------------------------------------------------------
SetMiscModelDefaults(gentity_t * ent,useFunc_t use_func,char * material,int solid_mask,int animFlag,qboolean take_damage,qboolean damage_model=qfalse)65 void SetMiscModelDefaults( gentity_t *ent, useFunc_t use_func, char *material, int solid_mask,int animFlag,
66 									qboolean take_damage, qboolean damage_model = qfalse )
67 {
68 	// Apply damage and chunk models if they exist
69 	SetMiscModelModels( ent->model, ent, damage_model );
70 
71 	ent->s.eFlags = animFlag;
72 	ent->svFlags |= SVF_PLAYER_USABLE;
73 	ent->contents = solid_mask;
74 
75 	G_SetOrigin( ent, ent->s.origin );
76 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
77 	gi.linkentity (ent);
78 
79 	// Set a generic use function
80 
81 	ent->e_UseFunc = use_func;
82 /*	if (use_func == useF_health_use)
83 	{
84 		G_SoundIndex("sound/player/suithealth.wav");
85 	}
86 	else if (use_func == useF_ammo_use )
87 	{
88 		G_SoundIndex("sound/player/suitenergy.wav");
89 	}
90 */
91 	G_SpawnInt( "material", material, (int *)&ent->material );
92 
93 	if (ent->health)
94 	{
95 		ent->max_health = ent->health;
96 		ent->takedamage = take_damage;
97 		ent->e_PainFunc = painF_misc_model_breakable_pain;
98 		ent->e_DieFunc  = dieF_misc_model_breakable_die;
99 	}
100 }
101 
HealthStationSettings(gentity_t * ent)102 void HealthStationSettings(gentity_t *ent)
103 {
104 	G_SpawnInt( "count", "0", &ent->count );
105 
106 	if (!ent->count)
107 	{
108 		switch (g_spskill->integer)
109 		{
110 		case 0:	//	EASY
111 			ent->count = 100;
112 			break;
113 		case 1:	//	MEDIUM
114 			ent->count = 75;
115 			break;
116 		default :
117 		case 2:	//	HARD
118 			ent->count = 50;
119 			break;
120 		}
121 	}
122 }
123 
124 
CrystalAmmoSettings(gentity_t * ent)125 void CrystalAmmoSettings(gentity_t *ent)
126 {
127 	G_SpawnInt( "count", "0", &ent->count );
128 
129 	if (!ent->count)
130 	{
131 		switch (g_spskill->integer)
132 		{
133 		case 0:	//	EASY
134 			ent->count = 75;
135 			break;
136 		case 1:	//	MEDIUM
137 			ent->count = 75;
138 			break;
139 		default :
140 		case 2:	//	HARD
141 			ent->count = 75;
142 			break;
143 		}
144 	}
145 }
146 
147 
148 //------------------------------------------------------------
149 
150 //------------------------------------------------------------
151 /*QUAKED misc_model_ghoul (1 0 0) (-16 -16 -37) (16 16 32)
152 "model"		arbitrary .glm file to display
153 "health" - how much health the model has - default 60 (zero makes non-breakable)
154 */
155 //------------------------------------------------------------
156 #include "anims.h"
157 extern qboolean G_ParseAnimFileSet( const char *filename, const char *animCFG, int *animFileIndex );
158 int temp_animFileIndex;
set_MiscAnim(gentity_t * ent)159 void set_MiscAnim( gentity_t *ent)
160 {
161 	animation_t *animations = level.knownAnimFileSets[temp_animFileIndex].animations;
162 	if (ent->playerModel & 1)
163 	{
164 		int anim = BOTH_STAND3;
165 		float animSpeed = 50.0f / animations[anim].frameLerp;
166 
167 		// yes, its the same animation, so work out where we are in the leg anim, and blend us
168 		gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", animations[anim].firstFrame,
169 							(animations[anim].numFrames -1 )+ animations[anim].firstFrame,
170 							BONE_ANIM_OVERRIDE_FREEZE | BONE_ANIM_BLEND , animSpeed, (cg.time?cg.time:level.time), -1, 350);
171 	}
172 	else
173 	{
174 		int anim = BOTH_PAIN3;
175 		float animSpeed = 50.0f / animations[anim].frameLerp;
176 		gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", animations[anim].firstFrame,
177 						(animations[anim].numFrames -1 )+ animations[anim].firstFrame,
178 						BONE_ANIM_OVERRIDE_FREEZE | BONE_ANIM_BLEND, animSpeed, (cg.time?cg.time:level.time), -1, 350);
179 	}
180 	ent->nextthink = level.time + 900;
181 	ent->playerModel++;
182 
183 }
184 
SP_misc_model_ghoul(gentity_t * ent)185 void SP_misc_model_ghoul( gentity_t *ent )
186 {
187 #if 1
188 	ent->s.modelindex = G_ModelIndex( ent->model );
189 	gi.G2API_InitGhoul2Model(ent->ghoul2, ent->model, ent->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0);
190 	ent->s.radius = 50;
191 
192 	G_SetOrigin( ent, ent->s.origin );
193 	G_SetAngles( ent, ent->s.angles );
194 
195 	qboolean bHasScale = G_SpawnVector( "modelscale_vec", "1 1 1", ent->s.modelScale );
196 	if ( !bHasScale ) {
197 		float temp;
198 		G_SpawnFloat( "modelscale", "0", &temp );
199 		if ( temp != 0.0f ) {
200 			ent->s.modelScale[0] = ent->s.modelScale[1] = ent->s.modelScale[2] = temp;
201 			bHasScale = qtrue;
202 		}
203 	}
204 	if ( bHasScale ) {
205 		//scale the x axis of the bbox up.
206 		ent->maxs[0] *= ent->s.modelScale[0];
207 		ent->mins[0] *= ent->s.modelScale[0];
208 
209 		//scale the y axis of the bbox up.
210 		ent->maxs[1] *= ent->s.modelScale[1];
211 		ent->mins[1] *= ent->s.modelScale[1];
212 
213 		//scale the z axis of the bbox up and adjust origin accordingly
214 		ent->maxs[2] *= ent->s.modelScale[2];
215 		float oldMins2 = ent->mins[2];
216 		ent->mins[2] *= ent->s.modelScale[2];
217 		ent->s.origin[2] += (oldMins2 - ent->mins[2]);
218 	}
219 
220 	gi.linkentity (ent);
221 #else
222 	char name1[200] = "models/players/kyle/model.glm";
223 	ent->s.modelindex = G_ModelIndex( name1 );
224 
225 	gi.G2API_InitGhoul2Model(ent->ghoul2, name1, ent->s.modelindex);
226 	ent->s.radius = 150;
227 
228 			// we found the model ok - load it's animation config
229  	if ( !G_ParseAnimFileSet( "kyle", "_humanoid", &temp_animFileIndex ) )
230  	{
231  		Com_Printf( S_COLOR_RED"Failed to load animation file set models/players/jedi/animation.cfg\n");
232  	}
233 
234 
235 	ent->s.angles[0] = 0;
236 	ent->s.angles[1] = 90;
237 	ent->s.angles[2] = 0;
238 
239 	ent->s.origin[2] = 20;
240 	ent->s.origin[1] = 80;
241 //	ent->s.modelScale[0] = ent->s.modelScale[1] = ent->s.modelScale[2] = 0.8f;
242 
243 	VectorSet (ent->mins, -16, -16, -37);
244 	VectorSet (ent->maxs, 16, 16, 32);
245 //#if _DEBUG
246 //loadsavecrash
247 //	VectorCopy(ent->mins, ent->s.mins);
248 //	VectorCopy(ent->maxs, ent->s.maxs);
249 //#endif
250 	ent->contents = CONTENTS_BODY;
251 	ent->clipmask = MASK_NPCSOLID;
252 
253 	G_SetOrigin( ent, ent->s.origin );
254 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
255 	ent->health = 1000;
256 
257 //	ent->s.modelindex = G_ModelIndex( "models/weapons2/blaster_r/g2blaster_w.glm" );
258 //	gi.G2API_InitGhoul2Model(ent->ghoul2, "models/weapons2/blaster_r/g2blaster_w.glm", ent->s.modelindex);
259 //	gi.G2API_AddBolt(&ent->ghoul2[0], "*weapon");
260 //	gi.G2API_AttachG2Model(&ent->ghoul2[1],&ent->ghoul2[0], 0, 0);
261 
262 	gi.linkentity (ent);
263 
264 	animation_t *animations = level.knownAnimFileSets[temp_animFileIndex].animations;
265 	int anim = BOTH_STAND3;
266 	float animSpeed = 50.0f / animations[anim].frameLerp;
267 	gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", animations[anim].firstFrame,
268 					(animations[anim].numFrames -1 )+ animations[anim].firstFrame,
269 					BONE_ANIM_OVERRIDE_FREEZE , animSpeed, cg.time);
270 
271 //	int test = gi.G2API_GetSurfaceRenderStatus(&ent->ghoul2[0], "l_hand");
272 //	gi.G2API_SetSurfaceOnOff(&ent->ghoul2[0], "l_arm",0x00000100);
273 //	test = gi.G2API_GetSurfaceRenderStatus(&ent->ghoul2[0], "l_hand");
274 
275 //	gi.G2API_SetNewOrigin(&ent->ghoul2[0], gi.G2API_AddBolt(&ent->ghoul2[0], "rhang_tag_bone"));
276 //	ent->s.apos.trDelta[1] = 10;
277 //	ent->s.apos.trType = TR_LINEAR;
278 
279 
280 	ent->nextthink = level.time + 1000;
281 	ent->e_ThinkFunc = thinkF_set_MiscAnim;
282 #endif
283 }
284 
285 
286 #define RACK_BLASTER	1
287 #define RACK_REPEATER	2
288 #define RACK_ROCKET		4
289 
290 /*QUAKED misc_model_gun_rack (1 0 0.25) (-14 -14 -4) (14 14 30) BLASTER REPEATER ROCKET
291 #MODELNAME="models/map_objects/kejim/weaponsrack.md3"
292 
293 NOTE: can mix and match these spawnflags to get multi-weapon racks.  If only one type is checked the rack will be full of those weapons
294 BLASTER - Puts one or more blaster guns on the rack.
295 REPEATER - Puts one or more repeater guns on the rack.
296 ROCKET - Puts one or more rocket launchers on the rack.
297 */
298 
GunRackAddItem(gitem_t * gun,vec3_t org,vec3_t angs,float ffwd,float fright,float fup)299 void GunRackAddItem( gitem_t *gun, vec3_t org, vec3_t angs, float ffwd, float fright, float fup )
300 {
301 	vec3_t		fwd, right;
302 	gentity_t	*it_ent = G_Spawn();
303 	qboolean	rotate = qtrue;
304 
305 	AngleVectors( angs, fwd, right, NULL );
306 
307 	if ( it_ent && gun )
308 	{
309 		// FIXME: scaling the ammo will probably need to be tweaked to a reasonable amount...adjust as needed
310 		// Set base ammo per type
311 		if ( gun->giType == IT_WEAPON )
312 		{
313 			it_ent->spawnflags |= 16;// VERTICAL
314 
315 			switch( gun->giTag )
316 			{
317 			case WP_BLASTER:
318 				it_ent->count = 15;
319 				break;
320 			case WP_REPEATER:
321 				it_ent->count = 100;
322 				break;
323 			case WP_ROCKET_LAUNCHER:
324 				it_ent->count = 4;
325 				break;
326 			}
327 		}
328 		else
329 		{
330 			rotate = qfalse;
331 
332 			// must deliberately make it small, or else the objects will spawn inside of each other.
333 			VectorSet( it_ent->maxs, 6.75f, 6.75f, 6.75f );
334 			VectorScale( it_ent->maxs, -1, it_ent->mins );
335 		}
336 
337 		it_ent->spawnflags |= 1;// ITMSF_SUSPEND
338 		it_ent->classname = gun->classname;
339 		G_SpawnItem( it_ent, gun );
340 
341 		// FinishSpawningItem handles everything, so clear the thinkFunc that was set in G_SpawnItem
342 		FinishSpawningItem( it_ent );
343 
344 		if ( gun->giType == IT_AMMO )
345 		{
346 			if ( gun->giTag == AMMO_BLASTER ) // I guess this just has to use different logic??
347 			{
348 				if ( g_spskill->integer >= 2 )
349 				{
350 					it_ent->count += 10; // give more on higher difficulty because there will be more/harder enemies?
351 				}
352 			}
353 			else
354 			{
355 				// scale ammo based on skill
356 				switch ( g_spskill->integer )
357 				{
358 				case 0: // do default
359 					break;
360 				case 1:
361 					it_ent->count *= 0.75f;
362 					break;
363 				case 2:
364 					it_ent->count *= 0.5f;
365 					break;
366 				}
367 			}
368 		}
369 
370 		it_ent->nextthink = 0;
371 
372 		VectorCopy( org, it_ent->s.origin );
373 		VectorMA( it_ent->s.origin, fright, right, it_ent->s.origin );
374 		VectorMA( it_ent->s.origin, ffwd, fwd, it_ent->s.origin );
375 		it_ent->s.origin[2] += fup;
376 
377 		VectorCopy( angs, it_ent->s.angles );
378 
379 		// by doing this, we can force the amount of ammo we desire onto the weapon for when it gets picked-up
380 		it_ent->flags |= ( FL_DROPPED_ITEM | FL_FORCE_PULLABLE_ONLY );
381 		it_ent->physicsBounce = 0.1f;
382 
383 		for ( int t = 0; t < 3; t++ )
384 		{
385 			if ( rotate )
386 			{
387 				if ( t == YAW )
388 				{
389 					it_ent->s.angles[t] = AngleNormalize180( it_ent->s.angles[t] + 180 + Q_flrand(-1.0f, 1.0f) * 14 );
390 				}
391 				else
392 				{
393 					it_ent->s.angles[t] = AngleNormalize180( it_ent->s.angles[t] + Q_flrand(-1.0f, 1.0f) * 4 );
394 				}
395 			}
396 			else
397 			{
398 				if ( t == YAW )
399 				{
400 					it_ent->s.angles[t] = AngleNormalize180( it_ent->s.angles[t] + 90 + Q_flrand(-1.0f, 1.0f) * 4 );
401 				}
402 			}
403 		}
404 
405 		G_SetAngles( it_ent, it_ent->s.angles );
406 		G_SetOrigin( it_ent, it_ent->s.origin );
407 		gi.linkentity( it_ent );
408 	}
409 }
410 
411 //---------------------------------------------
SP_misc_model_gun_rack(gentity_t * ent)412 void SP_misc_model_gun_rack( gentity_t *ent )
413 {
414 	gitem_t		*blaster = NULL, *repeater = NULL, *rocket = NULL;
415 	int			ct = 0;
416 	float		ofz[3];
417 	gitem_t		*itemList[3];
418 
419 	// If BLASTER is checked...or nothing is checked then we'll do blasters
420 	if (( ent->spawnflags & RACK_BLASTER ) || !(ent->spawnflags & ( RACK_BLASTER | RACK_REPEATER | RACK_ROCKET )))
421 	{
422 		blaster	= FindItemForWeapon( WP_BLASTER );
423 	}
424 
425 	if (( ent->spawnflags & RACK_REPEATER ))
426 	{
427 		repeater = FindItemForWeapon( WP_REPEATER );
428 	}
429 
430 	if (( ent->spawnflags & RACK_ROCKET ))
431 	{
432 		rocket = FindItemForWeapon( WP_ROCKET_LAUNCHER );
433 	}
434 
435 	//---------weapon types
436 	if ( blaster )
437 	{
438 		ofz[ct] = 23.0f;
439 		itemList[ct++] = blaster;
440 	}
441 
442 	if ( repeater )
443 	{
444 		ofz[ct] = 24.5f;
445 		itemList[ct++] = repeater;
446 	}
447 
448 	if ( rocket )
449 	{
450 		ofz[ct] = 25.5f;
451 		itemList[ct++] = rocket;
452 	}
453 
454 	if ( ct ) //..should always have at least one item on their, but just being safe
455 	{
456 		for ( ; ct < 3 ; ct++ )
457 		{
458 			ofz[ct] = ofz[0];
459 			itemList[ct] = itemList[0]; // first weapon ALWAYS propagates to fill up the shelf
460 		}
461 	}
462 
463 	// now actually add the items to the shelf...validate that we have a list to add
464 	if ( ct )
465 	{
466 		for ( int i = 0; i < ct; i++ )
467 		{
468 			GunRackAddItem( itemList[i], ent->s.origin, ent->s.angles, Q_flrand(-1.0f, 1.0f) * 2, ( i - 1 ) * 9 + Q_flrand(-1.0f, 1.0f) * 2, ofz[i] );
469 		}
470 	}
471 
472 	ent->s.modelindex = G_ModelIndex( "models/map_objects/kejim/weaponsrack.md3" );
473 
474 	G_SetOrigin( ent, ent->s.origin );
475 	G_SetAngles( ent, ent->s.angles );
476 
477 	ent->contents = CONTENTS_SOLID;
478 
479 	gi.linkentity( ent );
480 }
481 
482 #define RACK_METAL_BOLTS	2
483 #define RACK_ROCKETS		4
484 #define RACK_WEAPONS		8
485 #define RACK_HEALTH			16
486 #define RACK_PWR_CELL		32
487 #define RACK_NO_FILL		64
488 
489 /*QUAKED misc_model_ammo_rack (1 0 0.25) (-14 -14 -4) (14 14 30) BLASTER METAL_BOLTS ROCKETS WEAPON HEALTH PWR_CELL NO_FILL
490 #MODELNAME="models/map_objects/kejim/weaponsrung.md3"
491 
492 NOTE: can mix and match these spawnflags to get multi-ammo racks.  If only one type is checked the rack will be full of that ammo.  Only three ammo packs max can be displayed.
493 
494 
495 BLASTER - Adds one or more ammo packs that are compatible with Blasters and the Bryar pistol.
496 METAL_BOLTS - Adds one or more metal bolt ammo packs that are compatible with the heavy repeater and the flechette gun
497 ROCKETS - Puts one or more rocket packs on a rack.
498 WEAPON - adds a weapon matching a selected ammo type to the rack.
499 HEALTH - adds a health pack to the top shelf of the ammo rack
500 PWR_CELL - Adds one or more power cell packs that are compatible with the Disuptor, bowcaster, and demp2
501 NO_FILL - Only puts selected ammo on the rack, it never fills up all three slots if only one or two items were checked
502 */
503 
504 extern gitem_t	*FindItemForAmmo( ammo_t ammo );
505 
506 //---------------------------------------------
SP_misc_model_ammo_rack(gentity_t * ent)507 void SP_misc_model_ammo_rack( gentity_t *ent )
508 {
509 // If BLASTER is checked...or nothing is checked then we'll do blasters
510 	if (( ent->spawnflags & RACK_BLASTER ) || !(ent->spawnflags & ( RACK_BLASTER | RACK_METAL_BOLTS | RACK_ROCKETS | RACK_PWR_CELL )))
511 	{
512 		if ( ent->spawnflags & RACK_WEAPONS )
513 		{
514 			RegisterItem( FindItemForWeapon( WP_BLASTER ));
515 		}
516 		RegisterItem( FindItemForAmmo( AMMO_BLASTER ));
517 	}
518 
519 	if (( ent->spawnflags & RACK_METAL_BOLTS ))
520 	{
521 		if ( ent->spawnflags & RACK_WEAPONS )
522 		{
523 			RegisterItem( FindItemForWeapon( WP_REPEATER ));
524 		}
525 		RegisterItem( FindItemForAmmo( AMMO_METAL_BOLTS ));
526 	}
527 
528 	if (( ent->spawnflags & RACK_ROCKETS ))
529 	{
530 		if ( ent->spawnflags & RACK_WEAPONS )
531 		{
532 			RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER ));
533 		}
534 		RegisterItem( FindItemForAmmo( AMMO_ROCKETS ));
535 	}
536 
537 	if (( ent->spawnflags & RACK_PWR_CELL ))
538 	{
539 		RegisterItem( FindItemForAmmo( AMMO_POWERCELL ));
540 	}
541 
542 	if (( ent->spawnflags & RACK_HEALTH ))
543 	{
544 		RegisterItem( FindItem( "item_medpak_instant" ));
545 	}
546 
547 	ent->e_ThinkFunc = thinkF_spawn_rack_goods;
548 	ent->nextthink = level.time + 100;
549 
550 	G_SetOrigin( ent, ent->s.origin );
551 	G_SetAngles( ent, ent->s.angles );
552 
553 	ent->contents = CONTENTS_SHOTCLIP|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//CONTENTS_SOLID;//so use traces can go through them
554 
555 	gi.linkentity( ent );
556 }
557 
558 // AMMO RACK!!
spawn_rack_goods(gentity_t * ent)559 void spawn_rack_goods( gentity_t *ent )
560 {
561 	float		v_off = 0;
562 	gitem_t		*blaster = NULL, *metal_bolts = NULL, *rockets = NULL, *it = NULL;
563 	gitem_t		*am_blaster = NULL, *am_metal_bolts = NULL, *am_rockets = NULL, *am_pwr_cell = NULL;
564 	gitem_t		*health = NULL;
565 	int			pos = 0, ct = 0;
566 	gitem_t		*itemList[4]; // allocating 4, but we only use 3.  done so I don't have to validate that the array isn't full before I add another
567 
568 	gi.unlinkentity( ent );
569 
570 	// If BLASTER is checked...or nothing is checked then we'll do blasters
571 	if (( ent->spawnflags & RACK_BLASTER ) || !(ent->spawnflags & ( RACK_BLASTER | RACK_METAL_BOLTS | RACK_ROCKETS | RACK_PWR_CELL )))
572 	{
573 		if ( ent->spawnflags & RACK_WEAPONS )
574 		{
575 			blaster	= FindItemForWeapon( WP_BLASTER );
576 		}
577 		am_blaster	= FindItemForAmmo( AMMO_BLASTER );
578 	}
579 
580 	if (( ent->spawnflags & RACK_METAL_BOLTS ))
581 	{
582 		if ( ent->spawnflags & RACK_WEAPONS )
583 		{
584 			metal_bolts = FindItemForWeapon( WP_REPEATER );
585 		}
586 		am_metal_bolts = FindItemForAmmo( AMMO_METAL_BOLTS );
587 	}
588 
589 	if (( ent->spawnflags & RACK_ROCKETS ))
590 	{
591 		if ( ent->spawnflags & RACK_WEAPONS )
592 		{
593 			rockets = FindItemForWeapon( WP_ROCKET_LAUNCHER );
594 		}
595 		am_rockets = FindItemForAmmo( AMMO_ROCKETS );
596 	}
597 
598 	if (( ent->spawnflags & RACK_PWR_CELL ))
599 	{
600 		am_pwr_cell = FindItemForAmmo( AMMO_POWERCELL );
601 	}
602 
603 	if (( ent->spawnflags & RACK_HEALTH ))
604 	{
605 		health = FindItem( "item_medpak_instant" );
606 		RegisterItem( health );
607 	}
608 
609 	//---------Ammo types
610 	if ( am_blaster )
611 	{
612 		itemList[ct++] = am_blaster;
613 	}
614 
615 	if ( am_metal_bolts )
616 	{
617 		itemList[ct++] = am_metal_bolts;
618 	}
619 
620 	if ( am_pwr_cell )
621 	{
622 		itemList[ct++] = am_pwr_cell;
623 	}
624 
625 	if ( am_rockets )
626 	{
627 		itemList[ct++] = am_rockets;
628 	}
629 
630 	if ( !(ent->spawnflags & RACK_NO_FILL) && ct ) //double negative..should always have at least one item on there, but just being safe
631 	{
632 		for ( ; ct < 3 ; ct++ )
633 		{
634 			itemList[ct] = itemList[0]; // first item ALWAYS propagates to fill up the shelf
635 		}
636 	}
637 
638 	// now actually add the items to the shelf...validate that we have a list to add
639 	if ( ct )
640 	{
641 		for ( int i = 0; i < ct; i++ )
642 		{
643 			GunRackAddItem( itemList[i], ent->s.origin, ent->s.angles, Q_flrand(-1.0f, 1.0f) * 0.5f, (i-1)* 8, 7.0f );
644 		}
645 	}
646 
647 	// -----Weapon option
648 	if ( ent->spawnflags & RACK_WEAPONS )
649 	{
650 		if ( !(ent->spawnflags & ( RACK_BLASTER | RACK_METAL_BOLTS | RACK_ROCKETS | RACK_PWR_CELL )))
651 		{
652 			// nothing was selected, so we assume blaster pack
653 			it = blaster;
654 		}
655 		else
656 		{
657 			// if weapon is checked...and so are one or more ammo types, then pick a random weapon to display..always give weaker weapons first
658 			if ( blaster )
659 			{
660 				it = blaster;
661 				v_off = 25.5f;
662 			}
663 			else if ( metal_bolts )
664 			{
665 				it = metal_bolts;
666 				v_off = 27.0f;
667 			}
668 			else if ( rockets )
669 			{
670 				it = rockets;
671 				v_off = 28.0f;
672 			}
673 		}
674 
675 		if ( it )
676 		{
677 			// since we may have to put up a health pack on the shelf, we should know where we randomly put
678 			//	the gun so we don't put the pack on the same spot..so pick either the left or right side
679 			pos = ( Q_flrand(0.0f, 1.0f) > .5 ) ? -1 : 1;
680 
681 			GunRackAddItem( it, ent->s.origin, ent->s.angles, Q_flrand(-1.0f, 1.0f) * 2, ( Q_flrand(0.0f, 1.0f) * 6 + 4 ) * pos, v_off );
682 		}
683 	}
684 
685 	// ------Medpack
686 	if (( ent->spawnflags & RACK_HEALTH ) && health )
687 	{
688 		if ( !pos )
689 		{
690 			// we haven't picked a side already...
691 			pos = ( Q_flrand(0.0f, 1.0f) > .5 ) ? -1 : 1;
692 		}
693 		else
694 		{
695 			// switch to the opposite side
696 			pos *= -1;
697 		}
698 
699 		GunRackAddItem( health, ent->s.origin, ent->s.angles, Q_flrand(-1.0f, 1.0f) * 0.5f, ( Q_flrand(0.0f, 1.0f) * 4 + 4 ) * pos, 24 );
700 	}
701 
702 	ent->s.modelindex = G_ModelIndex( "models/map_objects/kejim/weaponsrung.md3" );
703 
704 	G_SetOrigin( ent, ent->s.origin );
705 	G_SetAngles( ent, ent->s.angles );
706 
707 	gi.linkentity( ent );
708 }
709 
710 #define DROP_MEDPACK	1
711 #define DROP_SHIELDS	2
712 #define DROP_BACTA		4
713 #define DROP_BATTERIES	8
714 
715 /*QUAKED misc_model_cargo_small (1 0 0.25) (-14 -14 -4) (14 14 30) MEDPACK SHIELDS BACTA BATTERIES
716 #MODELNAME="models/map_objects/kejim/cargo_small.md3"
717 
718   Cargo crate that can only be destroyed by heavy class weapons ( turrets, emplaced guns, at-st )  Can spawn useful things when it breaks
719 
720 MEDPACK - instant use medpacks
721 SHIELDS - instant shields
722 BACTA - bacta tanks
723 BATTERIES -
724 
725 "health" - how much damage to take before blowing up ( default 25 )
726 "splashRadius" - damage range when it explodes ( default 96 )
727 "splashDamage" - damage to do within explode range ( default 1 )
728 
729 */
730 extern gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity, char *target );
731 
misc_model_cargo_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)732 void misc_model_cargo_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
733 {
734 	int		flags;
735 	vec3_t	org, temp;
736 	gitem_t *health = NULL, *shields = NULL, *bacta = NULL, *batteries = NULL;
737 
738 	// copy these for later
739 	flags = self->spawnflags;
740 	VectorCopy( self->currentOrigin, org );
741 
742 	// we already had spawn flags, but we don't care what they were...we just need to set up the flags we want for misc_model_breakable_die
743 	self->spawnflags = 8; // NO_DMODEL
744 
745 	// pass through to get the effects and such
746 	misc_model_breakable_die( self, inflictor, attacker, damage, mod );
747 
748 	// now that the model is broken, we can safely spawn these in it's place without them being in solid
749 	temp[2] = org[2] + 16;
750 
751 	// annoying, but spawn each thing in its own little quadrant so that they don't end up on top of each other
752 	if (( flags & DROP_MEDPACK ))
753 	{
754 		health = FindItem( "item_medpak_instant" );
755 
756 		if ( health )
757 		{
758 			temp[0] = org[0] + Q_flrand(-1.0f, 1.0f) * 8 + 16;
759 			temp[1] = org[1] + Q_flrand(-1.0f, 1.0f) * 8 + 16;
760 
761 			LaunchItem( health, temp, (float *)vec3_origin, NULL );
762 		}
763 	}
764 	if (( flags & DROP_SHIELDS ))
765 	{
766 		shields = FindItem( "item_shield_sm_instant" );
767 
768 		if ( shields )
769 		{
770 			temp[0] = org[0] + Q_flrand(-1.0f, 1.0f) * 8 - 16;
771 			temp[1] = org[1] + Q_flrand(-1.0f, 1.0f) * 8 + 16;
772 
773 			LaunchItem( shields, temp, (float *)vec3_origin, NULL );
774 		}
775 	}
776 
777 	if (( flags & DROP_BACTA ))
778 	{
779 		bacta = FindItem( "item_bacta" );
780 
781 		if ( bacta )
782 		{
783 			temp[0] = org[0] + Q_flrand(-1.0f, 1.0f) * 8 - 16;
784 			temp[1] = org[1] + Q_flrand(-1.0f, 1.0f) * 8 - 16;
785 
786 			LaunchItem( bacta, temp, (float *)vec3_origin, NULL );
787 		}
788 	}
789 
790 	if (( flags & DROP_BATTERIES ))
791 	{
792 		batteries = FindItem( "item_battery" );
793 
794 		if ( batteries )
795 		{
796 			temp[0] = org[0] + Q_flrand(-1.0f, 1.0f) * 8 + 16;
797 			temp[1] = org[1] + Q_flrand(-1.0f, 1.0f) * 8 - 16;
798 
799 			LaunchItem( batteries, temp, (float *)vec3_origin, NULL );
800 		}
801 	}
802 }
803 
804 //---------------------------------------------
SP_misc_model_cargo_small(gentity_t * ent)805 void SP_misc_model_cargo_small( gentity_t *ent )
806 {
807 	G_SpawnInt( "splashRadius", "96", &ent->splashRadius );
808 	G_SpawnInt( "splashDamage", "1", &ent->splashDamage );
809 
810 	if (( ent->spawnflags & DROP_MEDPACK ))
811 	{
812 		RegisterItem( FindItem( "item_medpak_instant" ));
813 	}
814 
815 	if (( ent->spawnflags & DROP_SHIELDS ))
816 	{
817 		RegisterItem( FindItem( "item_shield_sm_instant" ));
818 	}
819 
820 	if (( ent->spawnflags & DROP_BACTA ))
821 	{
822 		RegisterItem( FindItem( "item_bacta" ));
823 	}
824 
825 	if (( ent->spawnflags & DROP_BATTERIES ))
826 	{
827 		RegisterItem( FindItem( "item_battery" ));
828 	}
829 
830 	G_SpawnInt( "health", "25", &ent->health );
831 
832 	SetMiscModelDefaults( ent, useF_NULL, "11", CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, 0, qtrue, qfalse );
833 	ent->s.modelindex2 = G_ModelIndex("/models/map_objects/kejim/cargo_small.md3");	// Precache model
834 
835 	// we only take damage from a heavy weapon class missile
836 	ent->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
837 
838 	ent->e_DieFunc = dieF_misc_model_cargo_die;
839 
840 	ent->radius = 1.5f; // scale number of chunks spawned
841 }
842 
843