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