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