1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 #include "fireball/fireballs.h"
13 #include "graphics/tmapper.h"
14 #include "render/3d.h"
15 #include "model/model.h"
16 #include "object/object.h"
17 #include "ship/ship.h"
18 #include "gamesnd/gamesnd.h"
19 #include "localization/localize.h"
20 #include "cmdline/cmdline.h"
21 #include "parse/parselo.h"
22 #include "globalincs/pstypes.h"
23 #include "asteroid/asteroid.h"
24 
25 #include <stdlib.h>
26 
27 
28 // make use of the LOD checker for tbl/tbm parsing (from weapons.cpp)
29 extern SCP_vector<lod_checker> LOD_checker;
30 
31 static SCP_vector<color> LOD_color;
32 
33 int Warp_model;
34 int Knossos_warp_ani_used;
35 
36 #define WARPHOLE_GROW_TIME		(2.35f)	// time for warphole to reach max size (also time to shrink to nothing once it begins to shrink)
37 
38 #define MAX_FIREBALL_LOD						4
39 
40 #define MAX_FIREBALLS	200
41 
42 #define MAX_WARP_LOD	0
43 
44 fireball Fireballs[MAX_FIREBALLS];
45 
46 fireball_info Fireball_info[MAX_FIREBALL_TYPES];
47 
48 int fireball_used[MAX_FIREBALL_TYPES];
49 
50 int Num_fireballs = 0;
51 int Num_fireball_types = 0;
52 
53 int fireballs_inited = 0;
54 
55 int Warp_glow_bitmap = -1;
56 int Warp_ball_bitmap = -1;
57 
58 #define FB_INDEX(fb)	(fb-Fireballs)
59 
60 /**
61  * Play warp in sound for warp effect
62  */
fireball_play_warphole_open_sound(int ship_class,fireball * fb)63 void fireball_play_warphole_open_sound(int ship_class, fireball *fb)
64 {
65 	int		sound_index;
66 	float		range_multiplier = 1.0f;
67 	object	*fireball_objp;
68 
69 	Assert((fb != NULL) && (fb->objnum >= 0));
70 	if((fb == NULL) || (fb->objnum < 0)){
71 		return;
72 	}
73 	fireball_objp = &Objects[fb->objnum];
74 
75 	sound_index = SND_WARP_IN;
76 
77 	if(fb->warp_open_sound_index > -1) {
78 		sound_index = fb->warp_open_sound_index;
79 	} else if((ship_class >= 0) && (ship_class < Num_ship_classes)){
80 		if ( Ship_info[ship_class].flags & SIF_HUGE_SHIP ) {
81 			sound_index = SND_CAPITAL_WARP_IN;
82 			fb->flags |= FBF_WARP_CAPITAL_SIZE;
83 		} else if ( Ship_info[ship_class].flags & SIF_BIG_SHIP ) {
84 			range_multiplier = 6.0f;
85 			fb->flags |= FBF_WARP_CRUISER_SIZE;
86 		}
87 	}
88 
89 	snd_play_3d(&Snds[sound_index], &fireball_objp->pos, &Eye_position, fireball_objp->radius, NULL, 0, 1.0f, SND_PRIORITY_DOUBLE_INSTANCE, NULL, range_multiplier); // play warp sound effect
90 }
91 
92 /**
93  * Play warp out sound for warp effect
94  */
fireball_play_warphole_close_sound(fireball * fb)95 void fireball_play_warphole_close_sound(fireball *fb)
96 {
97 	int	sound_index;
98 
99 	object *fireball_objp;
100 
101 	fireball_objp = &Objects[fb->objnum];
102 
103 	sound_index = SND_WARP_OUT;
104 
105 	if ( fb->warp_close_sound_index > -1 ) {
106 		sound_index = fb->warp_close_sound_index;
107 	} else if ( fb->flags & FBF_WARP_CAPITAL_SIZE ) {
108 		sound_index = SND_CAPITAL_WARP_OUT;
109 	} else {
110 		return;
111 	}
112 
113 	snd_play_3d(&Snds[sound_index], &fireball_objp->pos, &Eye_position, fireball_objp->radius); // play warp sound effect
114 }
115 
116 /**
117  * Set default colors for each explosion type (original values from object.cpp)
118  */
fireball_set_default_color(int idx)119 static void fireball_set_default_color(int idx)
120 {
121 	Assert( (idx >= 0) && (idx < MAX_FIREBALL_TYPES) );
122 
123 	switch (idx)
124 	{
125 		case FIREBALL_EXPLOSION_LARGE1:
126 		case FIREBALL_EXPLOSION_LARGE2:
127 		case FIREBALL_EXPLOSION_MEDIUM:
128 		case FIREBALL_ASTEROID:
129 			Fireball_info[idx].exp_color[0] = 1.0f;
130 			Fireball_info[idx].exp_color[1] = 0.5f;
131 			Fireball_info[idx].exp_color[2] = 0.125f;
132 			break;
133 
134 		case FIREBALL_WARP:
135 			Fireball_info[idx].exp_color[0] = 0.75f;
136 			Fireball_info[idx].exp_color[1] = 0.75f;
137 			Fireball_info[idx].exp_color[2] = 1.0f;
138 			break;
139 
140 
141 		case FIREBALL_KNOSSOS:
142 			Fireball_info[idx].exp_color[0] = 0.75f;
143 			Fireball_info[idx].exp_color[1] = 1.0f;
144 			Fireball_info[idx].exp_color[2] = 0.75f;
145 			break;
146 
147 		default:
148 			Fireball_info[idx].exp_color[0] = 1.0f;
149 			Fireball_info[idx].exp_color[1] = 1.0f;
150 			Fireball_info[idx].exp_color[2] = 1.0f;
151 			break;
152 	}
153 }
154 
155 /**
156  * Parse fireball tbl
157  *
158  * NOTE: we can't be too trusting here so a tbm will only modify the LOD count, not add an entry
159  */
parse_fireball_tbl(const char * filename)160 void parse_fireball_tbl(const char *filename)
161 {
162 	int rval;
163 	lod_checker lod_check;
164 	color fb_color;
165 
166 	// open localization
167 	lcl_ext_open();
168 
169 	if ((rval = setjmp(parse_abort)) != 0) {
170 		mprintf(("TABLES: Unable to parse '%s'!  Error code = %i.\n", filename, rval));
171 		lcl_ext_close();
172 		return;
173 	}
174 
175 	read_file_text(filename, CF_TYPE_TABLES);
176 	reset_parse();
177 
178 	required_string("#Start");
179 
180 	while (required_string_either("#End", "$Name:")) {
181 		memset( &lod_check, 0, sizeof(lod_checker) );
182 
183 		// base filename
184 		required_string("$Name:");
185 		stuff_string(lod_check.filename, F_NAME, MAX_FILENAME_LEN);
186 
187 		lod_check.override = -1;
188 
189 		// these entries should only be in TBMs, and it has to include at least one
190 		if ( Parsing_modular_table ) {
191 			if (optional_string("+Explosion_Medium")) {
192 				lod_check.override = FIREBALL_EXPLOSION_MEDIUM;
193 			} else if (optional_string("+Warp_Effect")) {
194 				lod_check.override = FIREBALL_WARP;
195 			} else if (optional_string("+Knossos_Effect")) {
196 				lod_check.override = FIREBALL_KNOSSOS;
197 			} else if (optional_string("+Asteroid")) {
198 				lod_check.override = FIREBALL_ASTEROID;
199 			} else if (optional_string("+Explosion_Large1")) {
200 				lod_check.override = FIREBALL_EXPLOSION_LARGE1;
201 			} else if (optional_string("+Explosion_Large2")){
202 				lod_check.override = FIREBALL_EXPLOSION_LARGE2;
203 			} else {
204 				required_string("+Custom_Fireball");
205 				stuff_int(&lod_check.override);
206 			}
207 		}
208 
209 		lod_check.num_lods = 1;
210 
211 		// Do we have an LOD num
212 		if (optional_string("$LOD:")) {
213 			stuff_int(&lod_check.num_lods);
214 		}
215 
216 		if (lod_check.num_lods > MAX_FIREBALL_LOD) {
217 			lod_check.num_lods = MAX_FIREBALL_LOD;
218 		}
219 
220 		// check for particular lighting color
221 		if ( optional_string("$Light color:") ) {
222 			int r, g, b;
223 
224 			stuff_int(&r);
225 			stuff_int(&g);
226 			stuff_int(&b);
227 
228 			CLAMP(r, 0, 255);
229 			CLAMP(g, 0, 255);
230 			CLAMP(b, 0, 255);
231 
232 			gr_init_color(&fb_color, r, g, b);
233 		} else {
234 			// to keep things simple, we just use 0 alpha to indicate that a default value should be used
235 			memset( &fb_color, 0, sizeof(color) );
236 		}
237 
238 		// we may use one filename for multiple entries so we'll have to handle dupes post parse
239 		LOD_checker.push_back(lod_check);
240 		LOD_color.push_back(fb_color);
241 	}
242 
243 	required_string("#End");
244 
245 	// close localization
246 	lcl_ext_close();
247 }
248 
fireball_parse_tbl()249 void fireball_parse_tbl()
250 {
251 	int i = 0, j;
252 	SCP_vector<lod_checker>::iterator lod;
253 
254 	memset( &Fireball_info, 0, sizeof(fireball_info) * MAX_FIREBALL_TYPES );
255 
256 
257 	parse_fireball_tbl("fireball.tbl");
258 
259 	// look for any modular tables
260 	parse_modular_table(NOX("*-fbl.tbm"), parse_fireball_tbl);
261 
262 	// we've got our list so pass it off for final checking and loading.
263 	// we assume that entries in fireball.tbl are in the correct order
264 	for (lod = LOD_checker.begin(); lod != LOD_checker.end(); ++lod) {
265 		if ( (i < MAX_FIREBALL_TYPES) && (lod->override < 0) ) {
266 			strcpy_s( Fireball_info[i].lod[0].filename, lod->filename );
267 			Fireball_info[i].lod_count = lod->num_lods;
268 			Num_fireball_types++;
269 
270 			if (LOD_color[i].alpha == 255) {
271 				Fireball_info[i].exp_color[0] = (LOD_color[i].red / 255.0f);
272 				Fireball_info[i].exp_color[1] = (LOD_color[i].green / 255.0f);
273 				Fireball_info[i].exp_color[2] = (LOD_color[i].blue / 255.0f);
274 			} else {
275 				fireball_set_default_color(i);
276 			}
277 		}
278 		i++;
279 	}
280 
281 	// having to do this twice is less than optimal, but less error prone too.
282 	// this handles (and should only have to handle) TBM related entries
283 	i = 0;
284 	for (lod = LOD_checker.begin(); lod != LOD_checker.end(); ++lod) {
285 		// try entry replacement
286 		if ( (lod->override >= 0) && (lod->override < Num_fireball_types) ) {
287 			strcpy_s( Fireball_info[lod->override].lod[0].filename, lod->filename );
288 			Fireball_info[lod->override].lod_count = lod->num_lods;
289 
290 			if (LOD_color[i].alpha == 255) {
291 				Fireball_info[lod->override].exp_color[0] = (LOD_color[i].red / 255.0f);
292 				Fireball_info[lod->override].exp_color[1] = (LOD_color[i].green / 255.0f);
293 				Fireball_info[lod->override].exp_color[2] = (LOD_color[i].blue / 255.0f);
294 			} else {
295 				fireball_set_default_color(lod->override);
296 			}
297 		}
298 	}
299 
300 	// fill in extra LOD filenames
301 	for (i = 0; i < Num_fireball_types; i++) {
302 		for (j = 1; j < Fireball_info[i].lod_count; j++) {
303 			sprintf( Fireball_info[i].lod[j].filename, "%s_%d", Fireball_info[i].lod[0].filename, j);
304 		}
305 	}
306 
307 	// done
308 	LOD_checker.clear();
309 }
310 
311 
fireball_load_data()312 void fireball_load_data()
313 {
314 	int				i, idx;
315 	fireball_info	*fd;
316 
317 	for ( i = 0; i < Num_fireball_types; i++ ) {
318 		fd = &Fireball_info[i];
319 
320 		for(idx=0; idx<fd->lod_count; idx++){
321 			// we won't use a warp effect lod greater than MAX_WARP_LOD so don't load it either
322 			if ( (i == FIREBALL_WARP) && (idx > MAX_WARP_LOD) )
323 				continue;
324 
325 			fd->lod[idx].bitmap_id	= bm_load_animation( fd->lod[idx].filename, &fd->lod[idx].num_frames, &fd->lod[idx].fps, NULL, 1 );
326 			if ( fd->lod[idx].bitmap_id < 0 ) {
327 				Error(LOCATION, "Could not load %s anim file\n", fd->lod[idx].filename);
328 			}
329 		}
330 	}
331 
332 	if ( Warp_glow_bitmap == -1 )	{
333 		Warp_glow_bitmap = bm_load( NOX("warpglow01") );
334 	}
335 	if ( Warp_ball_bitmap == -1 )	{
336 		Warp_ball_bitmap = bm_load( NOX("warpball01") );
337 	}
338 }
339 
340 // This will get called at the start of each level.
fireball_init()341 void fireball_init()
342 {
343 	int i;
344 
345 	if ( !fireballs_inited ) {
346 		fireballs_inited = 1;
347 
348 		// Do all the processing that happens only once
349 		fireball_parse_tbl();
350 		fireball_load_data();
351 	}
352 
353 	// Reset everything between levels
354 	Num_fireballs = 0;
355 	for (i=0; i<MAX_FIREBALLS; i++ )	{
356 		Fireballs[i].objnum	= -1;
357 	}
358 
359 	// Goober5000 - reset Knossos warp flag
360 	Knossos_warp_ani_used = 0;
361 
362 	mprintf(("Loading warp model\n"));
363 	Warp_model = -1;
364 
365 	// Goober5000 - check for existence of file before trying to load it
366 	if (cf_exists_full("warp.pof", CF_TYPE_MODELS))
367 	{
368 		Warp_model = model_load("warp.pof", 0, NULL, 0);
369 	}
370 
371 	mprintf((" %d\n", Warp_model));
372 }
373 
MONITOR(NumFireballsRend)374 MONITOR( NumFireballsRend )
375 
376 void fireball_render(object * obj)
377 {
378 	int		num;
379 	vertex	p;
380 	fireball	*fb;
381 
382 	MONITOR_INC( NumFireballsRend, 1 );
383 
384 	num = obj->instance;
385 	fb = &Fireballs[num];
386 
387 	if ( Fireballs[num].current_bitmap < 0 )
388 		return;
389 
390 	// turn off fogging
391 	if(The_mission.flags & MISSION_FLAG_FULLNEB){
392 		gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
393 	}
394 
395 	if(Cmdline_nohtl) {
396 		g3_rotate_vertex(&p, &obj->pos );
397 	}else{
398 		g3_transfer_vertex(&p, &obj->pos);
399 	}
400 
401 	switch( fb->fireball_render_type )	{
402 
403 		case FIREBALL_MEDIUM_EXPLOSION:
404 			batch_add_bitmap (
405 				Fireballs[num].current_bitmap,
406 				TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT | TMAP_FLAG_SOFT_QUAD,
407 				&p,
408 				fb->orient,
409 				obj->radius
410 			);
411 			break;
412 
413 		case FIREBALL_LARGE_EXPLOSION:
414 			// Make the big explosions rotate with the viewer.
415 			batch_add_bitmap_rotated (
416 				Fireballs[num].current_bitmap,
417 				TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT | TMAP_FLAG_SOFT_QUAD,
418 				&p,
419 				(i2fl(fb->orient)*PI)/180.0f,
420 				obj->radius
421 			);
422 			break;
423 
424 		case FIREBALL_WARP_EFFECT: {
425 
426 				float percent_life = fb->time_elapsed / fb->total_time;
427 
428 				float rad;
429 
430 				// Code to make effect grow/shrink.
431 				float t = fb->time_elapsed;
432 
433 				if ( t < WARPHOLE_GROW_TIME )	{
434 					rad = (float)pow(t/WARPHOLE_GROW_TIME,0.4f)*obj->radius;
435 				} else if ( t < fb->total_time - WARPHOLE_GROW_TIME )	{
436 					rad = obj->radius;
437 				} else {
438 					rad = (float)pow((fb->total_time - t)/WARPHOLE_GROW_TIME,0.4f)*obj->radius;
439 				}
440 
441 				warpin_render(obj, &obj->orient, &obj->pos, Fireballs[num].current_bitmap, rad, percent_life, obj->radius, (Fireballs[num].flags & FBF_WARP_3D) );
442 			}
443 			break;
444 
445 
446 		default:
447 			Int3();
448 	}
449 }
450 
451 /**
452  * Delete a fireball.
453  * Called by object_delete() code... do not call directly.
454  */
fireball_delete(object * obj)455 void fireball_delete( object * obj )
456 {
457 	int	num;
458 	fireball	*fb;
459 
460 	num = obj->instance;
461 	fb = &Fireballs[num];
462 
463 	Assert( fb->objnum == OBJ_INDEX(obj));
464 
465 	Fireballs[num].objnum = -1;
466 	Num_fireballs--;
467 	Assert( Num_fireballs >= 0 );
468 }
469 
470 /**
471  * Delete all active fireballs, by calling obj_delete directly.
472  */
fireball_delete_all()473 void fireball_delete_all()
474 {
475 	fireball	*fb;
476 	int		i;
477 
478 	for ( i = 0; i < MAX_FIREBALLS; i++ ) {
479 		fb = &Fireballs[i];
480 		if ( fb->objnum != -1 ) {
481 			obj_delete(fb->objnum);
482 		}
483 	}
484 }
485 
fireball_set_framenum(int num)486 void fireball_set_framenum(int num)
487 {
488 	int				framenum;
489 	fireball			*fb;
490 	fireball_info	*fd;
491 	fireball_lod	*fl;
492 
493 	fb = &Fireballs[num];
494 	fd = &Fireball_info[Fireballs[num].fireball_info_index];
495 
496 	// valid lod?
497 	fl = NULL;
498 	if((fb->lod >= 0) && (fb->lod < fd->lod_count)){
499 		fl = &Fireball_info[Fireballs[num].fireball_info_index].lod[fb->lod];
500 	}
501 	if(fl == NULL){
502 		// argh
503 		return;
504 	}
505 
506 	if ( fb->fireball_render_type == FIREBALL_WARP_EFFECT )	{
507 		float total_time = i2fl(fl->num_frames) / fl->fps;	// in seconds
508 
509 		framenum = fl2i(fb->time_elapsed * fl->num_frames / total_time + 0.5);
510 
511 		if ( framenum < 0 ) framenum = 0;
512 
513 		framenum = framenum % fl->num_frames;
514 
515 		if ( fb->orient )	{
516 			// warp out effect plays backwards
517 			framenum = fl->num_frames-framenum-1;
518 			fb->current_bitmap = fl->bitmap_id + framenum;
519 		} else {
520 			fb->current_bitmap = fl->bitmap_id + framenum;
521 		}
522 	} else {
523 
524 		framenum = fl2i(fb->time_elapsed / fb->total_time * fl->num_frames + 0.5);
525 
526 		// ensure we don't go past the number of frames of animation
527 		if ( framenum > (fl->num_frames-1) ) {
528 			framenum = (fl->num_frames-1);
529 			Objects[fb->objnum].flags |= OF_SHOULD_BE_DEAD;
530 		}
531 
532 		if ( framenum < 0 ) framenum = 0;
533 		fb->current_bitmap = fl->bitmap_id + framenum;
534 	}
535 }
536 
fireball_is_perishable(object * obj)537 int fireball_is_perishable(object * obj)
538 {
539 	//	return 1;
540 	int			num, objnum;
541 	fireball		*fb;
542 
543 	num = obj->instance;
544 	objnum = OBJ_INDEX(obj);
545 	Assert( Fireballs[num].objnum == objnum );
546 
547 	fb = &Fireballs[num];
548 
549 	if ( fb->fireball_render_type == FIREBALL_MEDIUM_EXPLOSION )
550 		return 1;
551 
552 	if ( !(fb->fireball_render_type == FIREBALL_WARP_EFFECT) )	{
553 		if ( !(obj->flags & OF_WAS_RENDERED))	{
554 			return 1;
555 		}
556 	}
557 
558 	return 0;
559 }
560 
561 
562 /**
563  * There are too many fireballs, so delete the oldest small one
564  * to free up a slot.
565  *
566  * @return The fireball slot freed.
567  */
fireball_free_one()568 int fireball_free_one()
569 {
570 	fireball	*fb;
571 	int		i;
572 
573 	int		oldest_objnum = -1, oldest_slotnum = -1;
574 	float		lifeleft, oldest_lifeleft = 0.0f;
575 
576 	for ( i = 0; i < MAX_FIREBALLS; i++ ) {
577 		fb = &Fireballs[i];
578 
579 		// only remove the ones that aren't warp effects
580 		if ( (fb->objnum >= 0) && fireball_is_perishable(&Objects[fb->objnum]) )	{
581 
582 			lifeleft = fb->total_time - fb->time_elapsed;
583 			if ( (oldest_objnum < 0) || (lifeleft < oldest_lifeleft) )	{
584 				oldest_slotnum = i;
585 				oldest_lifeleft = lifeleft;
586 				oldest_objnum = fb->objnum;
587 			}
588 			break;
589 		}
590 	}
591 
592 	if ( oldest_objnum > -1 )	{
593 		obj_delete(oldest_objnum);
594 	}
595 	return oldest_slotnum;
596 }
597 
fireball_is_warp(object * obj)598 int fireball_is_warp(object * obj)
599 {
600 	int			num, objnum;
601 	fireball		*fb;
602 
603 	num = obj->instance;
604 	objnum = OBJ_INDEX(obj);
605 	Assert( Fireballs[num].objnum == objnum );
606 
607 	fb = &Fireballs[num];
608 
609 	if ( fb->fireball_render_type == FIREBALL_WARP_EFFECT)
610 		return 1;
611 
612 	return 0;
613 }
614 
615 // maybe play sound effect for warp hole closing
fireball_maybe_play_warp_close_sound(fireball * fb)616 void fireball_maybe_play_warp_close_sound(fireball *fb)
617 {
618 	float life_left;
619 
620 	// If not a warphole fireball, do a quick out
621 	if ( !(fb->fireball_render_type == FIREBALL_WARP_EFFECT)) {
622 		return;
623 	}
624 
625 	// If the warhole close sound has been played, don't play it again!
626 	if ( fb->flags & FBF_WARP_CLOSE_SOUND_PLAYED ) {
627 		return;
628 	}
629 
630 	life_left = fb->total_time - fb->time_elapsed;
631 
632 	if ( life_left < WARPHOLE_GROW_TIME ) {
633 		fireball_play_warphole_close_sound(fb);
634 		fb->flags |= FBF_WARP_CLOSE_SOUND_PLAYED;
635 	}
636 }
637 
MONITOR(NumFireballs)638 MONITOR( NumFireballs )
639 
640 void fireball_process_post(object * obj, float frame_time)
641 {
642 	int			num, objnum;
643 	fireball		*fb;
644 
645 	MONITOR_INC( NumFireballs, 1 );
646 
647 	num = obj->instance;
648 	objnum = OBJ_INDEX(obj);
649 	Assert( Fireballs[num].objnum == objnum );
650 
651 	fb = &Fireballs[num];
652 
653 	fb->time_elapsed += frame_time;
654 	if ( fb->time_elapsed > fb->total_time ) {
655 		obj->flags |= OF_SHOULD_BE_DEAD;
656 	}
657 
658 	fireball_maybe_play_warp_close_sound(fb);
659 
660 	fireball_set_framenum(num);
661 }
662 
663 /**
664  * Returns life left of a fireball in seconds
665  */
fireball_lifeleft(object * obj)666 float fireball_lifeleft( object *obj )
667 {
668 	int			num, objnum;
669 	fireball		*fb;
670 
671 	num = obj->instance;
672 	objnum = OBJ_INDEX(obj);
673 	Assert( Fireballs[num].objnum == objnum );
674 
675 	fb = &Fireballs[num];
676 
677 	return fb->total_time - fb->time_elapsed;
678 }
679 
680 /**
681  * Returns life left of a fireball in percent
682  */
fireball_lifeleft_percent(object * obj)683 float fireball_lifeleft_percent( object *obj )
684 {
685 	int			num, objnum;
686 	fireball		*fb;
687 
688 	num = obj->instance;
689 	objnum = OBJ_INDEX(obj);
690 	Assert( Fireballs[num].objnum == objnum );
691 
692 	fb = &Fireballs[num];
693 
694 	float p = (fb->total_time - fb->time_elapsed) / fb->total_time;
695 	if (p < 0)p=0.0f;
696 	return p;
697 }
698 
699 /**
700  * Determine LOD to use
701  */
fireball_get_lod(vec3d * pos,fireball_info * fd,float size)702 int fireball_get_lod(vec3d *pos, fireball_info *fd, float size)
703 {
704 	vertex v;
705 	int x, y, w, h, bm_size;
706 	int must_stop = 0;
707 	int ret_lod = 1;
708 	int behind = 0;
709 
710 	// bogus
711 	if(fd == NULL){
712 		return 1;
713 	}
714 
715 	// start the frame
716 	extern int G3_count;
717 
718 	if(!G3_count){
719 		g3_start_frame(1);
720 		must_stop = 1;
721 	}
722 	g3_set_view_matrix(&Eye_position, &Eye_matrix, Eye_fov);
723 
724 	// get extents of the rotated bitmap
725 	g3_rotate_vertex(&v, pos);
726 
727 	// if vertex is behind, find size if in front, then drop down 1 LOD
728 	if (v.codes & CC_BEHIND) {
729 		float dist = vm_vec_dist_quick(&Eye_position, pos);
730 		vec3d temp;
731 
732 		behind = 1;
733 		vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.vec.fvec, dist);
734 		g3_rotate_vertex(&v, &temp);
735 
736 		// if still behind, bail and go with default
737 		if (v.codes & CC_BEHIND) {
738 			behind = 0;
739 		}
740 	}
741 
742 	if(!g3_get_bitmap_dims(fd->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
743 		if (Detail.hardware_textures == 4) {
744 			// straight LOD
745 			if(w <= bm_size/8){
746 				ret_lod = 3;
747 			} else if(w <= bm_size/2){
748 				ret_lod = 2;
749 			} else if(w <= (1.56*bm_size)){
750 				ret_lod = 1;
751 			} else {
752 				ret_lod = 0;
753 			}
754 		} else {
755 			// less aggressive LOD for lower detail settings
756 			if(w <= bm_size/8){
757 				ret_lod = 3;
758 			} else if(w <= bm_size/3){
759 				ret_lod = 2;
760 			} else if(w <= (1.2*bm_size)){
761 				ret_lod = 1;
762 			} else {
763 				ret_lod = 0;
764 			}
765 		}
766 	}
767 
768 	// if it's behind, bump up LOD by 1
769 	if (behind) {
770 		ret_lod++;
771 	}
772 
773 	// end the frame
774 	if(must_stop){
775 		g3_end_frame();
776 	}
777 
778 	// return the best lod
779 	return MIN(ret_lod, fd->lod_count - 1);
780 }
781 
782 /**
783  * Create a fireball, return object index.
784  */
fireball_create(vec3d * pos,int fireball_type,int render_type,int parent_obj,float size,int reverse,vec3d * velocity,float warp_lifetime,int ship_class,matrix * orient_override,int low_res,int extra_flags,int warp_open_sound,int warp_close_sound)785 int fireball_create( vec3d * pos, int fireball_type, int render_type, int parent_obj, float size, int reverse, vec3d *velocity, float warp_lifetime, int ship_class, matrix *orient_override, int low_res, int extra_flags, int warp_open_sound, int warp_close_sound)
786 {
787 	int				n, objnum, fb_lod;
788 	object			*obj;
789 	fireball			*fb;
790 	fireball_info	*fd;
791 	fireball_lod	*fl;
792 
793 	Assert( fireball_type > -1 );
794 	Assert( fireball_type < Num_fireball_types );
795 
796 	fd = &Fireball_info[fireball_type];
797 
798 	// check to make sure this fireball type exists
799 	if (!fd->lod_count)
800 		return -1;
801 
802 	if ( !(Game_detail_flags & DETAIL_FLAG_FIREBALLS) )	{
803 		if ( !((fireball_type == FIREBALL_WARP) || (fireball_type == FIREBALL_KNOSSOS)) )	{
804 			return -1;
805 		}
806 	}
807 
808 	if ( (Num_fireballs >= MAX_FIREBALLS) || (Num_objects >= MAX_OBJECTS) )	{
809 
810 		// out of slots, so free one up.
811 		n = fireball_free_one();
812 		if ( n < 0 ) {
813 			return -1;
814 		}
815 	} else {
816 		for ( n = 0; n < MAX_FIREBALLS; n++ )	{
817 			if ( Fireballs[n].objnum < 0  )	{
818 				break;
819 			}
820 		}
821 		Assert( n != MAX_FIREBALLS );
822 	}
823 
824 	fb = &Fireballs[n];
825 
826 	// get an lod to use
827 	fb_lod = fireball_get_lod(pos, fd, size);
828 
829 	// change lod if low res is desired
830 	if (low_res) {
831 		fb_lod++;
832 		fb_lod = MIN(fb_lod, fd->lod_count - 1);
833 	}
834 
835 	// if this is a warpout fireball, never go higher than LOD 1
836 	if(fireball_type == FIREBALL_WARP){
837 		fb_lod = MAX_WARP_LOD;
838 	}
839 	fl = &fd->lod[fb_lod];
840 
841 	fb->lod = (char)fb_lod;
842 
843 	fb->flags = extra_flags;
844 	fb->warp_open_sound_index = warp_open_sound;
845 	fb->warp_close_sound_index = warp_close_sound;
846 
847 	matrix orient;
848 	if(orient_override != NULL){
849 		orient = *orient_override;
850 	} else {
851 		if ( parent_obj < 0 )	{
852 			orient = vmd_identity_matrix;
853 		} else {
854 			orient = Objects[parent_obj].orient;
855 		}
856 	}
857 
858 	objnum = obj_create(OBJ_FIREBALL, parent_obj, n, &orient, pos, size, OF_RENDERS);
859 
860 	if (objnum < 0) {
861 		Int3();				// Get John, we ran out of objects for fireballs
862 		return objnum;
863 	}
864 
865 	obj = &Objects[objnum];
866 
867 	fb->fireball_info_index = fireball_type;
868 	fb->fireball_render_type = render_type;
869 	fb->time_elapsed = 0.0f;
870 	fb->objnum = objnum;
871 	fb->current_bitmap = -1;
872 
873 	switch( fb->fireball_render_type )	{
874 
875 		case FIREBALL_MEDIUM_EXPLOSION:
876 			fb->orient = (myrand()>>8) & 7;							// 0 - 7
877 			break;
878 
879 		case FIREBALL_LARGE_EXPLOSION:
880 			fb->orient = (myrand()>>8) % 360;						// 0 - 359
881 			break;
882 
883 		case FIREBALL_WARP_EFFECT:
884 			// Play sound effect for warp hole opening up
885 			fireball_play_warphole_open_sound(ship_class, fb);
886 
887 			// warp in type
888 			if (reverse)	{
889 				fb->orient = 1;
890 				// if warp out, then reverse the orientation
891 				vm_vec_scale( &obj->orient.vec.fvec, -1.0f );	// Reverse the forward vector
892 				vm_vec_scale( &obj->orient.vec.rvec, -1.0f );	// Reverse the right vector
893 			} else {
894 				fb->orient = 0;
895 			}
896 			break;
897 
898 		default:
899 			Int3();
900 			break;
901 	}
902 
903 	if ( fb->fireball_render_type == FIREBALL_WARP_EFFECT )	{
904 		Assert( warp_lifetime >= 4.0f );		// Warp lifetime must be at least 4 seconds!
905 		if ( warp_lifetime < 4.0f )
906 			warp_lifetime = 4.0f;
907 		fb->total_time = warp_lifetime;	// in seconds
908 	} else {
909 		fb->total_time = i2fl(fl->num_frames) / fl->fps;	// in seconds
910 	}
911 
912 	fireball_set_framenum(n);
913 
914 	if ( velocity )	{
915 		// Make the explosion move at a constant velocity.
916 		obj->flags |= OF_PHYSICS;
917 		obj->phys_info.mass = 1.0f;
918 		obj->phys_info.side_slip_time_const = 0.0f;
919 		obj->phys_info.rotdamp = 0.0f;
920 		obj->phys_info.vel = *velocity;
921 		obj->phys_info.max_vel = *velocity;
922 		obj->phys_info.desired_vel = *velocity;
923 		obj->phys_info.speed = vm_vec_mag(velocity);
924 		vm_vec_zero(&obj->phys_info.max_rotvel);
925 	}
926 
927 	Num_fireballs++;
928 	return objnum;
929 }
930 
931 /**
932  * Called at game shutdown to clean up the fireball system
933  */
fireball_close()934 void fireball_close()
935 {
936 	if ( !fireballs_inited )
937 		return;
938 
939 	fireball_delete_all();
940 }
941 
fireballs_page_in()942 void fireballs_page_in()
943 {
944 	int				i, idx;
945 	fireball_info	*fd;
946 
947 	for ( i = 0; i < Num_fireball_types; i++ ) {
948 		if((i < NUM_DEFAULT_FIREBALLS) || fireball_used[i]){
949 			fd = &Fireball_info[i];
950 
951 			// if this is a Knossos ani, only load if Knossos_warp_ani_used is true
952 			if ( (i == FIREBALL_KNOSSOS) && !Knossos_warp_ani_used)
953 				continue;
954 
955 			for(idx=0; idx<fd->lod_count; idx++) {
956 				// we won't use a warp effect lod greater than MAX_WARP_LOD so don't load it either
957 				if ( (i == FIREBALL_WARP) && (idx > MAX_WARP_LOD) )
958 					continue;
959 
960 				bm_page_in_texture( fd->lod[idx].bitmap_id, fd->lod[idx].num_frames );
961 			}
962 		}
963 	}
964 
965 	bm_page_in_texture( Warp_glow_bitmap );
966 	bm_page_in_texture( Warp_ball_bitmap );
967 }
968 
fireball_get_color(int idx,float * red,float * green,float * blue)969 void fireball_get_color(int idx, float *red, float *green, float *blue)
970 {
971 	Assert( red && blue && green );
972 
973 	if ( (idx < 0) || (idx >= Num_fireball_types) ) {
974 		Int3();
975 
976 		*red = 1.0f;
977 		*green = 1.0f;
978 		*blue = 1.0f;
979 
980 		return;
981 	}
982 
983 	fireball_info *fbi = &Fireball_info[idx];
984 
985 	*red = fbi->exp_color[0];
986 	*green = fbi->exp_color[1];
987 	*blue = fbi->exp_color[2];
988 }
989 
fireball_ship_explosion_type(ship_info * sip)990 int fireball_ship_explosion_type(ship_info *sip)
991 {
992 	Assert( sip != NULL );
993 
994 	int index = -1;
995 	int ship_fireballs = (int)sip->explosion_bitmap_anims.size();
996 	int objecttype_fireballs = -1;
997 
998 	if (sip->class_type >= 0) {
999 		objecttype_fireballs = (int)Ship_types[sip->class_type].explosion_bitmap_anims.size();
1000 	}
1001 
1002 	if(ship_fireballs > 0){
1003 		index = sip->explosion_bitmap_anims[rand()%ship_fireballs];
1004 	} else if(objecttype_fireballs > 0){
1005 		index = Ship_types[sip->class_type].explosion_bitmap_anims[rand()%objecttype_fireballs];
1006 	}
1007 
1008 	return index;
1009 }
1010 
fireball_asteroid_explosion_type(asteroid_info * aip)1011 int fireball_asteroid_explosion_type(asteroid_info *aip)
1012 {
1013 	Assert( aip != NULL );
1014 
1015 	if (aip->explosion_bitmap_anims.empty())
1016 		return -1;
1017 
1018 	int index = -1;
1019 	int roid_fireballs = (int)aip->explosion_bitmap_anims.size();
1020 
1021 	if (roid_fireballs > 0) {
1022 		index = aip->explosion_bitmap_anims[rand()%roid_fireballs];
1023 	}
1024 
1025 	return index;
1026 }
1027 
fireball_wormhole_intensity(object * obj)1028 float fireball_wormhole_intensity( object *obj )
1029 {
1030 	int			num, objnum;
1031 	fireball		*fb;
1032 
1033 	num = obj->instance;
1034 	objnum = OBJ_INDEX(obj);
1035 	Assertion( Fireballs[num].objnum == objnum, "Basic sanity check. Fireballs[num].objnum (%d) should == objnum (%d)", Fireballs[num].objnum, objnum );
1036 
1037 	fb = &Fireballs[num];
1038 
1039 	float t = fb->time_elapsed;
1040 	float rad;
1041 
1042 	if ( t < WARPHOLE_GROW_TIME )	{
1043 		rad = (float)pow(t/WARPHOLE_GROW_TIME,0.4f);
1044 	} else if ( t < fb->total_time - WARPHOLE_GROW_TIME )	{
1045 		rad = 1;
1046 	} else {
1047 		rad = (float)pow((fb->total_time - t)/WARPHOLE_GROW_TIME,0.4f);
1048 	}
1049 	return rad;
1050 }
1051