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