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_headers.h"
24
25 #include "g_local.h"
26 #include "g_functions.h"
27 #include "../cgame/cg_media.h"
28
29 extern team_t TranslateTeamName( const char *name );
30
31 //client side shortcut hacks from cg_local.h
32 //extern void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke );
33 extern void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType );
34 extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs,
35 float speed, int numChunks, material_t chunkType, int customChunk, float baseScale );
36 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
37 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
38
39 extern qboolean player_locked;
40
41 //---------------------------------------------------
CacheChunkEffects(material_t material)42 static void CacheChunkEffects( material_t material )
43 {
44 switch( material )
45 {
46 default:
47 break;
48 case MAT_GLASS:
49 G_EffectIndex( "chunks/glassbreak" );
50 break;
51 case MAT_GLASS_METAL:
52 G_EffectIndex( "chunks/glassbreak" );
53 G_EffectIndex( "chunks/metalexplode" );
54 break;
55 case MAT_ELECTRICAL:
56 case MAT_ELEC_METAL:
57 G_EffectIndex( "chunks/sparkexplode" );
58 break;
59 case MAT_METAL:
60 case MAT_METAL2:
61 case MAT_METAL3:
62 case MAT_CRATE1:
63 case MAT_CRATE2:
64 G_EffectIndex( "chunks/metalexplode" );
65 break;
66 case MAT_GRATE1:
67 G_EffectIndex( "chunks/grateexplode" );
68 break;
69 case MAT_DRK_STONE:
70 case MAT_LT_STONE:
71 case MAT_GREY_STONE:
72 case MAT_WHITE_METAL: // what is this crap really supposed to be??
73 G_EffectIndex( "chunks/rockbreaklg" );
74 G_EffectIndex( "chunks/rockbreakmed" );
75 break;
76 case MAT_ROPE:
77 G_EffectIndex( "chunks/ropebreak" );
78 // G_SoundIndex(); // FIXME: give it a sound
79 break;
80 }
81 }
82
83 //--------------------------------------
funcBBrushDieGo(gentity_t * self)84 void funcBBrushDieGo (gentity_t *self)
85 {
86 vec3_t org, dir, up;
87 gentity_t *attacker = self->enemy;
88 float scale;
89 int numChunks, size = 0;
90 material_t chunkType = self->material;
91
92 // if a missile is stuck to us, blow it up so we don't look dumb
93 for ( int i = 0; i < MAX_GENTITIES; i++ )
94 {
95 if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
96 {
97 G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
98 }
99 }
100
101 //So chunks don't get stuck inside me
102 self->s.solid = 0;
103 self->contents = 0;
104 self->clipmask = 0;
105 gi.linkentity(self);
106
107 VectorSet(up, 0, 0, 1);
108
109 if ( self->target && attacker != NULL )
110 {
111 G_UseTargets(self, attacker);
112 }
113
114 VectorSubtract( self->absmax, self->absmin, org );// size
115
116 numChunks = Q_flrand(0.0f, 1.0f) * 6 + 18;
117
118 // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
119 // Volume is length * width * height...then break that volume down based on how many chunks we have
120 scale = sqrt( sqrt( org[0] * org[1] * org[2] )) * 1.75f;
121
122 if ( scale > 48 )
123 {
124 size = 2;
125 }
126 else if ( scale > 24 )
127 {
128 size = 1;
129 }
130
131 scale = scale / numChunks;
132
133 if ( self->radius > 0.0f )
134 {
135 // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
136 // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
137 numChunks *= self->radius;
138 }
139
140 VectorMA( self->absmin, 0.5, org, org );
141 VectorAdd( self->absmin,self->absmax, org );
142 VectorScale( org, 0.5f, org );
143
144 if ( attacker != NULL && attacker->client )
145 {
146 VectorSubtract( org, attacker->currentOrigin, dir );
147 VectorNormalize( dir );
148 }
149 else
150 {
151 VectorCopy(up, dir);
152 }
153
154 if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
155 {
156 // we are allowed to explode
157 CG_MiscModelExplosion( self->mins, self->maxs, size, chunkType );
158 }
159
160 if ( self->splashDamage > 0 && self->splashRadius > 0 )
161 {
162 //explode
163 AddSightEvent( attacker, org, 256, AEL_DISCOVERED, 100 );
164 AddSoundEvent( attacker, org, 128, AEL_DISCOVERED );
165 G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
166
167 gentity_t *te = G_TempEntity( org, EV_GENERAL_SOUND );
168 te->s.eventParm = G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
169 }
170 else
171 {//just break
172 AddSightEvent( attacker, org, 128, AEL_DISCOVERED );
173 AddSoundEvent( attacker, org, 64, AEL_SUSPICIOUS );
174 }
175
176 //FIXME: base numChunks off size?
177 CG_Chunks( self->s.number, org, dir, self->mins, self->maxs, 300, numChunks, chunkType, 0, scale );
178
179 gi.AdjustAreaPortalState( self, qtrue );
180 self->e_ThinkFunc = thinkF_G_FreeEntity;
181 self->nextthink = level.time + 50;
182 //G_FreeEntity( self );
183 }
184
funcBBrushDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)185 void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc)
186 {
187 self->takedamage = qfalse;//stop chain reaction runaway loops
188
189 G_SetEnemy(self, attacker);
190
191 if(self->delay)
192 {
193 self->e_ThinkFunc = thinkF_funcBBrushDieGo;
194 self->nextthink = level.time + floor(self->delay * 1000.0f);
195 return;
196 }
197
198 funcBBrushDieGo(self);
199 }
200
funcBBrushUse(gentity_t * self,gentity_t * other,gentity_t * activator)201 void funcBBrushUse (gentity_t *self, gentity_t *other, gentity_t *activator)
202 {
203 G_ActivateBehavior( self, BSET_USE );
204 if(self->spawnflags & 64)
205 {//Using it doesn't break it, makes it use it's targets
206 if(self->target && self->target[0])
207 {
208 G_UseTargets(self, activator);
209 }
210 }
211 else
212 {
213 funcBBrushDie(self, other, activator, self->health, MOD_UNKNOWN);
214 }
215 }
216
funcBBrushPain(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,vec3_t point,int damage,int mod,int hitLoc)217 void funcBBrushPain(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod,int hitLoc)
218 {
219 if ( self->painDebounceTime > level.time )
220 {
221 return;
222 }
223
224 if ( self->paintarget )
225 {
226 G_UseTargets2 (self, self->activator, self->paintarget);
227 }
228
229 G_ActivateBehavior( self, BSET_PAIN );
230
231 if ( self->material == MAT_DRK_STONE
232 || self->material == MAT_LT_STONE
233 || self->material == MAT_GREY_STONE )
234 {
235 vec3_t org, dir;
236 float scale;
237 VectorSubtract( self->absmax, self->absmin, org );// size
238 // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
239 // Volume is length * width * height...then break that volume down based on how many chunks we have
240 scale = VectorLength( org ) / 100.0f;
241 VectorMA( self->absmin, 0.5, org, org );
242 VectorAdd( self->absmin,self->absmax, org );
243 VectorScale( org, 0.5f, org );
244 if ( attacker != NULL && attacker->client )
245 {
246 VectorSubtract( attacker->currentOrigin, org, dir );
247 VectorNormalize( dir );
248 }
249 else
250 {
251 VectorSet( dir, 0, 0, 1 );
252 }
253 CG_Chunks( self->s.number, org, dir, self->mins, self->maxs, 300, Q_irand( 1, 3 ), self->material, 0, scale );
254 }
255
256 if ( self->wait == -1 )
257 {
258 self->e_PainFunc = painF_NULL;
259 return;
260 }
261
262 self->painDebounceTime = level.time + self->wait;
263 }
264
InitBBrush(gentity_t * ent)265 static void InitBBrush ( gentity_t *ent )
266 {
267 float light;
268 vec3_t color;
269 qboolean lightSet, colorSet;
270
271 VectorCopy( ent->s.origin, ent->pos1 );
272
273 gi.SetBrushModel( ent, ent->model );
274
275 ent->e_DieFunc = dieF_funcBBrushDie;
276
277 ent->svFlags |= SVF_BBRUSH;
278
279 // if the "model2" key is set, use a seperate model
280 // for drawing, but clip against the brushes
281 if ( ent->model2 )
282 {
283 ent->s.modelindex2 = G_ModelIndex( ent->model2 );
284 }
285
286 // if the "color" or "light" keys are set, setup constantLight
287 lightSet = G_SpawnFloat( "light", "100", &light );
288 colorSet = G_SpawnVector( "color", "1 1 1", color );
289 if ( lightSet || colorSet )
290 {
291 int r, g, b, i;
292
293 r = color[0] * 255;
294 if ( r > 255 ) {
295 r = 255;
296 }
297 g = color[1] * 255;
298 if ( g > 255 ) {
299 g = 255;
300 }
301 b = color[2] * 255;
302 if ( b > 255 ) {
303 b = 255;
304 }
305 i = light / 4;
306 if ( i > 255 ) {
307 i = 255;
308 }
309 ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
310 }
311
312 if(ent->spawnflags & 128)
313 {//Can be used by the player's BUTTON_USE
314 ent->svFlags |= SVF_PLAYER_USABLE;
315 }
316
317 ent->s.eType = ET_MOVER;
318 gi.linkentity (ent);
319
320 ent->s.pos.trType = TR_STATIONARY;
321 VectorCopy( ent->pos1, ent->s.pos.trBase );
322 }
323
funcBBrushTouch(gentity_t * ent,gentity_t * other,trace_t * trace)324 void funcBBrushTouch( gentity_t *ent, gentity_t *other, trace_t *trace )
325 {
326 }
327
328 /*QUAKED func_breakable (0 .8 .5) ? INVINCIBLE IMPACT CRUSHER THIN SABERONLY HEAVY_WEAP USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
329 INVINCIBLE - can only be broken by being used
330 IMPACT - does damage on impact
331 CRUSHER - won't reverse movement when hit an obstacle
332 THIN - can be broken by impact damage, like glass
333 SABERONLY - only takes damage from sabers
334 HEAVY_WEAP - only takes damage by a heavy weapon, like an emplaced gun or AT-ST gun.
335 USE_NOT_BREAK - Using it doesn't make it break, still can be destroyed by damage
336 PLAYER_USE - Player can use it with the use button
337 NO_EXPLOSION - Does not play an explosion effect, though will still create chunks if specified
338
339 When destroyed, fires it's trigger and chunks and plays sound "noise" or sound for type if no noise specified
340
341 "targetname" entities with matching target will fire it
342 "paintarget" target to fire when hit (but not destroyed)
343 "wait" how long minimum to wait between firing paintarget each time hit
344 "delay" When killed or used, how long (in seconds) to wait before blowing up (none by default)
345 "model2" .md3 model to also draw
346 "target" all entities with a matching targetname will be used when this is destoryed
347 "health" default is 10
348 "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks
349
350 Damage: default is none
351 "splashDamage" - damage to do
352 "splashRadius" - radius for above damage
353
354 "team" - This cannot take damage from members of this team:
355 "player"
356 "neutral"
357 "enemy"
358
359 Don't know if these work:
360 "color" constantLight color
361 "light" constantLight radius
362
363 "material" - default is "0 - MAT_METAL" - choose from this list:
364 0 = MAT_METAL (basic blue-grey scorched-DEFAULT)
365 1 = MAT_GLASS
366 2 = MAT_ELECTRICAL (sparks only)
367 3 = MAT_ELEC_METAL (METAL2 chunks and sparks)
368 4 = MAT_DRK_STONE (brown stone chunks)
369 5 = MAT_LT_STONE (tan stone chunks)
370 6 = MAT_GLASS_METAL (glass and METAL2 chunks)
371 7 = MAT_METAL2 (electronic type of metal)
372 8 = MAT_NONE (no chunks)
373 9 = MAT_GREY_STONE (grey colored stone)
374 10 = MAT_METAL3 (METAL and METAL2 chunk combo)
375 11 = MAT_CRATE1 (yellow multi-colored crate chunks)
376 12 = MAT_GRATE1 (grate chunks--looks horrible right now)
377 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
378 14 = MAT_CRATE2 (red multi-colored crate chunks)
379 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
380
381 */
SP_func_breakable(gentity_t * self)382 void SP_func_breakable( gentity_t *self )
383 {
384 if(!(self->spawnflags & 1))
385 {
386 if(!self->health)
387 {
388 self->health = 10;
389 }
390 }
391
392 if ( self->spawnflags & 16 ) // saber only
393 {
394 self->flags |= FL_DMG_BY_SABER_ONLY;
395 }
396 else if ( self->spawnflags & 32 ) // heavy weap
397 {
398 self->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
399 }
400
401 if (self->health)
402 {
403 self->takedamage = qtrue;
404 }
405
406 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching
407 G_SpawnFloat( "radius", "1", &self->radius ); // used to scale chunk code if desired by a designer
408 G_SpawnInt( "material", "0", (int*)&self->material );
409 CacheChunkEffects( self->material );
410
411 self->e_UseFunc = useF_funcBBrushUse;
412
413 //if ( self->paintarget )
414 {
415 self->e_PainFunc = painF_funcBBrushPain;
416 }
417
418 self->e_TouchFunc = touchF_funcBBrushTouch;
419
420 if ( self->team && self->team[0] )
421 {
422 self->noDamageTeam = TranslateTeamName( self->team );
423 if(self->noDamageTeam == TEAM_FREE)
424 {
425 G_Error("team name %s not recognized", self->team);
426 }
427 }
428 self->team = NULL;
429 if (!self->model) {
430 G_Error("func_breakable with NULL model");
431 }
432 InitBBrush( self );
433 }
434
435
misc_model_breakable_pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,vec3_t point,int damage,int mod,int hitLoc)436 void misc_model_breakable_pain ( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc )
437 {
438 if ( self->health > 0 )
439 {
440 // still alive, react to the pain
441 if ( self->paintarget )
442 {
443 G_UseTargets2 (self, self->activator, self->paintarget);
444 }
445
446 // Don't do script if dead
447 G_ActivateBehavior( self, BSET_PAIN );
448 }
449 }
450
misc_model_breakable_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)451 void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
452 {
453 int numChunks;
454 float size = 0, scale;
455 vec3_t dir, up, dis;
456
457 //NOTE: Stop any scripts that are currently running (FLUSH)... ?
458 //Turn off animation
459 self->s.frame = self->startFrame = self->endFrame = 0;
460 self->svFlags &= ~SVF_ANIMATING;
461
462 self->health = 0;
463
464 //Throw some chunks
465 AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
466 VectorNormalize( dir );
467
468 numChunks = Q_flrand(0.0f, 1.0f) * 6 + 20;
469
470 VectorSubtract( self->absmax, self->absmin, dis );
471
472 // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
473 // Volume is length * width * height...then break that volume down based on how many chunks we have
474 scale = sqrt( sqrt( dis[0] * dis[1] * dis[2] )) * 1.75f;
475
476 if ( scale > 48 )
477 {
478 size = 2;
479 }
480 else if ( scale > 24 )
481 {
482 size = 1;
483 }
484
485 scale = scale / numChunks;
486
487 if ( self->radius > 0.0f )
488 {
489 // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
490 // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
491 numChunks *= self->radius;
492 }
493
494 VectorAdd( self->absmax, self->absmin, dis );
495 VectorScale( dis, 0.5f, dis );
496
497 CG_Chunks( self->s.number, dis, dir, self->absmin, self->absmax, 300, numChunks, self->material, self->s.modelindex3, scale );
498
499 self->e_PainFunc = painF_NULL;
500 self->e_DieFunc = dieF_NULL;
501 // self->e_UseFunc = useF_NULL;
502
503 self->takedamage = qfalse;
504
505 if ( !(self->spawnflags & 4) )
506 {//We don't want to stay solid
507 self->s.solid = 0;
508 self->contents = 0;
509 self->clipmask = 0;
510 gi.linkentity(self);
511 }
512
513 VectorSet(up, 0, 0, 1);
514
515 if(self->target)
516 {
517 G_UseTargets(self, attacker);
518 }
519
520 if(inflictor->client)
521 {
522 VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir );
523 VectorNormalize( dir );
524 }
525 else
526 {
527 VectorCopy(up, dir);
528 }
529
530 if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
531 {
532 // Ok, we are allowed to explode, so do it now!
533 if(self->splashDamage > 0 && self->splashRadius > 0)
534 {//explode
535 vec3_t org;
536 AddSightEvent( attacker, self->currentOrigin, 256, AEL_DISCOVERED, 100 );
537 AddSoundEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED );
538 //FIXME: specify type of explosion? (barrel, electrical, etc.) Also, maybe just use the explosion effect below since it's
539 // a bit better?
540 // up the origin a little for the damage check, because several models have their origin on the ground, so they don't alwasy do damage, not the optimal solution...
541 VectorCopy( self->currentOrigin, org );
542 if ( self->mins[2] > -4 )
543 {//origin is going to be below it or very very low in the model
544 //center the origin
545 org[2] = self->currentOrigin[2] + self->mins[2] + (self->maxs[2] - self->mins[2])/2.0f;
546 }
547 G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
548
549 if ( self->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", self->model ) == 0 )
550 {//TEMP HACK for Tie Fighters- they're HUGE
551 G_PlayEffect( "fighter_explosion2", self->currentOrigin );
552 G_Sound( self, G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ) );
553 }
554 else
555 {
556 CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
557 G_Sound( self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav") );
558 }
559 }
560 else
561 {//just break
562 AddSightEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED );
563 AddSoundEvent( attacker, self->currentOrigin, 64, AEL_SUSPICIOUS );
564 // This is the default explosion
565 CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
566 G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"));
567 }
568 }
569
570 self->e_ThinkFunc = thinkF_NULL;
571 self->nextthink = -1;
572
573 if(self->s.modelindex2 != -1 && !(self->spawnflags & 8))
574 {//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist
575 self->svFlags |= SVF_BROKEN;
576 self->s.modelindex = self->s.modelindex2;
577 G_ActivateBehavior( self, BSET_DEATH );
578 }
579 else
580 {
581 G_FreeEntity( self );
582 }
583 }
584
misc_model_use(gentity_t * self,gentity_t * other,gentity_t * activator)585 void misc_model_use (gentity_t *self, gentity_t *other, gentity_t *activator)
586 {
587 if ( self->health <= 0 && self->max_health > 0)
588 {//used while broken fired target3
589 G_UseTargets2( self, activator, self->target3 );
590 return;
591 }
592
593 G_ActivateBehavior( self, BSET_USE );
594 //Don't explode if they've requested it to not
595 if ( self->spawnflags & 64 )
596 {//Usemodels toggling
597 if ( self->spawnflags & 32 )
598 {
599 if( self->s.modelindex == self->sound1to2 )
600 {
601 self->s.modelindex = self->sound2to1;
602 }
603 else
604 {
605 self->s.modelindex = self->sound1to2;
606 }
607 }
608
609 return;
610 }
611
612 misc_model_breakable_die( self, other, activator, self->health, MOD_UNKNOWN );
613 }
614
615 #define MDL_OTHER 0
616 #define MDL_ARMOR_HEALTH 1
617 #define MDL_AMMO 2
618
misc_model_breakable_init(gentity_t * ent)619 void misc_model_breakable_init( gentity_t *ent )
620 {
621 int type;
622
623 type = MDL_OTHER;
624
625 if (!ent->model) {
626 G_Error("no model set on %s at (%.1f %.1f %.1f)", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2]);
627 }
628 //Main model
629 ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model );
630
631 if ( ent->spawnflags & 1 )
632 {//Blocks movement
633 ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
634 }
635 else if ( ent->health )
636 {//Can only be shot
637 ent->contents = CONTENTS_SHOTCLIP;
638 }
639
640 if (type == MDL_OTHER)
641 {
642 ent->e_UseFunc = useF_misc_model_use;
643 }
644 else if ( type == MDL_ARMOR_HEALTH )
645 {
646 // G_SoundIndex("sound/player/suithealth.wav");
647 ent->e_UseFunc = useF_health_use;
648 if (!ent->count)
649 {
650 ent->count = 100;
651 }
652 ent->health = 60;
653 }
654 else if ( type == MDL_AMMO )
655 {
656 // G_SoundIndex("sound/player/suitenergy.wav");
657 ent->e_UseFunc = useF_ammo_use;
658 if (!ent->count)
659 {
660 ent->count = 100;
661 }
662 ent->health = 60;
663 }
664
665 if ( ent->health )
666 {
667 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
668 ent->max_health = ent->health;
669 ent->takedamage = qtrue;
670 ent->e_PainFunc = painF_misc_model_breakable_pain;
671 ent->e_DieFunc = dieF_misc_model_breakable_die;
672 }
673 }
674
TieFighterThink(gentity_t * self)675 void TieFighterThink ( gentity_t *self )
676 {
677 gentity_t *player = &g_entities[0];
678
679 if ( self->health <= 0 )
680 {
681 return;
682 }
683
684 self->nextthink = level.time + FRAMETIME;
685 if ( player )
686 {
687 vec3_t playerDir, fighterDir, fwd, rt;
688 float playerDist, fighterSpeed;
689
690 //use player eyepoint
691 VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir );
692 playerDist = VectorNormalize( playerDir );
693 VectorSubtract( self->currentOrigin, self->lastOrigin, fighterDir );
694 VectorCopy( self->currentOrigin, self->lastOrigin );
695 fighterSpeed = VectorNormalize( fighterDir )*1000;
696 AngleVectors( self->currentAngles, fwd, rt, NULL );
697
698 if ( fighterSpeed )
699 {
700 float side;
701
702 // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave
703 fighterSpeed *= sin( ( 100 ) * 0.003 );
704
705 // Clamp to prevent harsh rolling
706 if ( fighterSpeed > 10 )
707 fighterSpeed = 10;
708
709 side = fighterSpeed * DotProduct( fighterDir, rt );
710 self->s.apos.trBase[2] -= side;
711 }
712
713 //FIXME: bob up/down, strafe left/right some
714 float dot = DotProduct( playerDir, fighterDir );
715 if ( dot > 0 )
716 {//heading toward the player
717 if ( playerDist < 1024 )
718 {
719 if ( DotProduct( playerDir, fwd ) > 0.7 )
720 {//facing the player
721 if ( self->attackDebounceTime < level.time )
722 {
723 gentity_t *bolt;
724
725 bolt = G_Spawn();
726
727 bolt->classname = "tie_proj";
728 bolt->nextthink = level.time + 10000;
729 bolt->e_ThinkFunc = thinkF_G_FreeEntity;
730 bolt->s.eType = ET_MISSILE;
731 bolt->s.weapon = WP_BLASTER;
732 bolt->owner = self;
733 bolt->damage = 30;
734 bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
735 bolt->splashDamage = 0;
736 bolt->splashRadius = 0;
737 bolt->methodOfDeath = MOD_ENERGY; // ?
738 bolt->clipmask = MASK_SHOT;
739
740 bolt->s.pos.trType = TR_LINEAR;
741 bolt->s.pos.trTime = level.time; // move a bit on the very first frame
742 VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
743 VectorScale( fwd, 8000, bolt->s.pos.trDelta );
744 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
745 VectorCopy( self->currentOrigin, bolt->currentOrigin);
746
747 if ( !Q_irand( 0, 2 ) )
748 {
749 G_SoundOnEnt( bolt, CHAN_VOICE, "sound/weapons/tie_fighter/tie_fire.wav" );
750 }
751 else
752 {
753 G_SoundOnEnt( bolt, CHAN_VOICE, va( "sound/weapons/tie_fighter/tie_fire%d.wav", Q_irand( 2, 3 ) ) );
754 }
755 self->attackDebounceTime = level.time + Q_irand( 300, 2000 );
756 }
757 }
758 }
759 }
760
761 if ( playerDist < 1024 )//512 )
762 {//within range to start our sound
763 if ( dot > 0 )
764 {
765 if ( !self->fly_sound_debounce_time )
766 {//start sound
767 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/tie_fighter/tiepass%d.wav", Q_irand( 1, 5 ) ) );
768 self->fly_sound_debounce_time = 2000;
769 }
770 else
771 {//sound already started
772 self->fly_sound_debounce_time = -1;
773 }
774 }
775 }
776 else if ( self->fly_sound_debounce_time < level.time )
777 {
778 self->fly_sound_debounce_time = 0;
779 }
780 }
781 }
782
misc_model_breakable_gravity_init(gentity_t * ent,qboolean dropToFloor)783 void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor )
784 {
785 ent->s.eType = ET_GENERAL;
786 ent->s.eFlags |= EF_BOUNCE_HALF;
787 ent->clipmask = MASK_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//?
788 ent->physicsBounce = ent->mass = VectorLength( ent->maxs ) + VectorLength( ent->mins );
789 //drop to floor
790 trace_t tr;
791 vec3_t top, bottom;
792
793 if ( dropToFloor )
794 {
795 VectorCopy( ent->currentOrigin, top );
796 top[2] += 1;
797 VectorCopy( ent->currentOrigin, bottom );
798 bottom[2] = MIN_WORLD_COORD;
799 gi.trace( &tr, top, ent->mins, ent->maxs, bottom, ent->s.number, MASK_NPCSOLID, G2_NOCOLLIDE, 0 );
800 if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 )
801 {
802 G_SetOrigin( ent, tr.endpos );
803 gi.linkentity( ent );
804 }
805 }
806 else
807 {
808 G_SetOrigin( ent, ent->currentOrigin );
809 gi.linkentity( ent );
810 }
811 //set up for object thinking
812 if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) )
813 {//not moving
814 ent->s.pos.trType = TR_STATIONARY;
815 }
816 else
817 {
818 ent->s.pos.trType = TR_GRAVITY;
819 }
820 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
821 VectorClear( ent->s.pos.trDelta );
822 ent->s.pos.trTime = level.time;
823 if ( VectorCompare( ent->s.apos.trDelta, vec3_origin ) )
824 {//not moving
825 ent->s.apos.trType = TR_STATIONARY;
826 }
827 else
828 {
829 ent->s.apos.trType = TR_LINEAR;
830 }
831 VectorCopy( ent->currentAngles, ent->s.apos.trBase );
832 VectorClear( ent->s.apos.trDelta );
833 ent->s.apos.trTime = level.time;
834 ent->nextthink = level.time + FRAMETIME;
835 ent->e_ThinkFunc = thinkF_G_RunObject;
836 }
837 /*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
838 SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health
839 AUTOANIMATE - Will cycle it's anim
840 DEADSOLID - Stay solid even when destroyed (in case damage model is rather large).
841 NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists
842 USE_MODEL - When used, will toggle to it's usemodel (model name + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked
843 USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
844 PLAYER_USE - Player can use it with the use button
845 NO_EXPLOSION - By default, will explode when it dies...this is your override.
846
847 "model" arbitrary .md3 file to display
848 "health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving
849 "targetname" when used, dies and displays damagemodel, if any (if not, removes itself)
850 "target" What to use when it dies
851 "target2" What to use when it's repaired
852 "target3" What to use when it's used while it's broken
853 "paintarget" target to fire when hit (but not destroyed)
854 "count" the amount of armor/health/ammo given (default 50)
855 "gravity" if set to 1, this will be affected by gravity
856 "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks
857
858 Damage: default is none
859 "splashDamage" - damage to do (will make it explode on death)
860 "splashRadius" - radius for above damage
861
862 "team" - This cannot take damage from members of this team:
863 "player"
864 "neutral"
865 "enemy"
866
867 "material" - default is "8 - MAT_NONE" - choose from this list:
868 0 = MAT_METAL (grey metal)
869 1 = MAT_GLASS
870 2 = MAT_ELECTRICAL (sparks only)
871 3 = MAT_ELEC_METAL (METAL chunks and sparks)
872 4 = MAT_DRK_STONE (brown stone chunks)
873 5 = MAT_LT_STONE (tan stone chunks)
874 6 = MAT_GLASS_METAL (glass and METAL chunks)
875 7 = MAT_METAL2 (blue/grey metal)
876 8 = MAT_NONE (no chunks-DEFAULT)
877 9 = MAT_GREY_STONE (grey colored stone)
878 10 = MAT_METAL3 (METAL and METAL2 chunk combo)
879 11 = MAT_CRATE1 (yellow multi-colored crate chunks)
880 12 = MAT_GRATE1 (grate chunks--looks horrible right now)
881 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
882 14 = MAT_CRATE2 (red multi-colored crate chunks)
883 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
884 FIXME/TODO:
885 set size better?
886 multiple damage models?
887 custom explosion effect/sound?
888 */
SP_misc_model_breakable(gentity_t * ent)889 void SP_misc_model_breakable( gentity_t *ent )
890 {
891 char damageModel[MAX_QPATH];
892 char chunkModel[MAX_QPATH];
893 char useModel[MAX_QPATH];
894 int len;
895
896 // Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this.
897 G_SpawnInt( "material", "8", (int*)&ent->material );
898 G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer
899 CacheChunkEffects( ent->material );
900
901 misc_model_breakable_init( ent );
902
903 len = strlen( ent->model ) - 4;
904 strncpy( damageModel, ent->model, len );
905 damageModel[len] = 0; //chop extension
906 strncpy( chunkModel, damageModel, sizeof(chunkModel));
907 strncpy( useModel, damageModel, sizeof(useModel));
908
909 if (ent->takedamage) {
910 //Dead/damaged model
911 if( !(ent->spawnflags & 8) ) { //no dmodel
912 strcat( damageModel, "_d1.md3" );
913 ent->s.modelindex2 = G_ModelIndex( damageModel );
914 }
915
916 //Chunk model
917 strcat( chunkModel, "_c1.md3" );
918 ent->s.modelindex3 = G_ModelIndex( chunkModel );
919 }
920
921 //Use model
922 if( ent->spawnflags & 32 ) { //has umodel
923 strcat( useModel, "_u1.md3" );
924 ent->sound1to2 = G_ModelIndex( useModel );
925 }
926 if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] )
927 {
928 VectorSet (ent->mins, -16, -16, -16);
929 }
930 if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] )
931 {
932 VectorSet (ent->maxs, 16, 16, 16);
933 }
934
935 if ( ent->spawnflags & 2 )
936 {
937 ent->s.eFlags |= EF_ANIM_ALLFAST;
938 }
939
940 G_SetOrigin( ent, ent->s.origin );
941 G_SetAngles( ent, ent->s.angles );
942 gi.linkentity (ent);
943
944 if ( ent->spawnflags & 128 )
945 {//Can be used by the player's BUTTON_USE
946 ent->svFlags |= SVF_PLAYER_USABLE;
947 }
948
949 if ( ent->team && ent->team[0] )
950 {
951 ent->noDamageTeam = TranslateTeamName( ent->team );
952 if ( ent->noDamageTeam == TEAM_FREE )
953 {
954 G_Error("team name %s not recognized", ent->team);
955 }
956 }
957
958 ent->team = NULL;
959
960 //HACK
961 if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 )
962 {//run a think
963 G_EffectIndex( "fighter_explosion2" );
964 G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" );
965 G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" );
966 G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" );
967 G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" );
968 G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" );
969 G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" );
970 G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" );
971 G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" );
972 G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" );
973 ent->e_ThinkFunc = thinkF_TieFighterThink;
974 ent->nextthink = level.time + FRAMETIME;
975 }
976 float grav = 0;
977 G_SpawnFloat( "gravity", "0", &grav );
978 if ( grav )
979 {//affected by gravity
980 G_SetAngles( ent, ent->s.angles );
981 G_SetOrigin( ent, ent->currentOrigin );
982 misc_model_breakable_gravity_init( ent, qtrue );
983 }
984 }
985
986
987 //----------------------------------
988 //
989 // Breaking Glass Technology
990 //
991 //----------------------------------
992
993 // Really naughty cheating. Put in an EVENT at some point...
994 extern void cgi_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal );
995 extern void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius );
996 extern cgs_t cgs;
997
998 //-----------------------------------------------------
funcGlassDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)999 void funcGlassDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
1000 {
1001 vec3_t verts[4], normal;
1002
1003 // if a missile is stuck to us, blow it up so we don't look dumb....we could, alternately, just let the missile drop off??
1004 for ( int i = 0; i < MAX_GENTITIES; i++ )
1005 {
1006 if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
1007 {
1008 G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
1009 }
1010 }
1011
1012 // Really naughty cheating. Put in an EVENT at some point...
1013 cgi_R_GetBModelVerts( cgs.inlineDrawModel[self->s.modelindex], verts, normal );
1014 CG_DoGlass( verts, normal, self->pos1, self->pos2, self->splashRadius );
1015
1016 self->takedamage = qfalse;//stop chain reaction runaway loops
1017
1018 G_SetEnemy( self, self->enemy );
1019
1020 //So chunks don't get stuck inside me
1021 self->s.solid = 0;
1022 self->contents = 0;
1023 self->clipmask = 0;
1024 gi.linkentity(self);
1025
1026 if ( self->target && attacker != NULL )
1027 {
1028 G_UseTargets( self, attacker );
1029 }
1030
1031 gi.AdjustAreaPortalState( self, qtrue );
1032 G_FreeEntity( self );
1033 }
1034
1035 //-----------------------------------------------------
funcGlassUse(gentity_t * self,gentity_t * other,gentity_t * activator)1036 void funcGlassUse( gentity_t *self, gentity_t *other, gentity_t *activator )
1037 {
1038 vec3_t temp1, temp2;
1039
1040 // For now, we just break on use
1041 G_ActivateBehavior( self, BSET_USE );
1042
1043 VectorAdd( self->mins, self->maxs, temp1 );
1044 VectorScale( temp1, 0.5f, temp1 );
1045
1046 VectorAdd( other->mins, other->maxs, temp2 );
1047 VectorScale( temp2, 0.5f, temp2 );
1048
1049 VectorSubtract( temp1, temp2, self->pos2 );
1050 VectorCopy( temp1, self->pos1 );
1051
1052 VectorNormalize( self->pos2 );
1053 VectorScale( self->pos2, 390, self->pos2 );
1054
1055 self->splashRadius = 40; // ?? some random number, maybe it's ok?
1056
1057 funcGlassDie( self, other, activator, self->health, MOD_UNKNOWN );
1058 }
1059
1060 //-----------------------------------------------------
1061 /*QUAKED func_glass (0 .8 .5) ? INVINCIBLE
1062 When destroyed, fires it's target, breaks into glass chunks and plays glass noise
1063 For now, instantly breaks on impact
1064
1065 INVINCIBLE - can only be broken by being used
1066
1067 "targetname" entities with matching target will fire it
1068 "target" all entities with a matching targetname will be used when this is destroyed
1069 "health" default is 1
1070 */
1071 //-----------------------------------------------------
SP_func_glass(gentity_t * self)1072 void SP_func_glass( gentity_t *self )
1073 {
1074 if ( !(self->spawnflags & 1 ))
1075 {
1076 if ( !self->health )
1077 {
1078 self->health = 1;
1079 }
1080 }
1081
1082 if ( self->health )
1083 {
1084 self->takedamage = qtrue;
1085 }
1086
1087 self->e_UseFunc = useF_funcGlassUse;
1088 self->e_DieFunc = dieF_funcGlassDie;
1089
1090 VectorCopy( self->s.origin, self->pos1 );
1091
1092 gi.SetBrushModel( self, self->model );
1093 self->svFlags |= (SVF_GLASS_BRUSH|SVF_BBRUSH);
1094 self->material = MAT_GLASS;
1095
1096 self->s.eType = ET_MOVER;
1097
1098 self->s.pos.trType = TR_STATIONARY;
1099 VectorCopy( self->pos1, self->s.pos.trBase );
1100
1101 G_SoundIndex( "sound/effects/glassbreak1.wav" );
1102 G_EffectIndex( "glass_impact" );
1103
1104 gi.linkentity( self );
1105 }
1106
G_EntIsBreakable(int entityNum)1107 qboolean G_EntIsBreakable( int entityNum )
1108 {
1109 if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
1110 {
1111 return qfalse;
1112 }
1113
1114 gentity_t *ent = &g_entities[entityNum];
1115 if ( (ent->svFlags&SVF_GLASS_BRUSH) )
1116 {
1117 return qtrue;
1118 }
1119 if ( (ent->svFlags&SVF_BBRUSH) )
1120 {
1121 return qtrue;
1122 }
1123 if ( !Q_stricmp( "misc_model_breakable", ent->classname ) )
1124 {
1125 return qtrue;
1126 }
1127 if ( !Q_stricmp( "misc_maglock", ent->classname ) )
1128 {
1129 return qtrue;
1130 }
1131
1132 return qfalse;
1133 }
1134