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