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 "../cgame/cg_media.h"
26 #include "g_navigator.h"
27
28 //client side shortcut hacks from cg_local.h
29 extern void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType );
30 extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs,
31 float speed, int numChunks, material_t chunkType, int customChunk, float baseScale, int customSound = 0 );
32 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
33
34 extern gentity_t *G_CreateObject ( gentity_t *owner, vec3_t origin, vec3_t angles, int modelIndex, int frame, trType_t trType, int effectID );
35
36 extern qboolean player_locked;
37
38 //---------------------------------------------------
CacheChunkEffects(material_t material)39 static void CacheChunkEffects( material_t material )
40 {
41 switch( material )
42 {
43 case MAT_GLASS:
44 G_EffectIndex( "chunks/glassbreak" );
45 break;
46 case MAT_GLASS_METAL:
47 G_EffectIndex( "chunks/glassbreak" );
48 G_EffectIndex( "chunks/metalexplode" );
49 break;
50 case MAT_ELECTRICAL:
51 case MAT_ELEC_METAL:
52 G_EffectIndex( "chunks/sparkexplode" );
53 break;
54 case MAT_METAL:
55 case MAT_METAL2:
56 case MAT_METAL3:
57 case MAT_CRATE1:
58 case MAT_CRATE2:
59 G_EffectIndex( "chunks/metalexplode" );
60 break;
61 case MAT_GRATE1:
62 G_EffectIndex( "chunks/grateexplode" );
63 break;
64 case MAT_DRK_STONE:
65 case MAT_LT_STONE:
66 case MAT_GREY_STONE:
67 case MAT_WHITE_METAL: // what is this crap really supposed to be??
68 G_EffectIndex( "chunks/rockbreaklg" );
69 G_EffectIndex( "chunks/rockbreakmed" );
70 break;
71 case MAT_ROPE:
72 G_EffectIndex( "chunks/ropebreak" );
73 // G_SoundIndex(); // FIXME: give it a sound
74 break;
75 default:
76 break;
77 }
78 }
79
80 //--------------------------------------
funcBBrushDieGo(gentity_t * self)81 void funcBBrushDieGo (gentity_t *self)
82 {
83 vec3_t org, dir, up;
84 gentity_t *attacker = self->enemy;
85 float scale;
86 int numChunks, size = 0;
87 material_t chunkType = self->material;
88
89 // if a missile is stuck to us, blow it up so we don't look dumb
90 // FIXME: flag me so I should know to do this check!
91 for ( int i = 0; i < MAX_GENTITIES; i++ )
92 {
93 if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
94 {
95 G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
96 }
97 }
98
99 //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!!
100 gi.AdjustAreaPortalState( self, qtrue );
101
102 //So chunks don't get stuck inside me
103 self->s.solid = 0;
104 self->contents = 0;
105 self->clipmask = 0;
106 gi.linkentity(self);
107
108 VectorSet(up, 0, 0, 1);
109
110 if ( self->target && attacker != NULL )
111 {
112 G_UseTargets(self, attacker);
113 }
114
115 VectorSubtract( self->absmax, self->absmin, org );// size
116
117 numChunks = Q_flrand(0.0f, 1.0f) * 6 + 18;
118
119 // 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.
120 // Volume is length * width * height...then break that volume down based on how many chunks we have
121 scale = sqrt( sqrt( org[0] * org[1] * org[2] )) * 1.75f;
122
123 if ( scale > 48 )
124 {
125 size = 2;
126 }
127 else if ( scale > 24 )
128 {
129 size = 1;
130 }
131
132 scale = scale / numChunks;
133
134 if ( self->radius > 0.0f )
135 {
136 // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
137 // 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
138 numChunks *= self->radius;
139 }
140
141 VectorMA( self->absmin, 0.5, org, org );
142 VectorAdd( self->absmin,self->absmax, org );
143 VectorScale( org, 0.5f, org );
144
145 if ( attacker != NULL && attacker->client )
146 {
147 VectorSubtract( org, attacker->currentOrigin, dir );
148 VectorNormalize( dir );
149 }
150 else
151 {
152 VectorCopy(up, dir);
153 }
154
155 if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
156 {
157 // we are allowed to explode
158 CG_MiscModelExplosion( self->absmin, self->absmax, size, chunkType );
159 }
160
161 if ( self->splashDamage > 0 && self->splashRadius > 0 )
162 {
163 //explode
164 AddSightEvent( attacker, org, 256, AEL_DISCOVERED, 100 );
165 AddSoundEvent( attacker, org, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: am I on ground or not?
166 G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
167
168 gentity_t *te = G_TempEntity( org, EV_GENERAL_SOUND );
169 te->s.eventParm = G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
170 }
171 else
172 {//just break
173 AddSightEvent( attacker, org, 128, AEL_DISCOVERED );
174 AddSoundEvent( attacker, org, 64, AEL_SUSPICIOUS, qfalse, qtrue );//FIXME: am I on ground or not?
175 }
176
177 //FIXME: base numChunks off size?
178 CG_Chunks( self->s.number, org, dir, self->absmin, self->absmax, 300, numChunks, chunkType, 0, scale, self->noise_index );
179
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,const vec3_t point,int damage,int mod,int hitLoc)217 void funcBBrushPain(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const 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->absmin, self->absmax, 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 "modelAngles" md3 model's angles <pitch yaw roll> (in addition to any rotation on the part of the brush entity itself)
347 "target" all entities with a matching targetname will be used when this is destoryed
348 "health" default is 10
349 "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
350 "NPC_targetname" - Only the NPC with this name can damage this
351 "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
352 "redCrosshair" - crosshair turns red when you look at this
353
354 Damage: default is none
355 "splashDamage" - damage to do
356 "splashRadius" - radius for above damage
357
358 "team" - This cannot take damage from members of this team:
359 "player"
360 "neutral"
361 "enemy"
362
363 Don't know if these work:
364 "color" constantLight color
365 "light" constantLight radius
366
367 "material" - default is "0 - MAT_METAL" - choose from this list:
368 0 = MAT_METAL (basic blue-grey scorched-DEFAULT)
369 1 = MAT_GLASS
370 2 = MAT_ELECTRICAL (sparks only)
371 3 = MAT_ELEC_METAL (METAL2 chunks and sparks)
372 4 = MAT_DRK_STONE (brown stone chunks)
373 5 = MAT_LT_STONE (tan stone chunks)
374 6 = MAT_GLASS_METAL (glass and METAL2 chunks)
375 7 = MAT_METAL2 (electronic type of metal)
376 8 = MAT_NONE (no chunks)
377 9 = MAT_GREY_STONE (grey colored stone)
378 10 = MAT_METAL3 (METAL and METAL2 chunk combo)
379 11 = MAT_CRATE1 (yellow multi-colored crate chunks)
380 12 = MAT_GRATE1 (grate chunks--looks horrible right now)
381 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
382 14 = MAT_CRATE2 (red multi-colored crate chunks)
383 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
384
385 */
SP_func_breakable(gentity_t * self)386 void SP_func_breakable( gentity_t *self )
387 {
388 if(!(self->spawnflags & 1))
389 {
390 if(!self->health)
391 {
392 self->health = 10;
393 }
394 }
395
396 if ( self->spawnflags & 16 ) // saber only
397 {
398 self->flags |= FL_DMG_BY_SABER_ONLY;
399 }
400 else if ( self->spawnflags & 32 ) // heavy weap
401 {
402 self->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
403 }
404
405 if (self->health)
406 {
407 self->takedamage = qtrue;
408 }
409
410 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching
411 G_SpawnFloat( "radius", "1", &self->radius ); // used to scale chunk code if desired by a designer
412 G_SpawnInt( "material", "0", (int*)&self->material );
413 CacheChunkEffects( self->material );
414
415 self->e_UseFunc = useF_funcBBrushUse;
416
417 //if ( self->paintarget )
418 {
419 self->e_PainFunc = painF_funcBBrushPain;
420 }
421
422 self->e_TouchFunc = touchF_funcBBrushTouch;
423
424 if ( self->team && self->team[0] )
425 {
426 self->noDamageTeam = (team_t)GetIDForString( TeamTable, self->team );
427 if(self->noDamageTeam == TEAM_FREE)
428 {
429 G_Error("team name %s not recognized\n", self->team);
430 }
431 }
432 self->team = NULL;
433 if (!self->model) {
434 G_Error("func_breakable with NULL model\n");
435 }
436 InitBBrush( self );
437
438 char buffer[MAX_QPATH];
439 char *s;
440 if ( G_SpawnString( "noise", "*NOSOUND*", &s ) )
441 {
442 Q_strncpyz( buffer, s, sizeof(buffer) );
443 COM_DefaultExtension( buffer, sizeof(buffer), ".wav");
444 self->noise_index = G_SoundIndex(buffer);
445 }
446
447 int forceVisible = 0;
448 G_SpawnInt( "forcevisible", "0", &forceVisible );
449 if ( forceVisible )
450 {//can see these through walls with force sight, so must be broadcast
451 if ( VectorCompare( self->s.origin, vec3_origin ) )
452 {//no origin brush
453 self->svFlags |= SVF_BROADCAST;
454 }
455 self->s.eFlags |= EF_FORCE_VISIBLE;
456 }
457
458 int redCrosshair = 0;
459 G_SpawnInt( "redCrosshair", "0", &redCrosshair );
460 if ( redCrosshair )
461 {//can see these through walls with force sight, so must be broadcast
462 self->flags |= FL_RED_CROSSHAIR;
463 }
464 }
465
466
misc_model_breakable_pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,const vec3_t point,int damage,int mod,int hitLoc)467 void misc_model_breakable_pain ( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
468 {
469 if ( self->health > 0 )
470 {
471 // still alive, react to the pain
472 if ( self->paintarget )
473 {
474 G_UseTargets2 (self, self->activator, self->paintarget);
475 }
476
477 // Don't do script if dead
478 G_ActivateBehavior( self, BSET_PAIN );
479 }
480 }
481
misc_model_breakable_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)482 void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
483 {
484 int numChunks;
485 float size = 0, scale;
486 vec3_t dir, up, dis;
487
488 if (self->e_DieFunc == dieF_NULL) //i was probably already killed since my die func was removed
489 {
490 #ifndef FINAL_BUILD
491 gi.Printf(S_COLOR_YELLOW"Recursive misc_model_breakable_die. Use targets probably pointing back at self.\n");
492 #endif
493 return; //this happens when you have a cyclic target chain!
494 }
495 //NOTE: Stop any scripts that are currently running (FLUSH)... ?
496 //Turn off animation
497 self->s.frame = self->startFrame = self->endFrame = 0;
498 self->svFlags &= ~SVF_ANIMATING;
499
500 self->health = 0;
501 //Throw some chunks
502 AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
503 VectorNormalize( dir );
504
505 numChunks = Q_flrand(0.0f, 1.0f) * 6 + 20;
506
507 VectorSubtract( self->absmax, self->absmin, dis );
508
509 // 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.
510 // Volume is length * width * height...then break that volume down based on how many chunks we have
511 scale = sqrt( sqrt( dis[0] * dis[1] * dis[2] )) * 1.75f;
512
513 if ( scale > 48 )
514 {
515 size = 2;
516 }
517 else if ( scale > 24 )
518 {
519 size = 1;
520 }
521
522 scale = scale / numChunks;
523
524 if ( self->radius > 0.0f )
525 {
526 // designer wants to scale number of chunks, helpful because the above scale code is far from perfect
527 // 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
528 numChunks *= self->radius;
529 }
530
531 VectorAdd( self->absmax, self->absmin, dis );
532 VectorScale( dis, 0.5f, dis );
533
534 CG_Chunks( self->s.number, dis, dir, self->absmin, self->absmax, 300, numChunks, self->material, self->s.modelindex3, scale );
535
536 self->e_PainFunc = painF_NULL;
537 self->e_DieFunc = dieF_NULL;
538 // self->e_UseFunc = useF_NULL;
539
540 self->takedamage = qfalse;
541
542 if ( !(self->spawnflags & 4) )
543 {//We don't want to stay solid
544 self->s.solid = 0;
545 self->contents = 0;
546 self->clipmask = 0;
547 if (self!=0)
548 {
549 NAV::WayEdgesNowClear(self);
550 }
551 gi.linkentity(self);
552 }
553
554 VectorSet(up, 0, 0, 1);
555
556 if(self->target)
557 {
558 G_UseTargets(self, attacker);
559 }
560
561 if(inflictor->client)
562 {
563 VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir );
564 VectorNormalize( dir );
565 }
566 else
567 {
568 VectorCopy(up, dir);
569 }
570
571 if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
572 {
573 // Ok, we are allowed to explode, so do it now!
574 if(self->splashDamage > 0 && self->splashRadius > 0)
575 {//explode
576 vec3_t org;
577 AddSightEvent( attacker, self->currentOrigin, 256, AEL_DISCOVERED, 100 );
578 AddSoundEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: am I on ground or not?
579 //FIXME: specify type of explosion? (barrel, electrical, etc.) Also, maybe just use the explosion effect below since it's
580 // a bit better?
581 // 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...
582 VectorCopy( self->currentOrigin, org );
583 if ( self->mins[2] > -4 )
584 {//origin is going to be below it or very very low in the model
585 //center the origin
586 org[2] = self->currentOrigin[2] + self->mins[2] + (self->maxs[2] - self->mins[2])/2.0f;
587 }
588 G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
589
590 if ( self->model && ( Q_stricmp( "models/map_objects/ships/tie_fighter.md3", self->model ) == 0 ||
591 Q_stricmp( "models/map_objects/ships/tie_bomber.md3", self->model ) == 0 ) )
592 {//TEMP HACK for Tie Fighters- they're HUGE
593 G_PlayEffect( "explosions/fighter_explosion2", self->currentOrigin );
594 G_Sound( self, G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ) );
595 self->s.loopSound = 0;
596 }
597 else
598 {
599 CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
600 G_Sound( self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav") );
601 self->s.loopSound = 0;
602 }
603 }
604 else
605 {//just break
606 AddSightEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED );
607 AddSoundEvent( attacker, self->currentOrigin, 64, AEL_SUSPICIOUS, qfalse, qtrue );//FIXME: am I on ground or not?
608 // This is the default explosion
609 CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
610 G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"));
611 }
612 }
613
614 self->e_ThinkFunc = thinkF_NULL;
615 self->nextthink = -1;
616
617 if(self->s.modelindex2 != -1 && !(self->spawnflags & 8))
618 {//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist
619 self->svFlags |= SVF_BROKEN;
620 self->s.modelindex = self->s.modelindex2;
621 G_ActivateBehavior( self, BSET_DEATH );
622 }
623 else
624 {
625 G_FreeEntity( self );
626 }
627 }
628
misc_model_throw_at_target4(gentity_t * self,gentity_t * activator)629 void misc_model_throw_at_target4( gentity_t *self, gentity_t *activator )
630 {
631 vec3_t pushDir, kvel;
632 float knockback = 200;
633 float mass = self->mass;
634 gentity_t *target = G_Find( NULL, FOFS(targetname), self->target4 );
635 if ( !target )
636 {//nothing to throw ourselves at...
637 return;
638 }
639 VectorSubtract( target->currentOrigin, self->currentOrigin, pushDir );
640 knockback -= VectorNormalize( pushDir );
641 if ( knockback < 100 )
642 {
643 knockback = 100;
644 }
645 VectorCopy( self->currentOrigin, self->s.pos.trBase );
646 self->s.pos.trTime = level.time; // move a bit on the very first frame
647 if ( self->s.pos.trType != TR_INTERPOLATE )
648 {//don't do this to rolling missiles
649 self->s.pos.trType = TR_GRAVITY;
650 }
651
652 if ( mass < 50 )
653 {//???
654 mass = 50;
655 }
656
657 if ( g_gravity->value > 0 )
658 {
659 VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel );
660 kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5;
661 }
662 else
663 {
664 VectorScale( pushDir, g_knockback->value * knockback / mass, kvel );
665 }
666
667 VectorAdd( self->s.pos.trDelta, kvel, self->s.pos.trDelta );
668 if ( g_gravity->value > 0 )
669 {
670 if ( self->s.pos.trDelta[2] < knockback )
671 {
672 self->s.pos.trDelta[2] = knockback;
673 }
674 }
675 //no trDuration?
676 if ( self->e_ThinkFunc != thinkF_G_RunObject )
677 {//objects spin themselves?
678 //spin it
679 //FIXME: messing with roll ruins the rotational center???
680 self->s.apos.trTime = level.time;
681 self->s.apos.trType = TR_LINEAR;
682 VectorClear( self->s.apos.trDelta );
683 self->s.apos.trDelta[1] = Q_irand( -800, 800 );
684 }
685
686 self->forcePushTime = level.time + 600; // let the push effect last for 600 ms
687 if ( activator )
688 {
689 self->forcePuller = activator->s.number;//remember this regardless
690 }
691 else
692 {
693 self->forcePuller = 0;
694 }
695 }
696
misc_model_use(gentity_t * self,gentity_t * other,gentity_t * activator)697 void misc_model_use (gentity_t *self, gentity_t *other, gentity_t *activator)
698 {
699 if ( self->target4 )
700 {//throw me at my target!
701 misc_model_throw_at_target4( self, activator );
702 return;
703 }
704
705 if ( self->health <= 0 && self->max_health > 0)
706 {//used while broken fired target3
707 G_UseTargets2( self, activator, self->target3 );
708 return;
709 }
710
711 // Become solid again.
712 if ( !self->count )
713 {
714 self->count = 1;
715 self->activator = activator;
716 self->svFlags &= ~SVF_NOCLIENT;
717 self->s.eFlags &= ~EF_NODRAW;
718 }
719
720 G_ActivateBehavior( self, BSET_USE );
721 //Don't explode if they've requested it to not
722 if ( self->spawnflags & 64 )
723 {//Usemodels toggling
724 if ( self->spawnflags & 32 )
725 {
726 if( self->s.modelindex == self->sound1to2 )
727 {
728 self->s.modelindex = self->sound2to1;
729 }
730 else
731 {
732 self->s.modelindex = self->sound1to2;
733 }
734 }
735
736 return;
737 }
738
739 self->e_DieFunc = dieF_misc_model_breakable_die;
740 misc_model_breakable_die( self, other, activator, self->health, MOD_UNKNOWN );
741 }
742
743 #define MDL_OTHER 0
744 #define MDL_ARMOR_HEALTH 1
745 #define MDL_AMMO 2
746
misc_model_breakable_init(gentity_t * ent)747 void misc_model_breakable_init( gentity_t *ent )
748 {
749 int type;
750
751 type = MDL_OTHER;
752
753 if (!ent->model) {
754 G_Error("no model set on %s at (%.1f %.1f %.1f)\n", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2]);
755 }
756 //Main model
757 ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model );
758
759 if ( ent->spawnflags & 1 )
760 {//Blocks movement
761 ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
762 }
763 else if ( ent->health )
764 {//Can only be shot
765 ent->contents = CONTENTS_SHOTCLIP;
766 }
767
768 if (type == MDL_OTHER)
769 {
770 ent->e_UseFunc = useF_misc_model_use;
771 }
772 else if ( type == MDL_ARMOR_HEALTH )
773 {
774 // G_SoundIndex("sound/player/suithealth.wav");
775 ent->e_UseFunc = useF_health_use;
776 if (!ent->count)
777 {
778 ent->count = 100;
779 }
780 ent->health = 60;
781 }
782 else if ( type == MDL_AMMO )
783 {
784 // G_SoundIndex("sound/player/suitenergy.wav");
785 ent->e_UseFunc = useF_ammo_use;
786 if (!ent->count)
787 {
788 ent->count = 100;
789 }
790 ent->health = 60;
791 }
792
793 if ( ent->health )
794 {
795 G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
796 ent->max_health = ent->health;
797 ent->takedamage = qtrue;
798 ent->e_PainFunc = painF_misc_model_breakable_pain;
799 ent->e_DieFunc = dieF_misc_model_breakable_die;
800 }
801 }
802
TieFighterThink(gentity_t * self)803 void TieFighterThink ( gentity_t *self )
804 {
805 gentity_t *player = &g_entities[0];
806
807 if ( self->health <= 0 )
808 {
809 return;
810 }
811
812 self->nextthink = level.time + FRAMETIME;
813 if ( player )
814 {
815 vec3_t playerDir, fighterDir, fwd, rt;
816 float playerDist, fighterSpeed;
817
818 //use player eyepoint
819 VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir );
820 playerDist = VectorNormalize( playerDir );
821 VectorSubtract( self->currentOrigin, self->lastOrigin, fighterDir );
822 VectorCopy( self->currentOrigin, self->lastOrigin );
823 fighterSpeed = VectorNormalize( fighterDir )*1000;
824 AngleVectors( self->currentAngles, fwd, rt, NULL );
825
826 if ( fighterSpeed )
827 {
828 float side;
829
830 // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave
831 fighterSpeed *= sin( ( 100 ) * 0.003 );
832
833 // Clamp to prevent harsh rolling
834 if ( fighterSpeed > 10 )
835 fighterSpeed = 10;
836
837 side = fighterSpeed * DotProduct( fighterDir, rt );
838 self->s.apos.trBase[2] -= side;
839 }
840
841 //FIXME: bob up/down, strafe left/right some
842 float dot = DotProduct( playerDir, fighterDir );
843 if ( dot > 0 )
844 {//heading toward the player
845 if ( playerDist < 1024 )
846 {
847 if ( DotProduct( playerDir, fwd ) > 0.7 )
848 {//facing the player
849 if ( self->attackDebounceTime < level.time )
850 {
851 gentity_t *bolt;
852
853 bolt = G_Spawn();
854
855 bolt->classname = "tie_proj";
856 bolt->nextthink = level.time + 10000;
857 bolt->e_ThinkFunc = thinkF_G_FreeEntity;
858 bolt->s.eType = ET_MISSILE;
859 bolt->s.weapon = WP_BLASTER;
860 bolt->owner = self;
861 bolt->damage = 30;
862 bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
863 bolt->splashDamage = 0;
864 bolt->splashRadius = 0;
865 bolt->methodOfDeath = MOD_ENERGY; // ?
866 bolt->clipmask = MASK_SHOT;
867
868 bolt->s.pos.trType = TR_LINEAR;
869 bolt->s.pos.trTime = level.time; // move a bit on the very first frame
870 VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
871 VectorScale( fwd, 8000, bolt->s.pos.trDelta );
872 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
873 VectorCopy( self->currentOrigin, bolt->currentOrigin);
874
875 if ( !Q_irand( 0, 2 ) )
876 {
877 G_SoundOnEnt( bolt, CHAN_VOICE, "sound/weapons/tie_fighter/tie_fire.wav" );
878 }
879 else
880 {
881 G_SoundOnEnt( bolt, CHAN_VOICE, va( "sound/weapons/tie_fighter/tie_fire%d.wav", Q_irand( 2, 3 ) ) );
882 }
883 self->attackDebounceTime = level.time + Q_irand( 300, 2000 );
884 }
885 }
886 }
887 }
888
889 if ( playerDist < 1024 )//512 )
890 {//within range to start our sound
891 if ( dot > 0 )
892 {
893 if ( !self->fly_sound_debounce_time )
894 {//start sound
895 G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/tie_fighter/tiepass%d.wav", Q_irand( 1, 5 ) ) );
896 self->fly_sound_debounce_time = 2000;
897 }
898 else
899 {//sound already started
900 self->fly_sound_debounce_time = -1;
901 }
902 }
903 }
904 else if ( self->fly_sound_debounce_time < level.time )
905 {
906 self->fly_sound_debounce_time = 0;
907 }
908 }
909 }
910
TieFighterUse(gentity_t * self,gentity_t * other,gentity_t * activator)911 void TieFighterUse( gentity_t *self, gentity_t *other, gentity_t *activator )
912 {
913 if ( !self || !other || !activator )
914 return;
915
916 vec3_t fwd, rt;
917 AngleVectors( self->currentAngles, fwd, rt, NULL );
918
919 gentity_t *bolt;
920 bolt = G_Spawn();
921
922 bolt->classname = "tie_proj";
923 bolt->nextthink = level.time + 10000;
924 bolt->e_ThinkFunc = thinkF_G_FreeEntity;
925 bolt->s.eType = ET_MISSILE;
926 bolt->s.weapon = WP_TIE_FIGHTER;
927 bolt->owner = self;
928 bolt->damage = 30;
929 bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
930 bolt->splashDamage = 0;
931 bolt->splashRadius = 0;
932 bolt->methodOfDeath = MOD_ENERGY; // ?
933 bolt->clipmask = MASK_SHOT;
934
935 bolt->s.pos.trType = TR_LINEAR;
936 bolt->s.pos.trTime = level.time; // move a bit on the very first frame
937 VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
938 rt[2] += 2.0f;
939 VectorMA( bolt->s.pos.trBase, -15.0, rt, bolt->s.pos.trBase );
940 VectorScale( fwd, 3000, bolt->s.pos.trDelta );
941 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
942 VectorCopy( self->currentOrigin, bolt->currentOrigin);
943
944 bolt = G_Spawn();
945
946 bolt->classname = "tie_proj";
947 bolt->nextthink = level.time + 10000;
948 bolt->e_ThinkFunc = thinkF_G_FreeEntity;
949 bolt->s.eType = ET_MISSILE;
950 bolt->s.weapon = WP_TIE_FIGHTER;
951 bolt->owner = self;
952 bolt->damage = 30;
953 bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming
954 bolt->splashDamage = 0;
955 bolt->splashRadius = 0;
956 bolt->methodOfDeath = MOD_ENERGY; // ?
957 bolt->clipmask = MASK_SHOT;
958
959 bolt->s.pos.trType = TR_LINEAR;
960 bolt->s.pos.trTime = level.time; // move a bit on the very first frame
961 VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
962 rt[2] -= 4.0f;
963 VectorMA( bolt->s.pos.trBase, 15.0, rt, bolt->s.pos.trBase );
964 VectorScale( fwd, 3000, bolt->s.pos.trDelta );
965 SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
966 VectorCopy( self->currentOrigin, bolt->currentOrigin);
967 }
968
TouchTieBomb(gentity_t * self,gentity_t * other,trace_t * trace)969 void TouchTieBomb( gentity_t *self, gentity_t *other, trace_t *trace )
970 {
971 // Stop the effect.
972 G_StopEffect( G_EffectIndex( "ships/tiebomber_bomb_falling" ), self->playerModel, gi.G2API_AddBolt( &self->ghoul2[0], "model_root" ), self->s.number );
973
974 self->e_ThinkFunc = thinkF_G_FreeEntity;
975 self->nextthink = level.time + FRAMETIME;
976 G_PlayEffect( G_EffectIndex( "ships/tiebomber_explosion2" ), self->currentOrigin, self->currentAngles );
977 G_RadiusDamage( self->currentOrigin, self, 900, 500, self, MOD_EXPLOSIVE_SPLASH );
978 }
979
980 #define MIN_PLAYER_DIST 1600
981
TieBomberThink(gentity_t * self)982 void TieBomberThink( gentity_t *self )
983 {
984 // NOTE: Lerp2Angles will think this thinkfunc if the model is a misc_model_breakable. Watchout
985 // for that in a script (try to just use ROFF's?).
986
987 // Stop thinking, you're dead.
988 if ( self->health <= 0 )
989 {
990 return;
991 }
992
993 // Needed every think...
994 self->nextthink = level.time + FRAMETIME;
995
996 gentity_t *player = &g_entities[0];
997 vec3_t playerDir;
998 float playerDist;
999
1000 //use player eyepoint
1001 VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir );
1002 playerDist = VectorNormalize( playerDir );
1003
1004 // Time to attack?
1005 if ( player->health > 0 && playerDist < MIN_PLAYER_DIST && self->attackDebounceTime < level.time )
1006 {
1007 // Doesn't matter what model gets loaded here, as long as it exists.
1008 // It's only used as a point on to which the falling effect for the bomb
1009 // can be attached to (kinda inefficient, I know).
1010 char name1[200] = "models/players/gonk/model.glm";
1011 gentity_t *bomb = G_CreateObject( self, self->s.pos.trBase, self->s.apos.trBase, 0, 0, TR_GRAVITY, 0 );
1012 bomb->s.modelindex = G_ModelIndex( name1 );
1013 gi.G2API_InitGhoul2Model( bomb->ghoul2, name1, bomb->s.modelindex, NULL_HANDLE, NULL_HANDLE, 0, 0);
1014 bomb->s.radius = 50;
1015 bomb->s.eFlags |= EF_NODRAW;
1016
1017 // Make the bombs go forward in the bombers direction a little.
1018 vec3_t fwd, rt;
1019 AngleVectors( self->currentAngles, fwd, rt, NULL );
1020 rt[2] -= 0.5f;
1021 VectorMA( bomb->s.pos.trBase, -30.0, rt, bomb->s.pos.trBase );
1022 VectorScale( fwd, 300, bomb->s.pos.trDelta );
1023 SnapVector( bomb->s.pos.trDelta ); // save net bandwidth
1024
1025 // Start the effect.
1026 G_PlayEffect( G_EffectIndex( "ships/tiebomber_bomb_falling" ), bomb->playerModel, gi.G2API_AddBolt( &bomb->ghoul2[0], "model_root" ), bomb->s.number, bomb->currentOrigin, 1000, qtrue );
1027
1028 // Set the tie bomb to have a touch function, so when it hits the ground (or whatever), there's a nice 'boom'.
1029 bomb->e_TouchFunc = touchF_TouchTieBomb;
1030
1031 self->attackDebounceTime = level.time + 1000;
1032 }
1033 }
1034
misc_model_breakable_gravity_init(gentity_t * ent,qboolean dropToFloor)1035 void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor )
1036 {
1037 //G_SoundIndex( "sound/movers/objects/objectHurt.wav" );
1038 G_EffectIndex( "melee/kick_impact" );
1039 G_EffectIndex( "melee/kick_impact_silent" );
1040 //G_SoundIndex( "sound/weapons/melee/punch1.mp3" );
1041 //G_SoundIndex( "sound/weapons/melee/punch2.mp3" );
1042 //G_SoundIndex( "sound/weapons/melee/punch3.mp3" );
1043 //G_SoundIndex( "sound/weapons/melee/punch4.mp3" );
1044 G_SoundIndex( "sound/movers/objects/objectHit.wav" );
1045 G_SoundIndex( "sound/movers/objects/objectHitHeavy.wav" );
1046 G_SoundIndex( "sound/movers/objects/objectBreak.wav" );
1047 //FIXME: dust impact effect when hits ground?
1048 ent->s.eType = ET_GENERAL;
1049 ent->s.eFlags |= EF_BOUNCE_HALF;
1050 ent->clipmask = MASK_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//?
1051 if ( !ent->mass )
1052 {//not overridden by designer
1053 ent->mass = VectorLength( ent->maxs ) + VectorLength( ent->mins );
1054 }
1055 ent->physicsBounce = ent->mass;
1056 //drop to floor
1057 trace_t tr;
1058 vec3_t top, bottom;
1059
1060 if ( dropToFloor )
1061 {
1062 VectorCopy( ent->currentOrigin, top );
1063 top[2] += 1;
1064 VectorCopy( ent->currentOrigin, bottom );
1065 bottom[2] = MIN_WORLD_COORD;
1066 gi.trace( &tr, top, ent->mins, ent->maxs, bottom, ent->s.number, MASK_NPCSOLID, (EG2_Collision)0, 0 );
1067 if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 )
1068 {
1069 G_SetOrigin( ent, tr.endpos );
1070 gi.linkentity( ent );
1071 }
1072 }
1073 else
1074 {
1075 G_SetOrigin( ent, ent->currentOrigin );
1076 gi.linkentity( ent );
1077 }
1078 //set up for object thinking
1079 if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) )
1080 {//not moving
1081 ent->s.pos.trType = TR_STATIONARY;
1082 }
1083 else
1084 {
1085 ent->s.pos.trType = TR_GRAVITY;
1086 }
1087 VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
1088 VectorClear( ent->s.pos.trDelta );
1089 ent->s.pos.trTime = level.time;
1090 if ( VectorCompare( ent->s.apos.trDelta, vec3_origin ) )
1091 {//not moving
1092 ent->s.apos.trType = TR_STATIONARY;
1093 }
1094 else
1095 {
1096 ent->s.apos.trType = TR_LINEAR;
1097 }
1098 VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1099 VectorClear( ent->s.apos.trDelta );
1100 ent->s.apos.trTime = level.time;
1101 ent->nextthink = level.time + FRAMETIME;
1102 ent->e_ThinkFunc = thinkF_G_RunObject;
1103 }
1104
1105 /*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 START_OFF
1106 SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health
1107 AUTOANIMATE - Will cycle it's anim
1108 DEADSOLID - Stay solid even when destroyed (in case damage model is rather large).
1109 NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists
1110 USE_MODEL - When used, will toggle to it's usemodel (model + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked
1111 USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
1112 PLAYER_USE - Player can use it with the use button
1113 NO_EXPLOSION - By default, will explode when it dies...this is your override.
1114 START_OFF - Will start off and will not appear until used.
1115
1116 "model" arbitrary .md3 file to display
1117 "modelscale" "x" uniform scale
1118 "modelscale_vec" "x y z" scale model in each axis - height, width and length - bbox will scale with it
1119 "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
1120 "targetname" when used, dies and displays damagemodel (model + "_d1.md3"), if any (if not, removes itself)
1121 "target" What to use when it dies
1122 "target2" What to use when it's repaired
1123 "target3" What to use when it's used while it's broken
1124 "paintarget" target to fire when hit (but not destroyed)
1125 "count" the amount of armor/health/ammo given (default 50)
1126 "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
1127 "NPC_targetname" - Only the NPC with this name can damage this
1128 "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
1129 "redCrosshair" - crosshair turns red when you look at this
1130
1131 "gravity" if set to 1, this will be affected by gravity
1132 "throwtarget" if set (along with gravity), this thing, when used, will throw itself at the entity whose targetname matches this string
1133 "mass" if gravity is on, this determines how much damage this thing does when it hits someone. Default is the size of the object from one corner to the other, that works very well. Only override if this is an object whose mass should be very high or low for it's size (density)
1134
1135 Damage: default is none
1136 "splashDamage" - damage to do (will make it explode on death)
1137 "splashRadius" - radius for above damage
1138
1139 "team" - This cannot take damage from members of this team:
1140 "player"
1141 "neutral"
1142 "enemy"
1143
1144 "material" - default is "8 - MAT_NONE" - choose from this list:
1145 0 = MAT_METAL (grey metal)
1146 1 = MAT_GLASS
1147 2 = MAT_ELECTRICAL (sparks only)
1148 3 = MAT_ELEC_METAL (METAL chunks and sparks)
1149 4 = MAT_DRK_STONE (brown stone chunks)
1150 5 = MAT_LT_STONE (tan stone chunks)
1151 6 = MAT_GLASS_METAL (glass and METAL chunks)
1152 7 = MAT_METAL2 (blue/grey metal)
1153 8 = MAT_NONE (no chunks-DEFAULT)
1154 9 = MAT_GREY_STONE (grey colored stone)
1155 10 = MAT_METAL3 (METAL and METAL2 chunk combo)
1156 11 = MAT_CRATE1 (yellow multi-colored crate chunks)
1157 12 = MAT_GRATE1 (grate chunks--looks horrible right now)
1158 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
1159 14 = MAT_CRATE2 (red multi-colored crate chunks)
1160 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
1161 */
SP_misc_model_breakable(gentity_t * ent)1162 void SP_misc_model_breakable( gentity_t *ent )
1163 {
1164 char damageModel[MAX_QPATH];
1165 char chunkModel[MAX_QPATH];
1166 char useModel[MAX_QPATH];
1167 int len;
1168
1169 // Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this.
1170 G_SpawnInt( "material", "8", (int*)&ent->material );
1171 G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer
1172 qboolean bHasScale = G_SpawnVector("modelscale_vec", "0 0 0", ent->s.modelScale);
1173 if (!bHasScale)
1174 {
1175 float temp;
1176 G_SpawnFloat( "modelscale", "0", &temp);
1177 if (temp != 0.0f)
1178 {
1179 ent->s.modelScale[ 0 ] = ent->s.modelScale[ 1 ] = ent->s.modelScale[ 2 ] = temp;
1180 bHasScale = qtrue;
1181 }
1182 }
1183
1184 CacheChunkEffects( ent->material );
1185 misc_model_breakable_init( ent );
1186
1187 len = strlen( ent->model ) - 4;
1188 assert(ent->model[len]=='.');//we're expecting ".md3"
1189 strncpy( damageModel, ent->model, sizeof(damageModel) );
1190 damageModel[len] = 0; //chop extension
1191 strncpy( chunkModel, damageModel, sizeof(chunkModel));
1192 strncpy( useModel, damageModel, sizeof(useModel));
1193
1194 if (ent->takedamage) {
1195 //Dead/damaged model
1196 if( !(ent->spawnflags & 8) ) { //no dmodel
1197 strcat( damageModel, "_d1.md3" );
1198 ent->s.modelindex2 = G_ModelIndex( damageModel );
1199 }
1200
1201 //Chunk model
1202 strcat( chunkModel, "_c1.md3" );
1203 ent->s.modelindex3 = G_ModelIndex( chunkModel );
1204 }
1205
1206 //Use model
1207 if( ent->spawnflags & 32 ) { //has umodel
1208 strcat( useModel, "_u1.md3" );
1209 ent->sound1to2 = G_ModelIndex( useModel );
1210 }
1211 if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] )
1212 {
1213 VectorSet (ent->mins, -16, -16, -16);
1214 }
1215 if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] )
1216 {
1217 VectorSet (ent->maxs, 16, 16, 16);
1218 }
1219
1220 // Scale up the tie-bomber bbox a little.
1221 if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 )
1222 {
1223 VectorSet (ent->mins, -80, -80, -80);
1224 VectorSet (ent->maxs, 80, 80, 80);
1225
1226 //ent->s.modelScale[ 0 ] = ent->s.modelScale[ 1 ] = ent->s.modelScale[ 2 ] *= 2.0f;
1227 //bHasScale = qtrue;
1228 }
1229
1230 if (bHasScale)
1231 {
1232 //scale the x axis of the bbox up.
1233 ent->maxs[0] *= ent->s.modelScale[0];//*scaleFactor;
1234 ent->mins[0] *= ent->s.modelScale[0];//*scaleFactor;
1235
1236 //scale the y axis of the bbox up.
1237 ent->maxs[1] *= ent->s.modelScale[1];//*scaleFactor;
1238 ent->mins[1] *= ent->s.modelScale[1];//*scaleFactor;
1239
1240 //scale the z axis of the bbox up and adjust origin accordingly
1241 ent->maxs[2] *= ent->s.modelScale[2];
1242 float oldMins2 = ent->mins[2];
1243 ent->mins[2] *= ent->s.modelScale[2];
1244 ent->s.origin[2] += (oldMins2-ent->mins[2]);
1245 }
1246
1247 if ( ent->spawnflags & 2 )
1248 {
1249 ent->s.eFlags |= EF_ANIM_ALLFAST;
1250 }
1251
1252 G_SetOrigin( ent, ent->s.origin );
1253 G_SetAngles( ent, ent->s.angles );
1254 gi.linkentity (ent);
1255
1256 if ( ent->spawnflags & 128 )
1257 {//Can be used by the player's BUTTON_USE
1258 ent->svFlags |= SVF_PLAYER_USABLE;
1259 }
1260
1261 if ( ent->team && ent->team[0] )
1262 {
1263 ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team );
1264 if ( ent->noDamageTeam == TEAM_FREE )
1265 {
1266 G_Error("team name %s not recognized\n", ent->team);
1267 }
1268 }
1269
1270 ent->team = NULL;
1271
1272 //HACK
1273 if ( ent->model && Q_stricmp( "models/map_objects/ships/x_wing_nogear.md3", ent->model ) == 0 )
1274 {
1275 if( ent->splashDamage > 0 && ent->splashRadius > 0 )
1276 {
1277 ent->s.loopSound = G_SoundIndex( "sound/vehicles/x-wing/loop.wav" );
1278 ent->s.eFlags |= EF_LESS_ATTEN;
1279 }
1280 }
1281 else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 )
1282 {//run a think
1283 G_EffectIndex( "explosions/fighter_explosion2" );
1284 G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" );
1285 /* G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" );
1286 G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" );
1287 G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" );
1288 G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" );*/
1289 G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" );
1290 /* G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" );
1291 G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" );*/
1292 G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" );
1293 RegisterItem( FindItemForWeapon( WP_TIE_FIGHTER ));
1294
1295 ent->s.eFlags |= EF_LESS_ATTEN;
1296
1297 if( ent->splashDamage > 0 && ent->splashRadius > 0 )
1298 {
1299 ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" );
1300 //ent->e_ThinkFunc = thinkF_TieFighterThink;
1301 //ent->e_UseFunc = thinkF_TieFighterThink;
1302 //ent->nextthink = level.time + FRAMETIME;
1303 ent->e_UseFunc = useF_TieFighterUse;
1304
1305 // Yeah, I could have just made this value changable from the editor, but I
1306 // need it immediately!
1307 float light;
1308 vec3_t color;
1309 qboolean lightSet, colorSet;
1310
1311 // if the "color" or "light" keys are set, setup constantLight
1312 lightSet = qtrue;//G_SpawnFloat( "light", "100", &light );
1313 light = 255;
1314 //colorSet = "1 1 1"//G_SpawnVector( "color", "1 1 1", color );
1315 colorSet = qtrue;
1316 color[0] = 1; color[1] = 1; color[2] = 1;
1317 if ( lightSet || colorSet )
1318 {
1319 int r, g, b, i;
1320
1321 r = color[0] * 255;
1322 if ( r > 255 ) {
1323 r = 255;
1324 }
1325 g = color[1] * 255;
1326 if ( g > 255 ) {
1327 g = 255;
1328 }
1329 b = color[2] * 255;
1330 if ( b > 255 ) {
1331 b = 255;
1332 }
1333 i = light / 4;
1334 if ( i > 255 ) {
1335 i = 255;
1336 }
1337 ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
1338 }
1339 }
1340 }
1341 else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 )
1342 {
1343 G_EffectIndex( "ships/tiebomber_bomb_falling" );
1344 G_EffectIndex( "ships/tiebomber_explosion2" );
1345 G_EffectIndex( "explosions/fighter_explosion2" );
1346 G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" );
1347 ent->e_ThinkFunc = thinkF_TieBomberThink;
1348 ent->nextthink = level.time + FRAMETIME;
1349 ent->attackDebounceTime = level.time + 1000;
1350 // We only take damage from a heavy weapon class missiles.
1351 ent->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
1352 ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" );
1353 ent->s.eFlags |= EF_LESS_ATTEN;
1354 }
1355
1356 float grav = 0;
1357 G_SpawnFloat( "gravity", "0", &grav );
1358 if ( grav )
1359 {//affected by gravity
1360 G_SetAngles( ent, ent->s.angles );
1361 G_SetOrigin( ent, ent->currentOrigin );
1362 G_SpawnString( "throwtarget", NULL, &ent->target4 ); // used to throw itself at something
1363 misc_model_breakable_gravity_init( ent, qtrue );
1364 }
1365
1366 // Start off.
1367 if ( ent->spawnflags & 4096 )
1368 {
1369 ent->spawnContents = ent->contents; // It Navs can temporarly turn it "on"
1370 ent->s.solid = 0;
1371 ent->contents = 0;
1372 ent->clipmask = 0;
1373 ent->svFlags |= SVF_NOCLIENT;
1374 ent->s.eFlags |= EF_NODRAW;
1375 ent->count = 0;
1376 }
1377
1378 int forceVisible = 0;
1379 G_SpawnInt( "forcevisible", "0", &forceVisible );
1380 if ( forceVisible )
1381 {//can see these through walls with force sight, so must be broadcast
1382 //ent->svFlags |= SVF_BROADCAST;
1383 ent->s.eFlags |= EF_FORCE_VISIBLE;
1384 }
1385
1386 int redCrosshair = 0;
1387 G_SpawnInt( "redCrosshair", "0", &redCrosshair );
1388 if ( redCrosshair )
1389 {//can see these through walls with force sight, so must be broadcast
1390 ent->flags |= FL_RED_CROSSHAIR;
1391 }
1392 }
1393
1394
1395 //----------------------------------
1396 //
1397 // Breaking Glass Technology
1398 //
1399 //----------------------------------
1400
1401 // Really naughty cheating. Put in an EVENT at some point...
1402 extern void cgi_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal );
1403 extern void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius );
1404 extern cgs_t cgs;
1405
1406 //-----------------------------------------------------
funcGlassDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int mod,int dFlags,int hitLoc)1407 void funcGlassDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
1408 {
1409 vec3_t verts[4], normal;
1410
1411 // 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??
1412 for ( int i = 0; i < MAX_GENTITIES; i++ )
1413 {
1414 if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
1415 {
1416 G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
1417 }
1418 }
1419
1420 // Really naughty cheating. Put in an EVENT at some point...
1421 cgi_R_GetBModelVerts( cgs.inlineDrawModel[self->s.modelindex], verts, normal );
1422 CG_DoGlass( verts, normal, self->pos1, self->pos2, self->splashRadius );
1423
1424 self->takedamage = qfalse;//stop chain reaction runaway loops
1425
1426 G_SetEnemy( self, self->enemy );
1427
1428 //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!!
1429 gi.AdjustAreaPortalState( self, qtrue );
1430
1431 //So chunks don't get stuck inside me
1432 self->s.solid = 0;
1433 self->contents = 0;
1434 self->clipmask = 0;
1435 gi.linkentity(self);
1436
1437 if ( self->target && attacker != NULL )
1438 {
1439 G_UseTargets( self, attacker );
1440 }
1441
1442 G_FreeEntity( self );
1443 }
1444
1445 //-----------------------------------------------------
funcGlassUse(gentity_t * self,gentity_t * other,gentity_t * activator)1446 void funcGlassUse( gentity_t *self, gentity_t *other, gentity_t *activator )
1447 {
1448 vec3_t temp1, temp2;
1449
1450 // For now, we just break on use
1451 G_ActivateBehavior( self, BSET_USE );
1452
1453 VectorAdd( self->mins, self->maxs, temp1 );
1454 VectorScale( temp1, 0.5f, temp1 );
1455
1456 VectorAdd( other->mins, other->maxs, temp2 );
1457 VectorScale( temp2, 0.5f, temp2 );
1458
1459 VectorSubtract( temp1, temp2, self->pos2 );
1460 VectorCopy( temp1, self->pos1 );
1461
1462 VectorNormalize( self->pos2 );
1463 VectorScale( self->pos2, 390, self->pos2 );
1464
1465 self->splashRadius = 40; // ?? some random number, maybe it's ok?
1466
1467 funcGlassDie( self, other, activator, self->health, MOD_UNKNOWN );
1468 }
1469
1470 //-----------------------------------------------------
1471 /*QUAKED func_glass (0 .8 .5) ? INVINCIBLE
1472 When destroyed, fires it's target, breaks into glass chunks and plays glass noise
1473 For now, instantly breaks on impact
1474
1475 INVINCIBLE - can only be broken by being used
1476
1477 "targetname" entities with matching target will fire it
1478 "target" all entities with a matching targetname will be used when this is destroyed
1479 "health" default is 1
1480 */
1481 //-----------------------------------------------------
SP_func_glass(gentity_t * self)1482 void SP_func_glass( gentity_t *self )
1483 {
1484 if ( !(self->spawnflags & 1 ))
1485 {
1486 if ( !self->health )
1487 {
1488 self->health = 1;
1489 }
1490 }
1491
1492 if ( self->health )
1493 {
1494 self->takedamage = qtrue;
1495 }
1496
1497 self->e_UseFunc = useF_funcGlassUse;
1498 self->e_DieFunc = dieF_funcGlassDie;
1499
1500 VectorCopy( self->s.origin, self->pos1 );
1501
1502 gi.SetBrushModel( self, self->model );
1503 self->svFlags |= (SVF_GLASS_BRUSH|SVF_BBRUSH);
1504 self->material = MAT_GLASS;
1505
1506 self->s.eType = ET_MOVER;
1507
1508 self->s.pos.trType = TR_STATIONARY;
1509 VectorCopy( self->pos1, self->s.pos.trBase );
1510
1511 G_SoundIndex( "sound/effects/glassbreak1.wav" );
1512 G_EffectIndex( "misc/glass_impact" );
1513
1514 gi.linkentity( self );
1515 }
1516
G_EntIsBreakable(int entityNum,gentity_t * breaker)1517 qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker )
1518 {//breakable brush/model that can actually be broken
1519 if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
1520 {
1521 return qfalse;
1522 }
1523
1524 gentity_t *ent = &g_entities[entityNum];
1525 if ( !ent->takedamage )
1526 {
1527 return qfalse;
1528 }
1529
1530 if ( ent->NPC_targetname )
1531 {//only a specific entity can break this!
1532 if ( !breaker
1533 || !breaker->targetname
1534 || Q_stricmp( ent->NPC_targetname, breaker->targetname ) != 0 )
1535 {//I'm not the one who can break it
1536 return qfalse;
1537 }
1538 }
1539
1540 if ( (ent->svFlags&SVF_GLASS_BRUSH) )
1541 {
1542 return qtrue;
1543 }
1544 if ( (ent->svFlags&SVF_BBRUSH) )
1545 {
1546 return qtrue;
1547 }
1548 if ( !Q_stricmp( "misc_model_breakable", ent->classname ) )
1549 {
1550 return qtrue;
1551 }
1552 if ( !Q_stricmp( "misc_maglock", ent->classname ) )
1553 {
1554 return qtrue;
1555 }
1556
1557 return qfalse;
1558 }
1559