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